package portmapperapi import ( "context" "net" "net/netip" "os" "strings" "github.com/moby/moby/v2/daemon/libnetwork/types" ) // Registerer provides a callback interface for registering port-mappers. type Registerer interface { // Register provides a way for port-mappers to dynamically register with libnetwork. Register(name string, driver PortMapper) error } // PortMapper maps / unmaps container ports to host ports. type PortMapper interface { // MapPorts takes a list of port binding requests, and returns a list of // PortBinding. Both lists MUST have the same size. // // Multiple port bindings are passed when they're all requesting the // same port range, or an ephemeral port, over multiple IP addresses and // all pointing to the same container port. In that case, the PortMapper // MUST assign the same HostPort for all IP addresses. // // When an ephemeral port, or a single port from a range is requested // MapPorts should attempt a few times to find a free port available // across all IP addresses. MapPorts(ctx context.Context, reqs []PortBindingReq) ([]PortBinding, error) // UnmapPorts takes a list of port bindings to unmap. UnmapPorts(ctx context.Context, pbs []PortBinding) error } type PortBindingReq struct { types.PortBinding // Mapper is the name of the port mapper used to process this PortBindingReq. Mapper string // ChildHostIP is a temporary field used to pass the host IP address as // seen from the daemon. (It'll be removed once the portmapper API is // implemented). ChildHostIP net.IP `json:"-"` } // Compare defines an ordering over PortBindingReq such that bindings that // differ only in host IP are adjacent (those bindings should be allocated the // same port). // // Port bindings are first sorted by their mapper, then: // - exact host ports are placed before ranges (in case exact ports fall within // ranges, giving a better chance of allocating the exact ports), then // - same container port are adjacent (lowest ports first), then // - same protocols are adjacent (tcp < udp < sctp), then // - same host ports or ranges are adjacent, then // - ordered by container IP (then host IP, if set). func (pbReq PortBindingReq) Compare(other PortBindingReq) int { if pbReq.Mapper != other.Mapper { return strings.Compare(pbReq.Mapper, other.Mapper) } // Exact host port < host port range. aIsRange := pbReq.HostPort == 0 || pbReq.HostPort != pbReq.HostPortEnd bIsRange := other.HostPort == 0 || other.HostPort != other.HostPortEnd if aIsRange != bIsRange { if aIsRange { return 1 } return -1 } if pbReq.Port != other.Port { return int(pbReq.Port) - int(other.Port) } if pbReq.Proto != other.Proto { return int(pbReq.Proto) - int(other.Proto) } if pbReq.HostPort != other.HostPort { return int(pbReq.HostPort) - int(other.HostPort) } if pbReq.HostPortEnd != other.HostPortEnd { return int(pbReq.HostPortEnd) - int(other.HostPortEnd) } aHostIP, _ := netip.AddrFromSlice(pbReq.HostIP) bHostIP, _ := netip.AddrFromSlice(other.HostIP) if c := aHostIP.Unmap().Compare(bHostIP.Unmap()); c != 0 { return c } aIP, _ := netip.AddrFromSlice(pbReq.IP) bIP, _ := netip.AddrFromSlice(other.IP) return aIP.Unmap().Compare(bIP.Unmap()) } type PortBinding struct { // PortBinding contains the port binding information reported through the // Engine API. types.PortBinding // Mapper is the name of the port mapper used to process this PortBinding. Mapper string // NAT represents the host IP and port that should be NATed to the // container IP and port specified in types.PortBinding. When set, callers // of the port mapper should reconfigure the host firewall. When it's not // set, callers won't reconfigure the host firewall. // // If the address is invalid, or a non-unicast address, or the port is 0, // it's treated as an error. If both Forwarding and NAT are specified, NAT // takes precedence. NAT netip.AddrPort // Forwarding indicates whether callers of the port mapper should update // the host firewall to allow traffic forwarding to IP:Port. Forwarding bool // BoundSocket is used to reserve a host port for the binding. If the // userland proxy is in-use, it's passed to the proxy when the proxy is // started, then it's closed and set to nil here. BoundSocket *os.File `json:"-"` // ChildHostIP is the host IP address, as seen from the daemon. This // is normally the same as PortBinding.HostIP but, in rootless mode, it // will be an address in the rootless network namespace. RootlessKit // binds the port on the real (parent) host address and maps it to the // same port number on the address dockerd sees in the child namespace. // So, for example, docker-proxy and DNAT rules need to use the child // namespace's host address. (PortBinding.HostIP isn't replaced by the // child address, because it's stored as user-config and the child // address may change if RootlessKit is configured differently.) ChildHostIP net.IP `json:"-"` // PortDriverRemove is a function that will inform the RootlessKit // port driver about removal of a port binding, or nil. PortDriverRemove func() error `json:"-"` // StopProxy is a function to stop the userland proxy for this binding, // if a proxy has been started - else nil. StopProxy func() error `json:"-"` // RootlesskitUnsupported is set to true when the port binding is not // supported by the port driver of RootlessKit. RootlesskitUnsupported bool `json:"-"` } // ChildPortBinding is pb.PortBinding, with the host address the daemon // will see - which, in rootless mode, will be an address in the RootlessKit's // child namespace (see PortBinding.ChildHostIP). func (pb PortBinding) ChildPortBinding() types.PortBinding { res := pb.PortBinding res.HostIP = pb.ChildHostIP return res }