Repository: moby/moby
File: daemon/libnetwork/portmapperapi/api.go (Go)

Code before:
		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, or it's invalid (e.g. host port is 0, etc...), callers won't
	// reconfigure the host firewall. It can be combined with Forwarding if
	// both NAT and forwarding are required.
	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,

Review comment (bug):
I worry that treating e.g. `netip.MustParseAddrPort("1.2.3.4:0")` as "no NAT" could mask bugs in the port mapper implementation. I suggest defining only the zero value `netip.AddrPort{}` as the sentinel value for "no NAT." Any value for NAT that has a zero port, invalid address or non-unicast address would be an error.

Code after:
		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.