github.com/rish1988/moby@v25.0.2+incompatible/libnetwork/portmapper/mapper.go (about) 1 package portmapper 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net" 8 9 "github.com/containerd/log" 10 "github.com/docker/docker/libnetwork/portallocator" 11 "github.com/ishidawataru/sctp" 12 ) 13 14 type mapping struct { 15 proto string 16 userlandProxy userlandProxy 17 host net.Addr 18 container net.Addr 19 } 20 21 // newProxy is used to mock out the proxy server in tests 22 var newProxy = newProxyCommand 23 24 var ( 25 // ErrUnknownBackendAddressType refers to an unknown container or unsupported address type 26 ErrUnknownBackendAddressType = errors.New("unknown container address type not supported") 27 // ErrPortMappedForIP refers to a port already mapped to an ip address 28 ErrPortMappedForIP = errors.New("port is already mapped to ip") 29 // ErrPortNotMapped refers to an unmapped port 30 ErrPortNotMapped = errors.New("port is not mapped") 31 // ErrSCTPAddrNoIP refers to a SCTP address without IP address. 32 ErrSCTPAddrNoIP = errors.New("sctp address does not contain any IP address") 33 ) 34 35 // New returns a new instance of PortMapper 36 func New() *PortMapper { 37 return NewWithPortAllocator(portallocator.Get(), "") 38 } 39 40 // NewWithPortAllocator returns a new instance of PortMapper which will use the specified PortAllocator 41 func NewWithPortAllocator(allocator *portallocator.PortAllocator, proxyPath string) *PortMapper { 42 return &PortMapper{ 43 currentMappings: make(map[string]*mapping), 44 allocator: allocator, 45 proxyPath: proxyPath, 46 } 47 } 48 49 // Map maps the specified container transport address to the host's network address and transport port 50 func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, _ error) { 51 return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy) 52 } 53 54 // MapRange maps the specified container transport address to the host's network address and transport port range 55 func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, hostPortEnd int, useProxy bool) (host net.Addr, retErr error) { 56 pm.lock.Lock() 57 defer pm.lock.Unlock() 58 59 var ( 60 m *mapping 61 proto string 62 allocatedHostPort int 63 ) 64 65 switch t := container.(type) { 66 case *net.TCPAddr: 67 proto = "tcp" 68 69 var err error 70 allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd) 71 if err != nil { 72 return nil, err 73 } 74 defer func() { 75 if retErr != nil { 76 pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort) 77 } 78 }() 79 80 m = &mapping{ 81 proto: proto, 82 host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort}, 83 container: container, 84 } 85 86 if useProxy { 87 m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath) 88 if err != nil { 89 return nil, err 90 } 91 } else { 92 m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort) 93 if err != nil { 94 return nil, err 95 } 96 } 97 case *net.UDPAddr: 98 proto = "udp" 99 100 var err error 101 allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd) 102 if err != nil { 103 return nil, err 104 } 105 defer func() { 106 if retErr != nil { 107 pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort) 108 } 109 }() 110 111 m = &mapping{ 112 proto: proto, 113 host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort}, 114 container: container, 115 } 116 117 if useProxy { 118 m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath) 119 if err != nil { 120 return nil, err 121 } 122 } else { 123 m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort) 124 if err != nil { 125 return nil, err 126 } 127 } 128 case *sctp.SCTPAddr: 129 proto = "sctp" 130 131 var err error 132 allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd) 133 if err != nil { 134 return nil, err 135 } 136 defer func() { 137 if retErr != nil { 138 pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort) 139 } 140 }() 141 142 m = &mapping{ 143 proto: proto, 144 host: &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: allocatedHostPort}, 145 container: container, 146 } 147 148 if useProxy { 149 sctpAddr := container.(*sctp.SCTPAddr) 150 if len(sctpAddr.IPAddrs) == 0 { 151 return nil, ErrSCTPAddrNoIP 152 } 153 m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, sctpAddr.IPAddrs[0].IP, sctpAddr.Port, pm.proxyPath) 154 if err != nil { 155 return nil, err 156 } 157 } else { 158 m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort) 159 if err != nil { 160 return nil, err 161 } 162 } 163 default: 164 return nil, ErrUnknownBackendAddressType 165 } 166 167 key := getKey(m.host) 168 if _, exists := pm.currentMappings[key]; exists { 169 return nil, ErrPortMappedForIP 170 } 171 172 containerIP, containerPort := getIPAndPort(m.container) 173 if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { 174 return nil, err 175 } 176 177 if err := m.userlandProxy.Start(); err != nil { 178 // FIXME(thaJeztah): both stopping the proxy and deleting iptables rules can produce an error, and both are not currently handled. 179 m.userlandProxy.Stop() 180 // need to undo the iptables rules before we return 181 pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) 182 return nil, err 183 } 184 185 pm.currentMappings[key] = m 186 return m.host, nil 187 } 188 189 // Unmap removes stored mapping for the specified host transport address 190 func (pm *PortMapper) Unmap(host net.Addr) error { 191 pm.lock.Lock() 192 defer pm.lock.Unlock() 193 194 key := getKey(host) 195 data, exists := pm.currentMappings[key] 196 if !exists { 197 return ErrPortNotMapped 198 } 199 200 if data.userlandProxy != nil { 201 data.userlandProxy.Stop() 202 } 203 204 delete(pm.currentMappings, key) 205 206 containerIP, containerPort := getIPAndPort(data.container) 207 hostIP, hostPort := getIPAndPort(data.host) 208 if err := pm.DeleteForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { 209 log.G(context.TODO()).Errorf("Error on iptables delete: %s", err) 210 } 211 212 switch a := host.(type) { 213 case *net.TCPAddr: 214 pm.allocator.ReleasePort(a.IP, "tcp", a.Port) 215 case *net.UDPAddr: 216 pm.allocator.ReleasePort(a.IP, "udp", a.Port) 217 case *sctp.SCTPAddr: 218 if len(a.IPAddrs) == 0 { 219 return ErrSCTPAddrNoIP 220 } 221 pm.allocator.ReleasePort(a.IPAddrs[0].IP, "sctp", a.Port) 222 default: 223 return ErrUnknownBackendAddressType 224 } 225 226 return nil 227 } 228 229 // ReMapAll re-applies all port mappings 230 func (pm *PortMapper) ReMapAll() { 231 pm.lock.Lock() 232 defer pm.lock.Unlock() 233 log.G(context.TODO()).Debugln("Re-applying all port mappings.") 234 for _, data := range pm.currentMappings { 235 containerIP, containerPort := getIPAndPort(data.container) 236 hostIP, hostPort := getIPAndPort(data.host) 237 if err := pm.AppendForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { 238 log.G(context.TODO()).Errorf("Error on iptables add: %s", err) 239 } 240 } 241 } 242 243 func getKey(a net.Addr) string { 244 switch t := a.(type) { 245 case *net.TCPAddr: 246 return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp") 247 case *net.UDPAddr: 248 return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp") 249 case *sctp.SCTPAddr: 250 if len(t.IPAddrs) == 0 { 251 log.G(context.TODO()).Error(ErrSCTPAddrNoIP) 252 return "" 253 } 254 return fmt.Sprintf("%s:%d/%s", t.IPAddrs[0].IP.String(), t.Port, "sctp") 255 } 256 return "" 257 } 258 259 func getIPAndPort(a net.Addr) (net.IP, int) { 260 switch t := a.(type) { 261 case *net.TCPAddr: 262 return t.IP, t.Port 263 case *net.UDPAddr: 264 return t.IP, t.Port 265 case *sctp.SCTPAddr: 266 if len(t.IPAddrs) == 0 { 267 log.G(context.TODO()).Error(ErrSCTPAddrNoIP) 268 return nil, 0 269 } 270 return t.IPAddrs[0].IP, t.Port 271 } 272 return nil, 0 273 }