1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
//go:build windows
// +build windows
package windows
import (
"bytes"
"errors"
"fmt"
"net"
"github.com/docker/docker/libnetwork/portmapper"
"github.com/docker/docker/libnetwork/types"
"github.com/ishidawataru/sctp"
"github.com/sirupsen/logrus"
)
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 {
logrus.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), false); err == nil {
break
}
// There is no point in immediately retrying to map an explicitly chosen port.
if bnd.HostPort != 0 {
logrus.Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err)
break
}
logrus.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)
}
|