| package portmapper |
| |
| import ( |
| "errors" |
| "fmt" |
| "net" |
| "sync" |
| |
| log "github.com/Sirupsen/logrus" |
| "github.com/docker/docker/daemon/networkdriver/portallocator" |
| "github.com/docker/docker/pkg/iptables" |
| ) |
| |
| type mapping struct { |
| proto string |
| userlandProxy UserlandProxy |
| host net.Addr |
| container net.Addr |
| } |
| |
| var NewProxy = NewProxyCommand |
| |
| 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") |
| ) |
| |
| type PortMapper struct { |
| chain *iptables.Chain |
| |
| // udp:ip:port |
| currentMappings map[string]*mapping |
| lock sync.Mutex |
| |
| Allocator *portallocator.PortAllocator |
| } |
| |
| func New() *PortMapper { |
| return NewWithPortAllocator(portallocator.New()) |
| } |
| |
| func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper { |
| return &PortMapper{ |
| currentMappings: make(map[string]*mapping), |
| Allocator: allocator, |
| } |
| } |
| |
| func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) { |
| pm.chain = c |
| } |
| |
| func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) { |
| pm.lock.Lock() |
| defer pm.lock.Unlock() |
| |
| var ( |
| m *mapping |
| proto string |
| allocatedHostPort int |
| proxy UserlandProxy |
| ) |
| |
| switch container.(type) { |
| case *net.TCPAddr: |
| proto = "tcp" |
| if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil { |
| return nil, err |
| } |
| |
| m = &mapping{ |
| proto: proto, |
| host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort}, |
| container: container, |
| } |
| |
| proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) |
| case *net.UDPAddr: |
| proto = "udp" |
| if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil { |
| return nil, err |
| } |
| |
| m = &mapping{ |
| proto: proto, |
| host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort}, |
| container: container, |
| } |
| |
| proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port) |
| default: |
| return nil, ErrUnknownBackendAddressType |
| } |
| |
| // release the allocated port on any further error during return. |
| defer func() { |
| if err != nil { |
| pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort) |
| } |
| }() |
| |
| key := getKey(m.host) |
| if _, exists := pm.currentMappings[key]; exists { |
| return nil, ErrPortMappedForIP |
| } |
| |
| containerIP, containerPort := getIPAndPort(m.container) |
| if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { |
| return nil, err |
| } |
| |
| cleanup := func() error { |
| // need to undo the iptables rules before we return |
| proxy.Stop() |
| pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) |
| if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| if err := proxy.Start(); err != nil { |
| if err := cleanup(); err != nil { |
| return nil, fmt.Errorf("Error during port allocation cleanup: %v", err) |
| } |
| return nil, err |
| } |
| m.userlandProxy = proxy |
| pm.currentMappings[key] = m |
| return m.host, nil |
| } |
| |
| func (pm *PortMapper) Unmap(host net.Addr) error { |
| pm.lock.Lock() |
| defer pm.lock.Unlock() |
| |
| key := getKey(host) |
| data, exists := pm.currentMappings[key] |
| if !exists { |
| return ErrPortNotMapped |
| } |
| |
| data.userlandProxy.Stop() |
| |
| delete(pm.currentMappings, key) |
| |
| containerIP, containerPort := getIPAndPort(data.container) |
| hostIP, hostPort := getIPAndPort(data.host) |
| if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { |
| log.Errorf("Error on iptables delete: %s", err) |
| } |
| |
| switch a := host.(type) { |
| case *net.TCPAddr: |
| return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port) |
| case *net.UDPAddr: |
| return pm.Allocator.ReleasePort(a.IP, "udp", a.Port) |
| } |
| 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 (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { |
| if pm.chain == nil { |
| return nil |
| } |
| return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort) |
| } |