github.com/jaegerpicker/docker@v0.7.7-0.20150325003727-22dba32b4dab/daemon/networkdriver/portmapper/mapper.go (about) 1 package portmapper 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "sync" 8 9 log "github.com/Sirupsen/logrus" 10 "github.com/docker/docker/daemon/networkdriver/portallocator" 11 "github.com/docker/docker/pkg/iptables" 12 ) 13 14 type mapping struct { 15 proto string 16 userlandProxy UserlandProxy 17 host net.Addr 18 container net.Addr 19 } 20 21 var ( 22 NewProxy = NewProxyCommand 23 24 DefaultPortMapper = NewWithPortAllocator(portallocator.DefaultPortAllocator) 25 SetIptablesChain = DefaultPortMapper.SetIptablesChain 26 Map = DefaultPortMapper.Map 27 Unmap = DefaultPortMapper.Unmap 28 ) 29 30 var ( 31 ErrUnknownBackendAddressType = errors.New("unknown container address type not supported") 32 ErrPortMappedForIP = errors.New("port is already mapped to ip") 33 ErrPortNotMapped = errors.New("port is not mapped") 34 ) 35 36 type PortMapper struct { 37 chain *iptables.Chain 38 39 // udp:ip:port 40 currentMappings map[string]*mapping 41 lock sync.Mutex 42 43 allocator *portallocator.PortAllocator 44 } 45 46 func New() *PortMapper { 47 return NewWithPortAllocator(portallocator.New()) 48 } 49 50 func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper { 51 return &PortMapper{ 52 currentMappings: make(map[string]*mapping), 53 allocator: allocator, 54 } 55 } 56 57 func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) { 58 pm.chain = c 59 } 60 61 func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) { 62 pm.lock.Lock() 63 defer pm.lock.Unlock() 64 65 var ( 66 m *mapping 67 proto string 68 allocatedHostPort int 69 proxy UserlandProxy 70 ) 71 72 switch container.(type) { 73 case *net.TCPAddr: 74 proto = "tcp" 75 if allocatedHostPort, err = pm.allocator.RequestPort(hostIP, proto, hostPort); err != nil { 76 return nil, err 77 } 78 79 m = &mapping{ 80 proto: proto, 81 host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort}, 82 container: container, 83 } 84 85 proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) 86 case *net.UDPAddr: 87 proto = "udp" 88 if allocatedHostPort, err = pm.allocator.RequestPort(hostIP, proto, hostPort); err != nil { 89 return nil, err 90 } 91 92 m = &mapping{ 93 proto: proto, 94 host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort}, 95 container: container, 96 } 97 98 proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port) 99 default: 100 return nil, ErrUnknownBackendAddressType 101 } 102 103 // release the allocated port on any further error during return. 104 defer func() { 105 if err != nil { 106 pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort) 107 } 108 }() 109 110 key := getKey(m.host) 111 if _, exists := pm.currentMappings[key]; exists { 112 return nil, ErrPortMappedForIP 113 } 114 115 containerIP, containerPort := getIPAndPort(m.container) 116 if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { 117 return nil, err 118 } 119 120 cleanup := func() error { 121 // need to undo the iptables rules before we return 122 proxy.Stop() 123 pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) 124 if err := pm.allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { 125 return err 126 } 127 128 return nil 129 } 130 131 if err := proxy.Start(); err != nil { 132 if err := cleanup(); err != nil { 133 return nil, fmt.Errorf("Error during port allocation cleanup: %v", err) 134 } 135 return nil, err 136 } 137 m.userlandProxy = proxy 138 pm.currentMappings[key] = m 139 return m.host, nil 140 } 141 142 func (pm *PortMapper) Unmap(host net.Addr) error { 143 pm.lock.Lock() 144 defer pm.lock.Unlock() 145 146 key := getKey(host) 147 data, exists := pm.currentMappings[key] 148 if !exists { 149 return ErrPortNotMapped 150 } 151 152 data.userlandProxy.Stop() 153 154 delete(pm.currentMappings, key) 155 156 containerIP, containerPort := getIPAndPort(data.container) 157 hostIP, hostPort := getIPAndPort(data.host) 158 if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { 159 log.Errorf("Error on iptables delete: %s", err) 160 } 161 162 switch a := host.(type) { 163 case *net.TCPAddr: 164 return pm.allocator.ReleasePort(a.IP, "tcp", a.Port) 165 case *net.UDPAddr: 166 return pm.allocator.ReleasePort(a.IP, "udp", a.Port) 167 } 168 return nil 169 } 170 171 func getKey(a net.Addr) string { 172 switch t := a.(type) { 173 case *net.TCPAddr: 174 return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp") 175 case *net.UDPAddr: 176 return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp") 177 } 178 return "" 179 } 180 181 func getIPAndPort(a net.Addr) (net.IP, int) { 182 switch t := a.(type) { 183 case *net.TCPAddr: 184 return t.IP, t.Port 185 case *net.UDPAddr: 186 return t.IP, t.Port 187 } 188 return nil, 0 189 } 190 191 func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { 192 if pm.chain == nil { 193 return nil 194 } 195 return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort) 196 }