mirror of
https://github.com/moby/moby.git
synced 2026-08-03 06:30:59 +00:00
Merge pull request #50289 from akerouanton/cleanup-windows-portmapper
libnet/portmapper: clean up windows port mapper
This commit is contained in:
@@ -159,14 +159,14 @@ func (d *driver) CreateEndpoint(ctx context.Context, nid, eid string, ifInfo dri
|
||||
}
|
||||
|
||||
ep.portMapping = epConnectivity.PortBindings
|
||||
ep.portMapping, err = windows.AllocatePorts(n.portMapper, ep.portMapping, ep.addr.IP)
|
||||
ep.portMapping, err = windows.AllocatePorts(n.pa, ep.portMapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
windows.ReleasePorts(n.portMapper, ep.portMapping)
|
||||
windows.ReleasePorts(n.pa, ep.portMapping)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -227,7 +227,7 @@ func (d *driver) DeleteEndpoint(nid, eid string) error {
|
||||
return fmt.Errorf("endpoint id %q not found", eid)
|
||||
}
|
||||
|
||||
windows.ReleasePorts(n.portMapper, ep.portMapping)
|
||||
windows.ReleasePorts(n.pa, ep.portMapping)
|
||||
|
||||
n.deleteEndpoint(eid)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/drivers/overlay"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portmapper"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ type network struct {
|
||||
initErr error
|
||||
subnets []*subnet
|
||||
secure bool
|
||||
portMapper *portmapper.PortMapper
|
||||
pa *portallocator.OSAllocator
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
@@ -86,11 +86,11 @@ func (d *driver) CreateNetwork(ctx context.Context, id string, option map[string
|
||||
}
|
||||
|
||||
n := &network{
|
||||
id: id,
|
||||
driver: d,
|
||||
endpoints: endpointTable{},
|
||||
subnets: []*subnet{},
|
||||
portMapper: portmapper.New(),
|
||||
id: id,
|
||||
driver: d,
|
||||
endpoints: endpointTable{},
|
||||
subnets: []*subnet{},
|
||||
pa: portallocator.New(),
|
||||
}
|
||||
|
||||
genData, ok := option[netlabel.GenericData].(map[string]string)
|
||||
|
||||
@@ -3,28 +3,26 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/ishidawataru/sctp"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portmapper"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
||||
)
|
||||
|
||||
const maxAllocatePortAttempts = 10
|
||||
|
||||
// AllocatePorts allocates ports specified in bindings from the portMapper
|
||||
func AllocatePorts(portMapper *portmapper.PortMapper, bindings []types.PortBinding, containerIP net.IP) ([]types.PortBinding, error) {
|
||||
// AllocatePorts allocates ports specified in bindings from the port allocator.
|
||||
func AllocatePorts(pa *portallocator.OSAllocator, bindings []types.PortBinding) ([]types.PortBinding, error) {
|
||||
bs := make([]types.PortBinding, 0, len(bindings))
|
||||
for _, c := range bindings {
|
||||
b := c.Copy()
|
||||
if err := allocatePort(portMapper, &b, containerIP); err != nil {
|
||||
b, err := allocatePort(pa, c)
|
||||
if err != nil {
|
||||
// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
|
||||
if cuErr := ReleasePorts(portMapper, bs); cuErr != nil {
|
||||
if cuErr := ReleasePorts(pa, bs); cuErr != nil {
|
||||
log.G(context.TODO()).Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr)
|
||||
}
|
||||
return nil, err
|
||||
@@ -34,35 +32,24 @@ func AllocatePorts(portMapper *portmapper.PortMapper, bindings []types.PortBindi
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func allocatePort(portMapper *portmapper.PortMapper, bnd *types.PortBinding, containerIP net.IP) error {
|
||||
var (
|
||||
host net.Addr
|
||||
err error
|
||||
)
|
||||
|
||||
func allocatePort(pa *portallocator.OSAllocator, bnd types.PortBinding) (types.PortBinding, error) {
|
||||
// Windows does not support a host ip for port bindings (this is validated in ConvertPortBindings()).
|
||||
// If the HostIP is nil, force it to be 0.0.0.0 for use as the key in portMapper.
|
||||
// If the HostIP is nil, force it to be 0.0.0.0 for use as the key in the port allocator.
|
||||
if bnd.HostIP == nil {
|
||||
bnd.HostIP = net.IPv4zero
|
||||
}
|
||||
|
||||
// Store the container interface address in the operational binding
|
||||
bnd.IP = containerIP
|
||||
|
||||
// Adjust HostPortEnd if this is not a range.
|
||||
if bnd.HostPortEnd == 0 {
|
||||
bnd.HostPortEnd = bnd.HostPort
|
||||
}
|
||||
|
||||
// Construct the container side transport address
|
||||
container, err := bnd.ContainerAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
|
||||
var allocatedPort int
|
||||
var err error
|
||||
for i := 0; i < maxAllocatePortAttempts; i++ {
|
||||
if host, err = portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd)); err == nil {
|
||||
allocatedPort, err = pa.AllocateHostPort(bnd.HostIP, bnd.Proto, int(bnd.HostPort), int(bnd.HostPortEnd))
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
// There is no point in immediately retrying to map an explicitly chosen port.
|
||||
@@ -73,51 +60,25 @@ func allocatePort(portMapper *portmapper.PortMapper, bnd *types.PortBinding, con
|
||||
log.G(context.TODO()).Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return types.PortBinding{}, err
|
||||
}
|
||||
|
||||
// Save the host port (regardless it was or not specified in the binding)
|
||||
switch netAddr := host.(type) {
|
||||
case *net.TCPAddr:
|
||||
bnd.HostPort = uint16(host.(*net.TCPAddr).Port)
|
||||
break
|
||||
case *net.UDPAddr:
|
||||
bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
|
||||
break
|
||||
case *sctp.SCTPAddr:
|
||||
bnd.HostPort = uint16(host.(*sctp.SCTPAddr).Port)
|
||||
break
|
||||
default:
|
||||
// For completeness
|
||||
return fmt.Errorf("unsupported address type: %T", netAddr)
|
||||
}
|
||||
// Windows does not support host port ranges.
|
||||
bnd.HostPortEnd = bnd.HostPort
|
||||
return nil
|
||||
bnd.HostPort = uint16(allocatedPort)
|
||||
bnd.HostPortEnd = uint16(allocatedPort)
|
||||
|
||||
return bnd, nil
|
||||
}
|
||||
|
||||
// ReleasePorts releases ports specified in bindings from the portMapper
|
||||
func ReleasePorts(portMapper *portmapper.PortMapper, bindings []types.PortBinding) error {
|
||||
var errorBuf bytes.Buffer
|
||||
// ReleasePorts releases ports specified in bindings from the portAlloc
|
||||
func ReleasePorts(pa *portallocator.OSAllocator, bindings []types.PortBinding) error {
|
||||
var errs []error
|
||||
|
||||
// Attempt to release all port bindings, do not stop on failure
|
||||
for _, m := range bindings {
|
||||
if err := releasePort(portMapper, m); err != nil {
|
||||
errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err))
|
||||
if err := pa.Deallocate(m.HostIP, m.Proto, int(m.HostPort)); err != nil {
|
||||
errs = append(errs, fmt.Errorf("could not release %v because of %v", m, err))
|
||||
}
|
||||
}
|
||||
|
||||
if errorBuf.Len() != 0 {
|
||||
return errors.New(errorBuf.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func releasePort(portMapper *portmapper.PortMapper, bnd types.PortBinding) error {
|
||||
// Construct the host side transport address
|
||||
host, err := bnd.HostAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return portMapper.Unmap(host)
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/datastore"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portmapper"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/scope"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
||||
"go.opentelemetry.io/otel"
|
||||
@@ -94,12 +94,12 @@ type hnsEndpoint struct {
|
||||
}
|
||||
|
||||
type hnsNetwork struct {
|
||||
id string
|
||||
created bool
|
||||
config *networkConfiguration
|
||||
endpoints map[string]*hnsEndpoint // key: endpoint id
|
||||
driver *driver // The network's driver
|
||||
portMapper *portmapper.PortMapper
|
||||
id string
|
||||
created bool
|
||||
config *networkConfiguration
|
||||
endpoints map[string]*hnsEndpoint // key: endpoint id
|
||||
driver *driver // The network's driver
|
||||
pa *portallocator.OSAllocator
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
@@ -308,11 +308,11 @@ func (ncfg *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data
|
||||
|
||||
func (d *driver) createNetwork(config *networkConfiguration) *hnsNetwork {
|
||||
network := &hnsNetwork{
|
||||
id: config.ID,
|
||||
endpoints: make(map[string]*hnsEndpoint),
|
||||
config: config,
|
||||
driver: d,
|
||||
portMapper: portmapper.New(),
|
||||
id: config.ID,
|
||||
endpoints: make(map[string]*hnsEndpoint),
|
||||
config: config,
|
||||
driver: d,
|
||||
pa: portallocator.New(),
|
||||
}
|
||||
|
||||
d.Lock()
|
||||
@@ -701,19 +701,14 @@ func (d *driver) CreateEndpoint(ctx context.Context, nid, eid string, ifInfo dri
|
||||
portMapping := epConnectivity.PortBindings
|
||||
|
||||
if n.config.Type == "l2bridge" || n.config.Type == "l2tunnel" {
|
||||
ip := net.IPv4(0, 0, 0, 0)
|
||||
if ifInfo.Address() != nil {
|
||||
ip = ifInfo.Address().IP
|
||||
}
|
||||
|
||||
portMapping, err = AllocatePorts(n.portMapper, portMapping, ip)
|
||||
portMapping, err = AllocatePorts(n.pa, portMapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ReleasePorts(n.portMapper, portMapping)
|
||||
ReleasePorts(n.pa, portMapping)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -828,7 +823,7 @@ func (d *driver) DeleteEndpoint(nid, eid string) error {
|
||||
}
|
||||
|
||||
if n.config.Type == "l2bridge" || n.config.Type == "l2tunnel" {
|
||||
ReleasePorts(n.portMapper, ep.portMapping)
|
||||
ReleasePorts(n.pa, ep.portMapping)
|
||||
}
|
||||
|
||||
n.Lock()
|
||||
|
||||
153
daemon/libnetwork/portallocator/osallocator_windows.go
Normal file
153
daemon/libnetwork/portallocator/osallocator_windows.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package portallocator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/ishidawataru/sctp"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// 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")
|
||||
)
|
||||
|
||||
// OSAllocator allocates ports from the OS by creating listening sockets.
|
||||
type OSAllocator struct {
|
||||
// osListeners stores listening sockets used by active port mappings
|
||||
// to reserve ports from the OS. Outer map is keyed by protocol, and inner
|
||||
// map is keyed by host address and port.
|
||||
osListeners map[types.Protocol]map[netip.AddrPort]io.Closer
|
||||
lock sync.Mutex
|
||||
|
||||
allocator *PortAllocator
|
||||
}
|
||||
|
||||
// New returns a new instance of OSAllocator
|
||||
func New() *OSAllocator {
|
||||
return &OSAllocator{
|
||||
osListeners: make(map[types.Protocol]map[netip.AddrPort]io.Closer),
|
||||
allocator: Get(),
|
||||
}
|
||||
}
|
||||
|
||||
// AllocateHostPort allocates a port from the OS (by creating a listening socket).
|
||||
func (pa *OSAllocator) AllocateHostPort(hostIP net.IP, proto types.Protocol, hostPortStart, hostPortEnd int) (_ int, retErr error) {
|
||||
pa.lock.Lock()
|
||||
defer pa.lock.Unlock()
|
||||
|
||||
allocatedHostPort, err := pa.allocator.RequestPortInRange(hostIP, proto.String(), hostPortStart, hostPortEnd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
pa.allocator.ReleasePort(hostIP, proto.String(), allocatedHostPort)
|
||||
}
|
||||
}()
|
||||
|
||||
if pa.osListeners[proto] == nil {
|
||||
pa.osListeners[proto] = make(map[netip.AddrPort]io.Closer)
|
||||
}
|
||||
|
||||
addr, ok := netip.AddrFromSlice(hostIP)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("invalid HostIP: %s", hostIP)
|
||||
}
|
||||
|
||||
hAddrPort := netip.AddrPortFrom(addr, uint16(allocatedHostPort))
|
||||
if _, exists := pa.osListeners[proto][hAddrPort]; exists {
|
||||
return 0, ErrPortMappedForIP
|
||||
}
|
||||
|
||||
var osListener io.Closer
|
||||
osListener, err = allocateHostPort(proto.String(), hostIP, allocatedHostPort)
|
||||
if err != nil {
|
||||
if osListener != nil {
|
||||
if err := osListener.Close(); err != nil {
|
||||
// Prior to v29.0, this error was never checked. So, instead of
|
||||
// returning an error, log it and proceed.
|
||||
log.G(context.TODO()).Infof("failed to stop dummy proxy for %s/%s: %v", hostIP, proto, err)
|
||||
}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
pa.osListeners[proto][hAddrPort] = osListener
|
||||
return allocatedHostPort, nil
|
||||
}
|
||||
|
||||
// allocateHostPort allocates a host port by binding to the specified host IP and port.
|
||||
func allocateHostPort(proto string, hostIP net.IP, hostPort int) (io.Closer, error) {
|
||||
// detect version of hostIP to bind only to correct version
|
||||
protoVer := proto + "4"
|
||||
if hostIP.To4() == nil {
|
||||
protoVer = proto + "6"
|
||||
}
|
||||
|
||||
switch proto {
|
||||
case "tcp":
|
||||
l, err := net.ListenTCP(protoVer, &net.TCPAddr{IP: hostIP, Port: hostPort})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
case "udp":
|
||||
l, err := net.ListenUDP(protoVer, &net.UDPAddr{IP: hostIP, Port: hostPort})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
case "sctp":
|
||||
l, err := sctp.ListenSCTP(protoVer, &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: hostPort})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("protocol %s not supported", proto)
|
||||
}
|
||||
}
|
||||
|
||||
// Deallocate removes stored mapping for the specified host transport address
|
||||
func (pa *OSAllocator) Deallocate(hostIP net.IP, proto types.Protocol, hostPort int) error {
|
||||
pa.lock.Lock()
|
||||
defer pa.lock.Unlock()
|
||||
|
||||
addr, ok := netip.AddrFromSlice(hostIP)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid HostIP: %s", hostIP)
|
||||
}
|
||||
|
||||
if pa.osListeners[proto] == nil {
|
||||
return ErrPortNotMapped
|
||||
}
|
||||
|
||||
hAddrPort := netip.AddrPortFrom(addr, uint16(hostPort))
|
||||
osListener, exists := pa.osListeners[proto][hAddrPort]
|
||||
if !exists {
|
||||
return ErrPortNotMapped
|
||||
}
|
||||
|
||||
if osListener != nil {
|
||||
if err := osListener.Close(); err != nil {
|
||||
// Prior to v29.0, this error was never checked. So, instead of
|
||||
// returning an error, log it and proceed.
|
||||
log.G(context.TODO()).Infof("failed to stop dummy proxy for %s/%s: %v", hostIP, proto, err)
|
||||
}
|
||||
}
|
||||
|
||||
delete(pa.osListeners[proto], hAddrPort)
|
||||
|
||||
pa.allocator.ReleasePort(hostIP, proto.String(), int(hostPort))
|
||||
return nil
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/ishidawataru/sctp"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
|
||||
)
|
||||
|
||||
// PortMapper manages the network address translation
|
||||
type PortMapper struct {
|
||||
bridgeName string
|
||||
|
||||
// udp:ip:port
|
||||
currentMappings map[string]*mapping
|
||||
lock sync.Mutex
|
||||
|
||||
proxyPath string
|
||||
|
||||
allocator *portallocator.PortAllocator
|
||||
}
|
||||
|
||||
// AppendForwardingTableEntry adds a port mapping to the forwarding table
|
||||
func (pm *PortMapper) AppendForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteForwardingTableEntry removes a port mapping from the forwarding table
|
||||
func (pm *PortMapper) DeleteForwardingTableEntry(proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/ishidawataru/sctp"
|
||||
)
|
||||
|
||||
// ipVersion refers to IP version - v4 or v6
|
||||
type ipVersion string
|
||||
|
||||
const (
|
||||
// IPv4 is version 4
|
||||
ipv4 ipVersion = "4"
|
||||
// IPv4 is version 6
|
||||
ipv6 ipVersion = "6"
|
||||
)
|
||||
|
||||
// dummyProxy just listen on some port, it is needed to prevent accidental
|
||||
// port allocations on bound port, because without userland proxy we using
|
||||
// iptables rules and not net.Listen
|
||||
type dummyProxy struct {
|
||||
listener io.Closer
|
||||
addr net.Addr
|
||||
ipVersion ipVersion
|
||||
}
|
||||
|
||||
func newDummyProxy(proto string, hostIP net.IP, hostPort int) (stop func() error, retErr error) {
|
||||
// detect version of hostIP to bind only to correct version
|
||||
version := ipv4
|
||||
if hostIP.To4() == nil {
|
||||
version = ipv6
|
||||
}
|
||||
var addr net.Addr
|
||||
switch proto {
|
||||
case "tcp":
|
||||
addr = &net.TCPAddr{IP: hostIP, Port: hostPort}
|
||||
case "udp":
|
||||
addr = &net.UDPAddr{IP: hostIP, Port: hostPort}
|
||||
case "sctp":
|
||||
addr = &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: hostPort}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown addr type: %s", proto)
|
||||
}
|
||||
p := &dummyProxy{addr: addr, ipVersion: version}
|
||||
if err := p.start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.stop, nil
|
||||
}
|
||||
|
||||
func (p *dummyProxy) start() error {
|
||||
switch addr := p.addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
l, err := net.ListenTCP("tcp"+string(p.ipVersion), addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.listener = l
|
||||
case *net.UDPAddr:
|
||||
l, err := net.ListenUDP("udp"+string(p.ipVersion), addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.listener = l
|
||||
case *sctp.SCTPAddr:
|
||||
l, err := sctp.ListenSCTP("sctp"+string(p.ipVersion), addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.listener = l
|
||||
default:
|
||||
return fmt.Errorf("Unknown addr type: %T", p.addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *dummyProxy) stop() error {
|
||||
if p.listener != nil {
|
||||
return p.listener.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -72,20 +72,6 @@ func (p PortBinding) HostAddr() (net.Addr, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerAddr returns the container side transport address
|
||||
func (p PortBinding) ContainerAddr() (net.Addr, error) {
|
||||
switch p.Proto {
|
||||
case UDP:
|
||||
return &net.UDPAddr{IP: p.IP, Port: int(p.Port)}, nil
|
||||
case TCP:
|
||||
return &net.TCPAddr{IP: p.IP, Port: int(p.Port)}, nil
|
||||
case SCTP:
|
||||
return &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: p.IP}}, Port: int(p.Port)}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid transport protocol: %s", p.Proto.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the PortBinding.
|
||||
func (p PortBinding) Copy() PortBinding {
|
||||
return PortBinding{
|
||||
|
||||
Reference in New Issue
Block a user