package ports import ( "errors" "net" "strconv" "sync" "time" "github.com/fatedier/frp/pkg/config/types" ) const ( MinPort = 1 MaxPort = 65535 MaxPortReservedDuration = time.Duration(24) * time.Hour CleanReservedPortsInterval = time.Hour ) var ( ErrPortAlreadyUsed = errors.New("port already used") ErrPortNotAllowed = errors.New("port not allowed") ErrPortUnAvailable = errors.New("port unavailable") ErrNoAvailablePort = errors.New("no available port") ) type PortCtx struct { ProxyName string Port int Closed bool UpdateTime time.Time } type Manager struct { reservedPorts map[string]*PortCtx usedPorts map[int]*PortCtx freePorts map[int]struct{} bindAddr string netType string mu sync.Mutex } func NewManager(netType string, bindAddr string, allowPorts []types.PortsRange) *Manager { pm := &Manager{ reservedPorts: make(map[string]*PortCtx), usedPorts: make(map[int]*PortCtx), freePorts: make(map[int]struct{}), bindAddr: bindAddr, netType: netType, } if len(allowPorts) > 0 { for _, pair := range allowPorts { if pair.Single > 0 { pm.freePorts[pair.Single] = struct{}{} } else { for i := pair.Start; i <= pair.End; i++ { pm.freePorts[i] = struct{}{} } } } } else { for i := MinPort; i <= MaxPort; i++ { pm.freePorts[i] = struct{}{} } } go pm.cleanReservedPortsWorker() return pm } func (pm *Manager) Acquire(name string, port int) (realPort int, err error) { portCtx := &PortCtx{ ProxyName: name, Closed: false, UpdateTime: time.Now(), } var ok bool pm.mu.Lock() defer func() { if err == nil { portCtx.Port = realPort } pm.mu.Unlock() }() // check reserved ports first if port == 0 { if ctx, ok := pm.reservedPorts[name]; ok { if pm.isPortAvailable(ctx.Port) { realPort = ctx.Port pm.usedPorts[realPort] = portCtx pm.reservedPorts[name] = portCtx delete(pm.freePorts, realPort) return } } } if port == 0 { // get random port count := 0 maxTryTimes := 5 for k := range pm.freePorts { count++ if count > maxTryTimes { break } if pm.isPortAvailable(k) { realPort = k pm.usedPorts[realPort] = portCtx pm.reservedPorts[name] = portCtx delete(pm.freePorts, realPort) break } } if realPort == 0 { err = ErrNoAvailablePort } } else { // specified port if _, ok = pm.freePorts[port]; ok { if pm.isPortAvailable(port) { realPort = port pm.usedPorts[realPort] = portCtx pm.reservedPorts[name] = portCtx delete(pm.freePorts, realPort) } else { err = ErrPortUnAvailable } } else { if _, ok = pm.usedPorts[port]; ok { err = ErrPortAlreadyUsed } else { err = ErrPortNotAllowed } } } return } func (pm *Manager) isPortAvailable(port int) bool { if pm.netType == "udp" { addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(pm.bindAddr, strconv.Itoa(port))) if err != nil { return false } l, err := net.ListenUDP("udp", addr) if err != nil { return false } l.Close() return true } l, err := net.Listen(pm.netType, net.JoinHostPort(pm.bindAddr, strconv.Itoa(port))) if err != nil { return false } l.Close() return true } func (pm *Manager) Release(port int) { pm.mu.Lock() defer pm.mu.Unlock() if ctx, ok := pm.usedPorts[port]; ok { pm.freePorts[port] = struct{}{} delete(pm.usedPorts, port) ctx.Closed = true ctx.UpdateTime = time.Now() } } // Release reserved port if it isn't used in last 24 hours. func (pm *Manager) cleanReservedPortsWorker() { for { time.Sleep(CleanReservedPortsInterval) pm.mu.Lock() for name, ctx := range pm.reservedPorts { if ctx.Closed && time.Since(ctx.UpdateTime) > MaxPortReservedDuration { delete(pm.reservedPorts, name) } } pm.mu.Unlock() } }