libnetwork/portmapper/mapper.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
}