改了什么: - cmd/node/config.go: 零依赖 ini 配置加载([server] Addr/TunnelBind/JwtSecret/SweepSeconds + [tunnel] PortRange); -config node.ini 启用 - main.go: -config 与 -port-range flag; 配置来源优先级 显式 flag > 配置文件(非零项) > 内置默认(flag.Visit 判显式) - internal/node/portrange.go: PortRange 解析(20000-21000/单端口/容错) + listen(随机起点顺延探测, 占用即生效避免 TOCTOU) - node.go: TunnelPortRange 字段; listenTunnel 统一隧道建听——端口 0 且配置范围时范围内取空闲端口, 否则原路径(指定端口直监听/系统随机) - Start 日志打出端口范围; portrange_test.go 覆盖解析与占满/释放取端口 为什么: agent 注册隧道从不指定端口(ListenPort=0), 节点 net.Listen 落系统随机高位段, 云主机防火墙无法预开; 配置范围后临时/永久隧道都落在固定段内, 防火墙放行 5212+范围即可
73 lines
2.1 KiB
Go
73 lines
2.1 KiB
Go
// 端口范围:临时/永久隧道注册时未指定监听端口(ListenPort=0)的取值范围。
|
|
// 云主机防火墙按范围放行,避免隧道端口落在系统随机高位段而无法预开。
|
|
package node
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// PortRange 端口范围(含两端);Min==0 表示未配置(回退系统随机分配)。
|
|
type PortRange struct {
|
|
Min, Max int
|
|
}
|
|
|
|
// Valid 是否已配置有效范围
|
|
func (r PortRange) Valid() bool { return r.Min > 0 && r.Max >= r.Min }
|
|
|
|
// ParsePortRange 解析 "20000-21000"(单端口 "20000" 视为 min=max)。
|
|
func ParsePortRange(s string) (PortRange, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return PortRange{}, nil
|
|
}
|
|
minStr, maxStr, ok := strings.Cut(s, "-")
|
|
if !ok {
|
|
maxStr = minStr
|
|
}
|
|
minP, err := strconv.Atoi(strings.TrimSpace(minStr))
|
|
if err != nil {
|
|
return PortRange{}, fmt.Errorf("端口范围格式错误:%s", s)
|
|
}
|
|
maxP, err := strconv.Atoi(strings.TrimSpace(maxStr))
|
|
if err != nil {
|
|
return PortRange{}, fmt.Errorf("端口范围格式错误:%s", s)
|
|
}
|
|
r := PortRange{Min: minP, Max: maxP}
|
|
if !r.Valid() || r.Max > 65535 {
|
|
return PortRange{}, fmt.Errorf("端口范围无效:%s", s)
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// String 还原配置形式(便于日志)
|
|
func (r PortRange) String() string {
|
|
if !r.Valid() {
|
|
return ""
|
|
}
|
|
if r.Min == r.Max {
|
|
return strconv.Itoa(r.Min)
|
|
}
|
|
return fmt.Sprintf("%d-%d", r.Min, r.Max)
|
|
}
|
|
|
|
// listen 从范围内随机起点顺延探测,返回第一个监听成功的 listener(占用即生效,避免探测与使用竞态)。
|
|
// 全部被占返回错误。bind 为隧道监听绑定地址。
|
|
func (r PortRange) listen(bind string) (net.Listener, error) {
|
|
if !r.Valid() {
|
|
return nil, fmt.Errorf("端口范围未配置")
|
|
}
|
|
start := r.Min + rand.Intn(r.Max-r.Min+1)
|
|
for i := 0; i <= r.Max-r.Min; i++ {
|
|
port := r.Min + (start-r.Min+i)%(r.Max-r.Min+1)
|
|
l, err := net.Listen("tcp", net.JoinHostPort(bind, strconv.Itoa(port)))
|
|
if err == nil {
|
|
return l, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("端口范围 %s 内无可用端口", r)
|
|
}
|