dotcloud/docker

View on GitHub
libnetwork/drivers/windows/port_mapping.go

Summary

Maintainability
A
0 mins
Test Coverage
//go:build windows

package windows

import (
    "bytes"
    "context"
    "errors"
    "fmt"
    "net"

    "github.com/containerd/log"
    "github.com/docker/docker/libnetwork/portmapper"
    "github.com/docker/docker/libnetwork/types"
    "github.com/ishidawataru/sctp"
)

const (
    maxAllocatePortAttempts = 10
)

// ErrUnsupportedAddressType is returned when the specified address type is not supported.
type ErrUnsupportedAddressType string

func (uat ErrUnsupportedAddressType) Error() string {
    return fmt.Sprintf("unsupported address type: %s", string(uat))
}

// AllocatePorts allocates ports specified in bindings from the portMapper
func AllocatePorts(portMapper *portmapper.PortMapper, bindings []types.PortBinding, containerIP net.IP) ([]types.PortBinding, error) {
    bs := make([]types.PortBinding, 0, len(bindings))
    for _, c := range bindings {
        b := c.GetCopy()
        if err := allocatePort(portMapper, &b, containerIP); err != nil {
            // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
            if cuErr := ReleasePorts(portMapper, 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
        }
        bs = append(bs, b)
    }
    return bs, nil
}

func allocatePort(portMapper *portmapper.PortMapper, bnd *types.PortBinding, containerIP net.IP) error {
    var (
        host net.Addr
        err  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 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.
    for i := 0; i < maxAllocatePortAttempts; i++ {
        if host, err = portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd)); err == nil {
            break
        }
        // There is no point in immediately retrying to map an explicitly chosen port.
        if bnd.HostPort != 0 {
            log.G(context.TODO()).Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err)
            break
        }
        log.G(context.TODO()).Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
    }
    if err != nil {
        return 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 ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
    }
    // Windows does not support host port ranges.
    bnd.HostPortEnd = bnd.HostPort
    return nil
}

// ReleasePorts releases ports specified in bindings from the portMapper
func ReleasePorts(portMapper *portmapper.PortMapper, bindings []types.PortBinding) error {
    var errorBuf bytes.Buffer

    // 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 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)
}