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