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