Files
moby/libnetwork/portmapper/mapper.go
Rob Murray 4f09af6267 Allocate same port for IPv4/IPv6 for 'any interface' mappings.
The bridge driver now does its own port-mapping, rather than using the
portmapper module (which ran as two completely separate instances, for
IPv4 and IPv6).

When asked for a mapping from any host address (0.0.0.0/0) with a range
of host ports, the same port will be allocated for IPv4 and IPv6, or the
mapping will fail with an error if that's not possible.

The bridge driver now manages its own port mappings. So, remove
linux-specific PortMapper code and make what's left Windows-only.

Also, replace the portmapper.userlandProxy interface with StartProxy().

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-06-11 16:50:15 +00:00

216 lines
5.7 KiB
Go

//go:build windows
package portmapper
import (
"context"
"errors"
"fmt"
"net"
"github.com/containerd/log"
"github.com/docker/docker/libnetwork/portallocator"
"github.com/ishidawataru/sctp"
)
type mapping struct {
proto string
stopUserlandProxy func() error
host net.Addr
container net.Addr
}
var (
// ErrUnknownBackendAddressType refers to an unknown container or unsupported address type
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
// ErrPortMappedForIP refers to a port already mapped to an ip address
ErrPortMappedForIP = errors.New("port is already mapped to ip")
// ErrPortNotMapped refers to an unmapped port
ErrPortNotMapped = errors.New("port is not mapped")
// ErrSCTPAddrNoIP refers to a SCTP address without IP address.
ErrSCTPAddrNoIP = errors.New("sctp address does not contain any IP address")
)
// New returns a new instance of PortMapper
func New() *PortMapper {
return NewWithPortAllocator(portallocator.Get(), "")
}
// NewWithPortAllocator returns a new instance of PortMapper which will use the specified PortAllocator
func NewWithPortAllocator(allocator *portallocator.PortAllocator, proxyPath string) *PortMapper {
return &PortMapper{
currentMappings: make(map[string]*mapping),
allocator: allocator,
proxyPath: proxyPath,
}
}
// MapRange maps the specified container transport address to the host's network address and transport port range
func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, hostPortEnd int) (host net.Addr, retErr error) {
pm.lock.Lock()
defer pm.lock.Unlock()
var (
m *mapping
proto string
allocatedHostPort int
)
switch container.(type) {
case *net.TCPAddr:
proto = "tcp"
var err error
allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd)
if err != nil {
return nil, err
}
defer func() {
if retErr != nil {
pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort)
}
}()
m = &mapping{
proto: proto,
host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
container: container,
}
case *net.UDPAddr:
proto = "udp"
var err error
allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd)
if err != nil {
return nil, err
}
defer func() {
if retErr != nil {
pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort)
}
}()
m = &mapping{
proto: proto,
host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
container: container,
}
case *sctp.SCTPAddr:
proto = "sctp"
var err error
allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd)
if err != nil {
return nil, err
}
defer func() {
if retErr != nil {
pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort)
}
}()
m = &mapping{
proto: proto,
host: &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: allocatedHostPort},
container: container,
}
default:
return nil, ErrUnknownBackendAddressType
}
key := getKey(m.host)
if _, exists := pm.currentMappings[key]; exists {
return nil, ErrPortMappedForIP
}
containerIP, containerPort := getIPAndPort(m.container)
if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
return nil, err
}
var err error
m.stopUserlandProxy, err = newDummyProxy(m.proto, hostIP, allocatedHostPort)
if err != nil {
// FIXME(thaJeztah): both stopping the proxy and deleting iptables rules can produce an error, and both are not currently handled.
m.stopUserlandProxy()
// need to undo the iptables rules before we return
pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
return nil, err
}
pm.currentMappings[key] = m
return m.host, nil
}
// Unmap removes stored mapping for the specified host transport address
func (pm *PortMapper) Unmap(host net.Addr) error {
pm.lock.Lock()
defer pm.lock.Unlock()
key := getKey(host)
data, exists := pm.currentMappings[key]
if !exists {
return ErrPortNotMapped
}
if data.stopUserlandProxy != nil {
data.stopUserlandProxy()
}
delete(pm.currentMappings, key)
containerIP, containerPort := getIPAndPort(data.container)
hostIP, hostPort := getIPAndPort(data.host)
if err := pm.DeleteForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
log.G(context.TODO()).Errorf("Error on iptables delete: %s", err)
}
switch a := host.(type) {
case *net.TCPAddr:
pm.allocator.ReleasePort(a.IP, "tcp", a.Port)
case *net.UDPAddr:
pm.allocator.ReleasePort(a.IP, "udp", a.Port)
case *sctp.SCTPAddr:
if len(a.IPAddrs) == 0 {
return ErrSCTPAddrNoIP
}
pm.allocator.ReleasePort(a.IPAddrs[0].IP, "sctp", a.Port)
default:
return ErrUnknownBackendAddressType
}
return nil
}
func getKey(a net.Addr) string {
switch t := a.(type) {
case *net.TCPAddr:
return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
case *net.UDPAddr:
return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
case *sctp.SCTPAddr:
if len(t.IPAddrs) == 0 {
log.G(context.TODO()).Error(ErrSCTPAddrNoIP)
return ""
}
return fmt.Sprintf("%s:%d/%s", t.IPAddrs[0].IP.String(), t.Port, "sctp")
}
return ""
}
func getIPAndPort(a net.Addr) (net.IP, int) {
switch t := a.(type) {
case *net.TCPAddr:
return t.IP, t.Port
case *net.UDPAddr:
return t.IP, t.Port
case *sctp.SCTPAddr:
if len(t.IPAddrs) == 0 {
log.G(context.TODO()).Error(ErrSCTPAddrNoIP)
return nil, 0
}
return t.IPAddrs[0].IP, t.Port
}
return nil, 0
}