2014-01-28 18:42:46 -05:00
|
|
|
package portmapper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"github.com/dotcloud/docker/pkg/iptables"
|
|
|
|
"github.com/dotcloud/docker/proxy"
|
|
|
|
"net"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
type mapping struct {
|
|
|
|
proto string
|
|
|
|
userlandProxy proxy.Proxy
|
|
|
|
host net.Addr
|
|
|
|
container net.Addr
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
chain *iptables.Chain
|
|
|
|
lock sync.Mutex
|
|
|
|
|
|
|
|
// udp:ip:port
|
|
|
|
currentMappings = make(map[string]*mapping)
|
2014-01-28 19:28:32 -05:00
|
|
|
newProxy = proxy.NewProxy
|
2014-01-28 18:42:46 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
|
|
|
|
ErrPortMappedForIP = errors.New("port is already mapped to ip")
|
|
|
|
ErrPortNotMapped = errors.New("port is not mapped")
|
|
|
|
)
|
|
|
|
|
2014-01-28 19:28:32 -05:00
|
|
|
func SetIptablesChain(c *iptables.Chain) {
|
2014-01-28 18:42:46 -05:00
|
|
|
chain = c
|
|
|
|
}
|
|
|
|
|
|
|
|
func Map(container net.Addr, hostIP net.IP, hostPort int) error {
|
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
|
|
|
var m *mapping
|
|
|
|
switch container.(type) {
|
|
|
|
case *net.TCPAddr:
|
|
|
|
m = &mapping{
|
|
|
|
proto: "tcp",
|
|
|
|
host: &net.TCPAddr{IP: hostIP, Port: hostPort},
|
|
|
|
container: container,
|
|
|
|
}
|
|
|
|
case *net.UDPAddr:
|
|
|
|
m = &mapping{
|
|
|
|
proto: "udp",
|
|
|
|
host: &net.UDPAddr{IP: hostIP, Port: hostPort},
|
|
|
|
container: container,
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return ErrUnknownBackendAddressType
|
|
|
|
}
|
|
|
|
|
|
|
|
key := getKey(m.host)
|
|
|
|
if _, exists := currentMappings[key]; exists {
|
|
|
|
return ErrPortMappedForIP
|
|
|
|
}
|
|
|
|
|
|
|
|
containerIP, containerPort := getIPAndPort(m.container)
|
|
|
|
if err := forward(iptables.Add, m.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-01-28 19:28:32 -05:00
|
|
|
p, err := newProxy(m.host, m.container)
|
2014-01-28 18:42:46 -05:00
|
|
|
if err != nil {
|
|
|
|
// need to undo the iptables rules before we reutrn
|
|
|
|
forward(iptables.Delete, m.proto, hostIP, hostPort, containerIP.String(), containerPort)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.userlandProxy = p
|
|
|
|
currentMappings[key] = m
|
|
|
|
|
|
|
|
go p.Run()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func Unmap(host net.Addr) error {
|
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
|
|
|
key := getKey(host)
|
|
|
|
data, exists := currentMappings[key]
|
|
|
|
if !exists {
|
|
|
|
return ErrPortNotMapped
|
|
|
|
}
|
|
|
|
|
|
|
|
data.userlandProxy.Close()
|
|
|
|
delete(currentMappings, key)
|
|
|
|
|
|
|
|
containerIP, containerPort := getIPAndPort(data.container)
|
|
|
|
hostIP, hostPort := getIPAndPort(data.host)
|
|
|
|
if err := forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getKey(a net.Addr) string {
|
|
|
|
switch t := a.(type) {
|
|
|
|
case *net.TCPAddr:
|
|
|
|
return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
|
|
|
|
case *net.UDPAddr:
|
|
|
|
return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func getIPAndPort(a net.Addr) (net.IP, int) {
|
|
|
|
switch t := a.(type) {
|
|
|
|
case *net.TCPAddr:
|
|
|
|
return t.IP, t.Port
|
|
|
|
case *net.UDPAddr:
|
|
|
|
return t.IP, t.Port
|
|
|
|
}
|
|
|
|
return nil, 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
|
|
|
if chain == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
|
|
|
|
}
|