2014-01-23 09:43:50 -05:00
|
|
|
package portallocator
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"github.com/dotcloud/docker/pkg/collections"
|
2014-01-23 10:46:42 -05:00
|
|
|
"net"
|
2014-01-23 09:43:50 -05:00
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
BeginPortRange = 49153
|
|
|
|
EndPortRange = 65535
|
|
|
|
)
|
|
|
|
|
2014-01-23 15:17:28 -05:00
|
|
|
type (
|
|
|
|
portMappings map[string]*collections.OrderedIntSet
|
|
|
|
ipMapping map[string]portMappings
|
|
|
|
)
|
|
|
|
|
2014-01-23 09:43:50 -05:00
|
|
|
var (
|
|
|
|
ErrPortAlreadyAllocated = errors.New("port has already been allocated")
|
|
|
|
ErrPortExceedsRange = errors.New("port exceeds upper range")
|
|
|
|
ErrUnknownProtocol = errors.New("unknown protocol")
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2014-01-23 15:17:28 -05:00
|
|
|
currentDynamicPort = map[string]int{
|
|
|
|
"tcp": BeginPortRange - 1,
|
|
|
|
"udp": BeginPortRange - 1,
|
|
|
|
}
|
|
|
|
defaultIP = net.ParseIP("0.0.0.0")
|
|
|
|
defaultAllocatedPorts = portMappings{}
|
|
|
|
otherAllocatedPorts = ipMapping{}
|
|
|
|
lock = sync.Mutex{}
|
2014-01-23 09:43:50 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2014-01-23 15:17:28 -05:00
|
|
|
defaultAllocatedPorts["tcp"] = collections.NewOrderedIntSet()
|
|
|
|
defaultAllocatedPorts["udp"] = collections.NewOrderedIntSet()
|
2014-01-23 09:43:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// RequestPort returns an available port if the port is 0
|
|
|
|
// If the provided port is not 0 then it will be checked if
|
|
|
|
// it is available for allocation
|
2014-01-23 10:46:42 -05:00
|
|
|
func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
2014-01-23 09:43:50 -05:00
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
|
|
|
if err := validateProtocol(proto); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2014-01-23 10:46:42 -05:00
|
|
|
// If the user requested a specific port to be allocated
|
2014-01-30 17:52:59 -05:00
|
|
|
if port > 0 {
|
2014-01-23 15:17:28 -05:00
|
|
|
if err := registerSetPort(ip, proto, port); err != nil {
|
2014-01-23 10:46:42 -05:00
|
|
|
return 0, err
|
|
|
|
}
|
2014-01-23 09:43:50 -05:00
|
|
|
return port, nil
|
|
|
|
}
|
2014-01-23 15:17:28 -05:00
|
|
|
return registerDynamicPort(ip, proto)
|
2014-01-23 09:43:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReleasePort will return the provided port back into the
|
|
|
|
// pool for reuse
|
2014-01-23 10:46:42 -05:00
|
|
|
func ReleasePort(ip net.IP, proto string, port int) error {
|
2014-01-23 09:43:50 -05:00
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
|
|
|
if err := validateProtocol(proto); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-01-23 15:17:28 -05:00
|
|
|
allocated := defaultAllocatedPorts[proto]
|
2014-01-23 09:43:50 -05:00
|
|
|
allocated.Remove(port)
|
|
|
|
|
2014-01-23 15:17:28 -05:00
|
|
|
if !equalsDefault(ip) {
|
|
|
|
registerIP(ip)
|
|
|
|
|
|
|
|
// Remove the port for the specific ip address
|
|
|
|
allocated = otherAllocatedPorts[ip.String()][proto]
|
|
|
|
allocated.Remove(port)
|
|
|
|
}
|
2014-01-23 09:43:50 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-01-23 15:17:28 -05:00
|
|
|
func ReleaseAll() error {
|
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
|
|
|
currentDynamicPort["tcp"] = BeginPortRange - 1
|
|
|
|
currentDynamicPort["udp"] = BeginPortRange - 1
|
|
|
|
|
|
|
|
defaultAllocatedPorts = portMappings{}
|
|
|
|
defaultAllocatedPorts["tcp"] = collections.NewOrderedIntSet()
|
|
|
|
defaultAllocatedPorts["udp"] = collections.NewOrderedIntSet()
|
|
|
|
|
|
|
|
otherAllocatedPorts = ipMapping{}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func registerDynamicPort(ip net.IP, proto string) (int, error) {
|
|
|
|
allocated := defaultAllocatedPorts[proto]
|
|
|
|
|
|
|
|
port := nextPort(proto)
|
|
|
|
if port > EndPortRange {
|
|
|
|
return 0, ErrPortExceedsRange
|
|
|
|
}
|
|
|
|
|
|
|
|
if !equalsDefault(ip) {
|
|
|
|
registerIP(ip)
|
|
|
|
|
|
|
|
ipAllocated := otherAllocatedPorts[ip.String()][proto]
|
|
|
|
ipAllocated.Push(port)
|
|
|
|
} else {
|
|
|
|
allocated.Push(port)
|
|
|
|
}
|
|
|
|
return port, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func registerSetPort(ip net.IP, proto string, port int) error {
|
|
|
|
allocated := defaultAllocatedPorts[proto]
|
|
|
|
if allocated.Exists(port) {
|
|
|
|
return ErrPortAlreadyAllocated
|
|
|
|
}
|
|
|
|
|
|
|
|
if !equalsDefault(ip) {
|
|
|
|
registerIP(ip)
|
|
|
|
|
|
|
|
ipAllocated := otherAllocatedPorts[ip.String()][proto]
|
|
|
|
if ipAllocated.Exists(port) {
|
|
|
|
return ErrPortAlreadyAllocated
|
|
|
|
}
|
|
|
|
ipAllocated.Push(port)
|
|
|
|
} else {
|
|
|
|
allocated.Push(port)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func equalsDefault(ip net.IP) bool {
|
|
|
|
return ip == nil || ip.Equal(defaultIP)
|
|
|
|
}
|
|
|
|
|
|
|
|
func nextPort(proto string) int {
|
|
|
|
c := currentDynamicPort[proto] + 1
|
|
|
|
currentDynamicPort[proto] = c
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
func registerIP(ip net.IP) {
|
|
|
|
if _, exists := otherAllocatedPorts[ip.String()]; !exists {
|
|
|
|
otherAllocatedPorts[ip.String()] = portMappings{
|
|
|
|
"tcp": collections.NewOrderedIntSet(),
|
|
|
|
"udp": collections.NewOrderedIntSet(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-23 09:43:50 -05:00
|
|
|
func validateProtocol(proto string) error {
|
2014-01-23 15:17:28 -05:00
|
|
|
if _, exists := defaultAllocatedPorts[proto]; !exists {
|
2014-01-23 09:43:50 -05:00
|
|
|
return ErrUnknownProtocol
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|