123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- 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()
- }
- }
|