mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Adding libnetwork support to publish on custom host port ranges.
See https://github.com/docker/docker/pull/12927 for docker portion. Signed-off-by: Don Kjer <don.kjer@gmail.com>
This commit is contained in:
parent
9b74f9d5ab
commit
8d73de9722
6 changed files with 179 additions and 40 deletions
|
@ -57,6 +57,11 @@ func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHos
|
||||||
bnd.HostIP = defHostIP
|
bnd.HostIP = defHostIP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust HostPortEnd if this is not a range.
|
||||||
|
if bnd.HostPortEnd == 0 {
|
||||||
|
bnd.HostPortEnd = bnd.HostPort
|
||||||
|
}
|
||||||
|
|
||||||
// Construct the container side transport address
|
// Construct the container side transport address
|
||||||
container, err := bnd.ContainerAddr()
|
container, err := bnd.ContainerAddr()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -65,12 +70,12 @@ func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHos
|
||||||
|
|
||||||
// Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
|
// Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
|
||||||
for i := 0; i < maxAllocatePortAttempts; i++ {
|
for i := 0; i < maxAllocatePortAttempts; i++ {
|
||||||
if host, err = n.portMapper.Map(container, bnd.HostIP, int(bnd.HostPort), ulPxyEnabled); err == nil {
|
if host, err = n.portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled); err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// There is no point in immediately retrying to map an explicitly chosen port.
|
// There is no point in immediately retrying to map an explicitly chosen port.
|
||||||
if bnd.HostPort != 0 {
|
if bnd.HostPort != 0 {
|
||||||
logrus.Warnf("Failed to allocate and map port %d: %s", bnd.HostPort, err)
|
logrus.Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
|
logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
|
||||||
|
|
|
@ -89,9 +89,11 @@ func getEmptyGenericOption() map[string]interface{} {
|
||||||
|
|
||||||
func getPortMapping() []types.PortBinding {
|
func getPortMapping() []types.PortBinding {
|
||||||
return []types.PortBinding{
|
return []types.PortBinding{
|
||||||
types.PortBinding{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
|
{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
|
||||||
types.PortBinding{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
|
{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
|
||||||
types.PortBinding{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
|
{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
|
||||||
|
{Proto: types.TCP, Port: uint16(320), HostPort: uint16(32000), HostPortEnd: uint16(32999)},
|
||||||
|
{Proto: types.UDP, Port: uint16(420), HostPort: uint16(42000), HostPortEnd: uint16(42001)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +281,7 @@ func TestBridge(t *testing.T) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Unexpected format for port mapping in endpoint operational data")
|
t.Fatalf("Unexpected format for port mapping in endpoint operational data")
|
||||||
}
|
}
|
||||||
if len(pm) != 3 {
|
if len(pm) != 5 {
|
||||||
t.Fatalf("Incomplete data for port mapping in endpoint operational data: %d", len(pm))
|
t.Fatalf("Incomplete data for port mapping in endpoint operational data: %d", len(pm))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,10 +70,15 @@ type (
|
||||||
Begin int
|
Begin int
|
||||||
End int
|
End int
|
||||||
}
|
}
|
||||||
|
portRange struct {
|
||||||
|
begin int
|
||||||
|
end int
|
||||||
|
last int
|
||||||
|
}
|
||||||
portMap struct {
|
portMap struct {
|
||||||
p map[int]struct{}
|
p map[int]struct{}
|
||||||
begin, end int
|
defaultRange string
|
||||||
last int
|
portRanges map[string]*portRange
|
||||||
}
|
}
|
||||||
protoMap map[string]*portMap
|
protoMap map[string]*portMap
|
||||||
)
|
)
|
||||||
|
@ -123,8 +128,17 @@ func getDynamicPortRange() (start int, end int, err error) {
|
||||||
|
|
||||||
// RequestPort requests new port from global ports pool for specified ip and proto.
|
// RequestPort requests new port from global ports pool for specified ip and proto.
|
||||||
// If port is 0 it returns first free port. Otherwise it checks port availability
|
// If port is 0 it returns first free port. Otherwise it checks port availability
|
||||||
// in pool and return that port or error if port is already busy.
|
// in proto's pool and returns that port or error if port is already busy.
|
||||||
func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
|
func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||||
|
return p.RequestPortInRange(ip, proto, port, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestPortInRange requests new port from global ports pool for specified ip and proto.
|
||||||
|
// If portStart and portEnd are 0 it returns the first free port in the default ephemeral range.
|
||||||
|
// If portStart != portEnd it returns the first free port in the requested range.
|
||||||
|
// Otherwise (portStart == portEnd) it checks port availability in the requested proto's port-pool
|
||||||
|
// and returns that port or error if port is already busy.
|
||||||
|
func (p *PortAllocator) RequestPortInRange(ip net.IP, proto string, portStart, portEnd int) (int, error) {
|
||||||
p.mutex.Lock()
|
p.mutex.Lock()
|
||||||
defer p.mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
|
@ -146,15 +160,15 @@ func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, err
|
||||||
p.ipMap[ipstr] = protomap
|
p.ipMap[ipstr] = protomap
|
||||||
}
|
}
|
||||||
mapping := protomap[proto]
|
mapping := protomap[proto]
|
||||||
if port > 0 {
|
if portStart > 0 && portStart == portEnd {
|
||||||
if _, ok := mapping.p[port]; !ok {
|
if _, ok := mapping.p[portStart]; !ok {
|
||||||
mapping.p[port] = struct{}{}
|
mapping.p[portStart] = struct{}{}
|
||||||
return port, nil
|
return portStart, nil
|
||||||
}
|
}
|
||||||
return 0, newErrPortAlreadyAllocated(ipstr, port)
|
return 0, newErrPortAlreadyAllocated(ipstr, portStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err := mapping.findPort()
|
port, err := mapping.findPort(portStart, portEnd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -178,12 +192,15 @@ func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortAllocator) newPortMap() *portMap {
|
func (p *PortAllocator) newPortMap() *portMap {
|
||||||
return &portMap{
|
defaultKey := getRangeKey(p.Begin, p.End)
|
||||||
p: map[int]struct{}{},
|
pm := &portMap{
|
||||||
begin: p.Begin,
|
p: map[int]struct{}{},
|
||||||
end: p.End,
|
defaultRange: defaultKey,
|
||||||
last: p.End,
|
portRanges: map[string]*portRange{
|
||||||
|
defaultKey: newPortRange(p.Begin, p.End),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
return pm
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseAll releases all ports for all ips.
|
// ReleaseAll releases all ports for all ips.
|
||||||
|
@ -194,17 +211,58 @@ func (p *PortAllocator) ReleaseAll() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *portMap) findPort() (int, error) {
|
func getRangeKey(portStart, portEnd int) string {
|
||||||
port := pm.last
|
return fmt.Sprintf("%d-%d", portStart, portEnd)
|
||||||
for i := 0; i <= pm.end-pm.begin; i++ {
|
}
|
||||||
|
|
||||||
|
func newPortRange(portStart, portEnd int) *portRange {
|
||||||
|
return &portRange{
|
||||||
|
begin: portStart,
|
||||||
|
end: portEnd,
|
||||||
|
last: portEnd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *portMap) getPortRange(portStart, portEnd int) (*portRange, error) {
|
||||||
|
var key string
|
||||||
|
if portStart == 0 && portEnd == 0 {
|
||||||
|
key = pm.defaultRange
|
||||||
|
} else {
|
||||||
|
key = getRangeKey(portStart, portEnd)
|
||||||
|
if portStart == portEnd ||
|
||||||
|
portStart == 0 || portEnd == 0 ||
|
||||||
|
portEnd < portStart {
|
||||||
|
return nil, fmt.Errorf("invalid port range: %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return existing port range, if already known.
|
||||||
|
if pr, exists := pm.portRanges[key]; exists {
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create a new port range.
|
||||||
|
pr := newPortRange(portStart, portEnd)
|
||||||
|
pm.portRanges[key] = pr
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *portMap) findPort(portStart, portEnd int) (int, error) {
|
||||||
|
pr, err := pm.getPortRange(portStart, portEnd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
port := pr.last
|
||||||
|
|
||||||
|
for i := 0; i <= pr.end-pr.begin; i++ {
|
||||||
port++
|
port++
|
||||||
if port > pm.end {
|
if port > pr.end {
|
||||||
port = pm.begin
|
port = pr.begin
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := pm.p[port]; !ok {
|
if _, ok := pm.p[port]; !ok {
|
||||||
pm.p[port] = struct{}{}
|
pm.p[port] = struct{}{}
|
||||||
pm.last = port
|
pr.last = port
|
||||||
return port, nil
|
return port, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,6 +236,72 @@ func TestPortAllocation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPortAllocationWithCustomRange(t *testing.T) {
|
||||||
|
p := Get()
|
||||||
|
defer resetPortAllocator()
|
||||||
|
|
||||||
|
start, end := 8081, 8082
|
||||||
|
specificPort := 8000
|
||||||
|
|
||||||
|
//get an ephemeral port.
|
||||||
|
port1, err := p.RequestPortInRange(defaultIP, "tcp", 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//request invalid ranges
|
||||||
|
if _, err := p.RequestPortInRange(defaultIP, "tcp", 0, end); err == nil {
|
||||||
|
t.Fatalf("Expected error for invalid range %d-%d", 0, end)
|
||||||
|
}
|
||||||
|
if _, err := p.RequestPortInRange(defaultIP, "tcp", start, 0); err == nil {
|
||||||
|
t.Fatalf("Expected error for invalid range %d-%d", 0, end)
|
||||||
|
}
|
||||||
|
if _, err := p.RequestPortInRange(defaultIP, "tcp", 8081, 8080); err == nil {
|
||||||
|
t.Fatalf("Expected error for invalid range %d-%d", 0, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
//request a single port
|
||||||
|
port, err := p.RequestPortInRange(defaultIP, "tcp", specificPort, specificPort)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if port != specificPort {
|
||||||
|
t.Fatalf("Expected port %d, got %d", specificPort, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
//get a port from the range
|
||||||
|
port2, err := p.RequestPortInRange(defaultIP, "tcp", start, end)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if port2 < start || port2 > end {
|
||||||
|
t.Fatalf("Expected a port between %d and %d, got %d", start, end, port2)
|
||||||
|
}
|
||||||
|
//get another ephemeral port (should be > port1)
|
||||||
|
port3, err := p.RequestPortInRange(defaultIP, "tcp", 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if port3 < port1 {
|
||||||
|
t.Fatalf("Expected new port > %d in the ephemeral range, got %d", port1, port3)
|
||||||
|
}
|
||||||
|
//get another (and in this case the only other) port from the range
|
||||||
|
port4, err := p.RequestPortInRange(defaultIP, "tcp", start, end)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if port4 < start || port4 > end {
|
||||||
|
t.Fatalf("Expected a port between %d and %d, got %d", start, end, port4)
|
||||||
|
}
|
||||||
|
if port4 == port2 {
|
||||||
|
t.Fatal("Allocated the same port from a custom range")
|
||||||
|
}
|
||||||
|
//request 3rd port from the range of 2
|
||||||
|
if _, err := p.RequestPortInRange(defaultIP, "tcp", start, end); err != ErrAllPortsAllocated {
|
||||||
|
t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNoDuplicateBPR(t *testing.T) {
|
func TestNoDuplicateBPR(t *testing.T) {
|
||||||
p := Get()
|
p := Get()
|
||||||
defer resetPortAllocator()
|
defer resetPortAllocator()
|
||||||
|
|
|
@ -62,6 +62,11 @@ func (pm *PortMapper) SetIptablesChain(c *iptables.ChainInfo, bridgeName string)
|
||||||
|
|
||||||
// Map maps the specified container transport address to the host's network address and transport port
|
// Map maps the specified container transport address to the host's network address and transport port
|
||||||
func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) {
|
func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) {
|
||||||
|
return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, useProxy bool) (host net.Addr, err error) {
|
||||||
pm.lock.Lock()
|
pm.lock.Lock()
|
||||||
defer pm.lock.Unlock()
|
defer pm.lock.Unlock()
|
||||||
|
|
||||||
|
@ -74,7 +79,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, usePr
|
||||||
switch container.(type) {
|
switch container.(type) {
|
||||||
case *net.TCPAddr:
|
case *net.TCPAddr:
|
||||||
proto = "tcp"
|
proto = "tcp"
|
||||||
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
if allocatedHostPort, err = pm.Allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +96,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, usePr
|
||||||
}
|
}
|
||||||
case *net.UDPAddr:
|
case *net.UDPAddr:
|
||||||
proto = "udp"
|
proto = "udp"
|
||||||
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
if allocatedHostPort, err = pm.Allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,12 @@ func (t *TransportPort) GetCopy() TransportPort {
|
||||||
|
|
||||||
// PortBinding represent a port binding between the container and the host
|
// PortBinding represent a port binding between the container and the host
|
||||||
type PortBinding struct {
|
type PortBinding struct {
|
||||||
Proto Protocol
|
Proto Protocol
|
||||||
IP net.IP
|
IP net.IP
|
||||||
Port uint16
|
Port uint16
|
||||||
HostIP net.IP
|
HostIP net.IP
|
||||||
HostPort uint16
|
HostPort uint16
|
||||||
|
HostPortEnd uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// HostAddr returns the host side transport address
|
// HostAddr returns the host side transport address
|
||||||
|
@ -58,11 +59,12 @@ func (p PortBinding) ContainerAddr() (net.Addr, error) {
|
||||||
// GetCopy returns a copy of this PortBinding structure instance
|
// GetCopy returns a copy of this PortBinding structure instance
|
||||||
func (p *PortBinding) GetCopy() PortBinding {
|
func (p *PortBinding) GetCopy() PortBinding {
|
||||||
return PortBinding{
|
return PortBinding{
|
||||||
Proto: p.Proto,
|
Proto: p.Proto,
|
||||||
IP: GetIPCopy(p.IP),
|
IP: GetIPCopy(p.IP),
|
||||||
Port: p.Port,
|
Port: p.Port,
|
||||||
HostIP: GetIPCopy(p.HostIP),
|
HostIP: GetIPCopy(p.HostIP),
|
||||||
HostPort: p.HostPort,
|
HostPort: p.HostPort,
|
||||||
|
HostPortEnd: p.HostPortEnd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +78,8 @@ func (p *PortBinding) Equal(o *PortBinding) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Proto != o.Proto || p.Port != o.Port || p.HostPort != o.HostPort {
|
if p.Proto != o.Proto || p.Port != o.Port ||
|
||||||
|
p.HostPort != o.HostPort || p.HostPortEnd != o.HostPortEnd {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue