feat(poller): add port pool for WinBox tunnel allocation
Implements PortPool with mutex-protected allocation, bind verification to skip ports already in use by the OS, and release-for-reuse semantics. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
63
poller/internal/tunnel/portpool.go
Normal file
63
poller/internal/tunnel/portpool.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// PortPool tracks available ports in a fixed range for WinBox tunnel allocation.
|
||||
type PortPool struct {
|
||||
mu sync.Mutex
|
||||
used []bool
|
||||
base int
|
||||
count int
|
||||
}
|
||||
|
||||
func NewPortPool(min, max int) *PortPool {
|
||||
count := max - min + 1
|
||||
return &PortPool{
|
||||
used: make([]bool, count),
|
||||
base: min,
|
||||
count: count,
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate returns the next free port, verifying it can actually be bound.
|
||||
// Returns error if all ports are exhausted.
|
||||
func (pp *PortPool) Allocate() (int, error) {
|
||||
pp.mu.Lock()
|
||||
defer pp.mu.Unlock()
|
||||
|
||||
for i := 0; i < pp.count; i++ {
|
||||
if pp.used[i] {
|
||||
continue
|
||||
}
|
||||
port := pp.base + i
|
||||
if !canBind(port) {
|
||||
continue
|
||||
}
|
||||
pp.used[i] = true
|
||||
return port, nil
|
||||
}
|
||||
return 0, fmt.Errorf("no ports available in range %d-%d", pp.base, pp.base+pp.count-1)
|
||||
}
|
||||
|
||||
// Release returns a port to the pool.
|
||||
func (pp *PortPool) Release(port int) {
|
||||
pp.mu.Lock()
|
||||
defer pp.mu.Unlock()
|
||||
idx := port - pp.base
|
||||
if idx >= 0 && idx < pp.count {
|
||||
pp.used[idx] = false
|
||||
}
|
||||
}
|
||||
|
||||
func canBind(port int) bool {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ln.Close()
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user