github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/libnetwork/drivers/bridge/port_mapping.go (about) 1 //go:build linux 2 // +build linux 3 4 package bridge 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "net" 11 "sync" 12 13 "github.com/docker/docker/libnetwork/types" 14 "github.com/ishidawataru/sctp" 15 "github.com/sirupsen/logrus" 16 ) 17 18 func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) { 19 if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil { 20 return nil, nil 21 } 22 23 defHostIP := net.IPv4zero // 0.0.0.0 24 if reqDefBindIP != nil { 25 defHostIP = reqDefBindIP 26 } 27 28 var containerIPv6 net.IP 29 if ep.addrv6 != nil { 30 containerIPv6 = ep.addrv6.IP 31 } 32 33 pb, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, containerIPv6, defHostIP, ulPxyEnabled) 34 if err != nil { 35 return nil, err 36 } 37 return pb, nil 38 } 39 40 func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIPv4, containerIPv6, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) { 41 bs := make([]types.PortBinding, 0, len(bindings)) 42 for _, c := range bindings { 43 bIPv4 := c.GetCopy() 44 bIPv6 := c.GetCopy() 45 // Allocate IPv4 Port mappings 46 if ok := n.validatePortBindingIPv4(&bIPv4, containerIPv4, defHostIP); ok { 47 if err := n.allocatePort(&bIPv4, ulPxyEnabled); err != nil { 48 // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message 49 if cuErr := n.releasePortsInternal(bs); cuErr != nil { 50 logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv4 port bindings: %v", bIPv4, cuErr) 51 } 52 return nil, err 53 } 54 bs = append(bs, bIPv4) 55 } 56 57 // skip adding implicit v6 addr, when the kernel was booted with `ipv6.disable=1` 58 // https://github.com/moby/moby/issues/42288 59 isV6Binding := c.HostIP != nil && c.HostIP.To4() == nil 60 if !isV6Binding && !IsV6Listenable() { 61 continue 62 } 63 64 // Allocate IPv6 Port mappings 65 // If the container has no IPv6 address, allow proxying host IPv6 traffic to it 66 // by setting up the binding with the IPv4 interface if the userland proxy is enabled 67 // This change was added to keep backward compatibility 68 containerIP := containerIPv6 69 if ulPxyEnabled && (containerIPv6 == nil) { 70 containerIP = containerIPv4 71 } 72 if ok := n.validatePortBindingIPv6(&bIPv6, containerIP, defHostIP); ok { 73 if err := n.allocatePort(&bIPv6, ulPxyEnabled); err != nil { 74 // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message 75 if cuErr := n.releasePortsInternal(bs); cuErr != nil { 76 logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv6 port bindings: %v", bIPv6, cuErr) 77 } 78 return nil, err 79 } 80 bs = append(bs, bIPv6) 81 } 82 } 83 return bs, nil 84 } 85 86 // validatePortBindingIPv4 validates the port binding, populates the missing Host IP field and returns true 87 // if this is a valid IPv4 binding, else returns false 88 func (n *bridgeNetwork) validatePortBindingIPv4(bnd *types.PortBinding, containerIPv4, defHostIP net.IP) bool { 89 //Return early if there is a valid Host IP, but its not a IPv4 address 90 if len(bnd.HostIP) > 0 && bnd.HostIP.To4() == nil { 91 return false 92 } 93 // Adjust the host address in the operational binding 94 if len(bnd.HostIP) == 0 { 95 // Return early if the default binding address is an IPv6 address 96 if defHostIP.To4() == nil { 97 return false 98 } 99 bnd.HostIP = defHostIP 100 } 101 bnd.IP = containerIPv4 102 return true 103 104 } 105 106 // validatePortBindingIPv6 validates the port binding, populates the missing Host IP field and returns true 107 // if this is a valid IPv6 binding, else returns false 108 func (n *bridgeNetwork) validatePortBindingIPv6(bnd *types.PortBinding, containerIP, defHostIP net.IP) bool { 109 // Return early if there is no container endpoint 110 if containerIP == nil { 111 return false 112 } 113 // Return early if there is a valid Host IP, which is a IPv4 address 114 if len(bnd.HostIP) > 0 && bnd.HostIP.To4() != nil { 115 return false 116 } 117 118 // Setup a binding to "::" if Host IP is empty and the default binding IP is 0.0.0.0 119 if len(bnd.HostIP) == 0 { 120 if defHostIP.Equal(net.IPv4zero) { 121 bnd.HostIP = net.IPv6zero 122 // If the default binding IP is an IPv6 address, use it 123 } else if defHostIP.To4() == nil { 124 bnd.HostIP = defHostIP 125 // Return false if default binding ip is an IPv4 address 126 } else { 127 return false 128 } 129 } 130 bnd.IP = containerIP 131 return true 132 } 133 134 func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, ulPxyEnabled bool) error { 135 var ( 136 host net.Addr 137 err error 138 ) 139 140 // Adjust HostPortEnd if this is not a range. 141 if bnd.HostPortEnd == 0 { 142 bnd.HostPortEnd = bnd.HostPort 143 } 144 145 // Construct the container side transport address 146 container, err := bnd.ContainerAddr() 147 if err != nil { 148 return err 149 } 150 151 portmapper := n.portMapper 152 153 if bnd.HostIP.To4() == nil { 154 portmapper = n.portMapperV6 155 } 156 157 // Try up to maxAllocatePortAttempts times to get a port that's not already allocated. 158 for i := 0; i < maxAllocatePortAttempts; i++ { 159 if host, err = portmapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled); err == nil { 160 break 161 } 162 // There is no point in immediately retrying to map an explicitly chosen port. 163 if bnd.HostPort != 0 { 164 logrus.Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err) 165 break 166 } 167 logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1) 168 } 169 if err != nil { 170 return err 171 } 172 173 // Save the host port (regardless it was or not specified in the binding) 174 switch netAddr := host.(type) { 175 case *net.TCPAddr: 176 bnd.HostPort = uint16(host.(*net.TCPAddr).Port) 177 return nil 178 case *net.UDPAddr: 179 bnd.HostPort = uint16(host.(*net.UDPAddr).Port) 180 return nil 181 case *sctp.SCTPAddr: 182 bnd.HostPort = uint16(host.(*sctp.SCTPAddr).Port) 183 return nil 184 default: 185 // For completeness 186 return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr)) 187 } 188 } 189 190 func (n *bridgeNetwork) releasePorts(ep *bridgeEndpoint) error { 191 return n.releasePortsInternal(ep.portMapping) 192 } 193 194 func (n *bridgeNetwork) releasePortsInternal(bindings []types.PortBinding) error { 195 var errorBuf bytes.Buffer 196 197 // Attempt to release all port bindings, do not stop on failure 198 for _, m := range bindings { 199 if err := n.releasePort(m); err != nil { 200 errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err)) 201 } 202 } 203 204 if errorBuf.Len() != 0 { 205 return errors.New(errorBuf.String()) 206 } 207 return nil 208 } 209 210 func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error { 211 // Construct the host side transport address 212 host, err := bnd.HostAddr() 213 if err != nil { 214 return err 215 } 216 217 portmapper := n.portMapper 218 219 if bnd.HostIP.To4() == nil { 220 portmapper = n.portMapperV6 221 } 222 223 return portmapper.Unmap(host) 224 } 225 226 var ( 227 v6ListenableCached bool 228 v6ListenableOnce sync.Once 229 ) 230 231 // IsV6Listenable returns true when `[::1]:0` is listenable. 232 // IsV6Listenable returns false mostly when the kernel was booted with `ipv6.disable=1` option. 233 func IsV6Listenable() bool { 234 v6ListenableOnce.Do(func() { 235 ln, err := net.Listen("tcp6", "[::1]:0") 236 if err != nil { 237 // When the kernel was booted with `ipv6.disable=1`, 238 // we get err "listen tcp6 [::1]:0: socket: address family not supported by protocol" 239 // https://github.com/moby/moby/issues/42288 240 logrus.Debugf("port_mapping: v6Listenable=false (%v)", err) 241 } else { 242 v6ListenableCached = true 243 ln.Close() 244 } 245 }) 246 return v6ListenableCached 247 }