2026-09-23 09:40:59 +08:00
package frp
2026-10-19 15:53:59 +08:00
import (
"strings"
"testing"
)
2026-09-23 09:40:59 +08:00
func TestValidateProxyInput ( t * testing . T ) {
port := 60022
cases := [] struct {
name string
input ProxyInput
wantErr bool
}{
{ name : "tcp" , input : ProxyInput { Name : "nas_ssh" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , RemotePort : & port }},
{ name : "http" , input : ProxyInput { Name : "nas-web" , Type : "http" , LocalIP : "127.0.0.1" , LocalPort : 18080 , CustomDomains : [] string { "nas.example.com" }}},
{ name : "missing remote port" , input : ProxyInput { Name : "nas" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 }, wantErr : true },
{ name : "invalid name" , input : ProxyInput { Name : "bad name" , Type : "http" , LocalIP : "127.0.0.1" , LocalPort : 80 , CustomDomains : [] string { "nas.example.com" }}, wantErr : true },
{ name : "invalid domain" , input : ProxyInput { Name : "nas" , Type : "http" , LocalIP : "127.0.0.1" , LocalPort : 80 , CustomDomains : [] string { "bad domain" }}, wantErr : true },
{ name : "udp" , input : ProxyInput { Name : "dns" , Type : "udp" , LocalIP : "127.0.0.1" , LocalPort : 53 , RemotePort : & port }},
{ name : "stcp" , input : ProxyInput { Name : "secret" , Type : "stcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , Options : ProxyOptions { SecretKey : "abc" }}},
{ name : "tcpmux missing multiplexer" , input : ProxyInput { Name : "mux" , Type : "tcpmux" , LocalIP : "127.0.0.1" , LocalPort : 80 }, wantErr : true },
{ name : "invalid bandwidth mode" , input : ProxyInput { Name : "nas" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , RemotePort : & port , Options : ProxyOptions { Transport : ProxyTransportOptions { BandwidthLimitMode : "wrong" }}}, wantErr : true },
{ name : "udp remote port" , input : ProxyInput { Name : "dns2" , Type : "udp" , LocalIP : "127.0.0.1" , LocalPort : 53 , RemotePort : & port }},
{ name : "xtcp missing secret key" , input : ProxyInput { Name : "p2p" , Type : "xtcp" , LocalIP : "127.0.0.1" , LocalPort : 22 }, wantErr : true },
{ name : "plugin http2https missing local addr" , input : ProxyInput { Name : "p1" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , RemotePort : & port , Options : ProxyOptions { Plugin : & ProxyPluginOptions { Type : "https2http" , CRTPath : "cert.pem" , KeyPath : "key.pem" }}}, wantErr : true },
{ name : "plugin https2http missing cert" , input : ProxyInput { Name : "p2" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , RemotePort : & port , Options : ProxyOptions { Plugin : & ProxyPluginOptions { Type : "https2http" , LocalAddr : "127.0.0.1:80" }}}, wantErr : true },
{ name : "plugin socks5 optional auth" , input : ProxyInput { Name : "p3" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , RemotePort : & port , Options : ProxyOptions { Plugin : & ProxyPluginOptions { Type : "socks5" }}}},
{ name : "plugin unix_domain_socket missing path" , input : ProxyInput { Name : "p4" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , RemotePort : & port , Options : ProxyOptions { Plugin : & ProxyPluginOptions { Type : "unix_domain_socket" }}}, wantErr : true },
{ name : "plugin static_file missing local path" , input : ProxyInput { Name : "p5" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , RemotePort : & port , Options : ProxyOptions { Plugin : & ProxyPluginOptions { Type : "static_file" }}}, wantErr : true },
{ name : "plugin unknown type" , input : ProxyInput { Name : "p6" , Type : "tcp" , LocalIP : "127.0.0.1" , LocalPort : 22 , RemotePort : & port , Options : ProxyOptions { Plugin : & ProxyPluginOptions { Type : "whatever" }}}, wantErr : true },
}
for _ , testCase := range cases {
t . Run ( testCase . name , func ( t * testing . T ) {
err := validateProxyInput ( testCase . input )
if ( err != nil ) != testCase . wantErr {
t . Fatalf ( "validateProxyInput() error = %v, wantErr %v" , err , testCase . wantErr )
}
// 对外导出的校验入口应与内部入口行为一致(关系表模式校验复用此函数)
if ( ValidateProxy ( testCase . input ) != nil ) != testCase . wantErr {
t . Fatalf ( "ValidateProxy() error = %v, wantErr %v" , ValidateProxy ( testCase . input ), testCase . wantErr )
}
})
}
}
2026-10-05 13:07:56 +08:00
func TestValidateServerInputVhostPorts ( t * testing . T ) {
base := ServerInput {
Name : "web" ,
ServerAddr : "203.0.113.10" ,
ServerPort : 7000 ,
Options : ServerOptions {
AuthMethod : "token" ,
LogLevel : "info" ,
Protocol : "tcp" ,
},
}
cases := [] struct {
name string
http int
https int
2026-10-11 15:07:07 +08:00
confirm bool
2026-10-05 13:07:56 +08:00
wantErr bool
}{
{ name : "默认端口" , http : 8080 , https : 8443 },
2026-10-11 15:07:07 +08:00
{ name : "HTTP 80 未二次确认" , http : 80 , https : 8443 , wantErr : true },
{ name : "HTTPS 443 未二次确认" , http : 8080 , https : 443 , wantErr : true },
{ name : "HTTP 80 已二次确认" , http : 80 , https : 8443 , confirm : true },
{ name : "HTTPS 443 已二次确认" , http : 8080 , https : 443 , confirm : true },
{ name : "80 与 443 同时使用且已二次确认" , http : 80 , https : 443 , confirm : true },
2026-10-05 13:07:56 +08:00
}
for _ , testCase := range cases {
t . Run ( testCase . name , func ( t * testing . T ) {
input := normalizeServerInput ( base )
input . Options . VhostHTTPPort = testCase . http
input . Options . VhostHTTPSPort = testCase . https
2026-10-11 15:07:07 +08:00
input . AllowPrivilegedVhostPorts = testCase . confirm
2026-10-05 13:07:56 +08:00
err := validateServerInput ( input )
if ( err != nil ) != testCase . wantErr {
t . Fatalf ( "validateServerInput() error = %v, wantErr %v" , err , testCase . wantErr )
}
})
}
}
2026-10-19 15:53:59 +08:00
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 )
}
})
}
}