+10
-2
@@ -692,7 +692,11 @@ func (s *Service) CreateProxy(ctx context.Context, in ProxyInput) (Proxy, error)
|
||||
if err := validateProxyInput(in); err != nil {
|
||||
return Proxy{}, err
|
||||
}
|
||||
if _, err := s.store.GetServer(ctx, in.ServerID); err != nil {
|
||||
server, err := s.store.GetServer(ctx, in.ServerID)
|
||||
if err != nil {
|
||||
return Proxy{}, err
|
||||
}
|
||||
if err := validateRemotePortAllowed(server, in); err != nil {
|
||||
return Proxy{}, err
|
||||
}
|
||||
proxy, err := s.store.CreateProxy(ctx, in)
|
||||
@@ -711,7 +715,11 @@ func (s *Service) UpdateProxy(ctx context.Context, id int64, in ProxyInput) (Pro
|
||||
if err := validateProxyInput(in); err != nil {
|
||||
return Proxy{}, err
|
||||
}
|
||||
if _, err := s.store.GetServer(ctx, in.ServerID); err != nil {
|
||||
server, err := s.store.GetServer(ctx, in.ServerID)
|
||||
if err != nil {
|
||||
return Proxy{}, err
|
||||
}
|
||||
if err := validateRemotePortAllowed(server, in); err != nil {
|
||||
return Proxy{}, err
|
||||
}
|
||||
proxy, err := s.store.UpdateProxy(ctx, id, in)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -394,6 +395,50 @@ func validHost(host string) bool {
|
||||
return validDomain(host)
|
||||
}
|
||||
|
||||
// validateRemotePortAllowed 检查 tcp/udp 规则的远程端口是否落在服务端的 allowPorts 白名单里。
|
||||
//
|
||||
// frps 只认 allowPorts:端口不在白名单时规则会被服务端拒绝,frpc 会一直重试并刷
|
||||
// `[规则名] start error: port not allowed`。与其等用户去翻 frpc 日志,不如保存时就说清楚。
|
||||
func validateRemotePortAllowed(server Server, in ProxyInput) error {
|
||||
allow := strings.TrimSpace(server.Options.AllowPorts)
|
||||
if allow == "" || in.RemotePort == nil {
|
||||
return nil
|
||||
}
|
||||
if in.Type != "tcp" && in.Type != "udp" {
|
||||
return nil
|
||||
}
|
||||
if portAllowed(allow, *in.RemotePort) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"远程端口 %d 不在服务端「%s」的 allowPorts 白名单(%s)内:frps 会拒绝这条规则,frpc 会反复报 \"port not allowed\"。请到「公网服务端 → 服务端参数」把 %d 加进白名单(或清空 allowPorts 表示不限),保存后重新下发 frps 配置",
|
||||
*in.RemotePort, server.Name, allow, *in.RemotePort)
|
||||
}
|
||||
|
||||
// portAllowed 按 allowPorts 的写法(单端口 / 起-止 区间,逗号分隔)判断端口是否放行。
|
||||
// 区间写反(6010-6000)、越界(0 或 >65535)与 renderAllowPorts 一样视为无效,不放行;
|
||||
// 否则会出现「校验说放行、实际生成的 frps.toml 里这条白名单被丢弃」的偏差。
|
||||
func portAllowed(allowPorts string, port int) bool {
|
||||
for _, part := range strings.Split(allowPorts, ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if start, end, ok := strings.Cut(part, "-"); ok {
|
||||
startN, err1 := strconv.Atoi(strings.TrimSpace(start))
|
||||
endN, err2 := strconv.Atoi(strings.TrimSpace(end))
|
||||
if err1 == nil && err2 == nil && startN > 0 && endN >= startN && endN <= 65535 && port >= startN && port <= endN {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if n, err := strconv.Atoi(part); err == nil && n > 0 && n <= 65535 && n == port {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validDomain(domain string) bool {
|
||||
domain = strings.TrimSuffix(strings.TrimSpace(domain), ".")
|
||||
if domain == "" || len(domain) > 253 || strings.Contains(domain, "..") {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package frp
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateProxyInput(t *testing.T) {
|
||||
port := 60022
|
||||
@@ -80,3 +83,43 @@ func TestValidateServerInputVhostPorts(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRemotePortAllowed(t *testing.T) {
|
||||
port := func(value int) *int { return &value }
|
||||
base := Server{Name: "雨云vps"}
|
||||
cases := []struct {
|
||||
name string
|
||||
allow string
|
||||
input ProxyInput
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "单端口命中", allow: "6000,6095", input: ProxyInput{Type: "tcp", RemotePort: port(6095)}},
|
||||
{name: "区间命中", allow: "6000-6100", input: ProxyInput{Type: "tcp", RemotePort: port(6095)}},
|
||||
{name: "区间下界命中", allow: "6095-6200", input: ProxyInput{Type: "udp", RemotePort: port(6095)}},
|
||||
{name: "带空格写法", allow: " 6000 - 6100 , 7000", input: ProxyInput{Type: "tcp", RemotePort: port(6095)}},
|
||||
{name: "不在白名单拒绝", allow: "6000-6010", input: ProxyInput{Type: "tcp", RemotePort: port(6095)}, wantErr: true},
|
||||
{name: "区间写反视为无效", allow: "6100-6000", input: ProxyInput{Type: "tcp", RemotePort: port(6095)}, wantErr: true},
|
||||
{name: "越界区间视为无效", allow: "6000-70000", input: ProxyInput{Type: "tcp", RemotePort: port(6095)}, wantErr: true},
|
||||
{name: "白名单为空不限制", allow: "", input: ProxyInput{Type: "tcp", RemotePort: port(6095)}},
|
||||
{name: "HTTP 规则跳过校验", allow: "6000-6010", input: ProxyInput{Type: "http", RemotePort: port(6095)}},
|
||||
{name: "无远程端口跳过校验", allow: "6000-6010", input: ProxyInput{Type: "tcp"}},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
server := base
|
||||
server.Options.AllowPorts = testCase.allow
|
||||
err := validateRemotePortAllowed(server, testCase.input)
|
||||
if (err != nil) != testCase.wantErr {
|
||||
t.Fatalf("validateRemotePortAllowed() error = %v, wantErr %v", err, testCase.wantErr)
|
||||
}
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
message := err.Error()
|
||||
if !strings.Contains(message, "6095") || !strings.Contains(message, testCase.allow) {
|
||||
t.Fatalf("错误信息应包含端口与白名单原文,实际为 %q", message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user