| package portallocator |
| |
| import ( |
| "errors" |
| "github.com/dotcloud/docker/pkg/collections" |
| "net" |
| "sync" |
| ) |
| |
| const ( |
| BeginPortRange = 49153 |
| EndPortRange = 65535 |
| ) |
| |
| type ( |
| portMappings map[string]*collections.OrderedIntSet |
| ipMapping map[string]portMappings |
| ) |
| |
| var ( |
| ErrPortAlreadyAllocated = errors.New("port has already been allocated") |
| ErrPortExceedsRange = errors.New("port exceeds upper range") |
| ErrUnknownProtocol = errors.New("unknown protocol") |
| ) |
| |
| var ( |
| currentDynamicPort = map[string]int{ |
| "tcp": BeginPortRange - 1, |
| "udp": BeginPortRange - 1, |
| } |
| defaultIP = net.ParseIP("0.0.0.0") |
| defaultAllocatedPorts = portMappings{} |
| otherAllocatedPorts = ipMapping{} |
| lock = sync.Mutex{} |
| ) |
| |
| func init() { |
| defaultAllocatedPorts["tcp"] = collections.NewOrderedIntSet() |
| defaultAllocatedPorts["udp"] = collections.NewOrderedIntSet() |
| } |
| |
| // 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 |
| func RequestPort(ip net.IP, proto string, port int) (int, error) { |
| lock.Lock() |
| defer lock.Unlock() |
| |
| if err := validateProtocol(proto); err != nil { |
| return 0, err |
| } |
| |
| // If the user requested a specific port to be allocated |
| if port > 0 { |
| if err := registerSetPort(ip, proto, port); err != nil { |
| return 0, err |
| } |
| return port, nil |
| } |
| return registerDynamicPort(ip, proto) |
| } |
| |
| // ReleasePort will return the provided port back into the |
| // pool for reuse |
| func ReleasePort(ip net.IP, proto string, port int) error { |
| lock.Lock() |
| defer lock.Unlock() |
| |
| if err := validateProtocol(proto); err != nil { |
| return err |
| } |
| |
| allocated := defaultAllocatedPorts[proto] |
| allocated.Remove(port) |
| |
| if !equalsDefault(ip) { |
| registerIP(ip) |
| |
| // Remove the port for the specific ip address |
| allocated = otherAllocatedPorts[ip.String()][proto] |
| allocated.Remove(port) |
| } |
| return nil |
| } |
| |
| 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(), |
| } |
| } |
| } |
| |
| func validateProtocol(proto string) error { |
| if _, exists := defaultAllocatedPorts[proto]; !exists { |
| return ErrUnknownProtocol |
| } |
| return nil |
| } |