Files
Albin Kerouanton fc045ad139 libnet/pmapi: remove firewaller arg from Map/UnmapPorts
Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-08-28 11:47:29 +02:00

147 lines
5.7 KiB
Go

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
}