mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
feat: add tunnel probe target normalization
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go-backend/internal/store/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultTunnelProbeTargetHost = "www.bing.com"
|
||||||
|
defaultTunnelProbeTargetPort = 443
|
||||||
|
)
|
||||||
|
|
||||||
|
type tunnelProbeTarget struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultTunnelProbeTarget() tunnelProbeTarget {
|
||||||
|
return tunnelProbeTarget{Host: defaultTunnelProbeTargetHost, Port: defaultTunnelProbeTargetPort}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeTunnelProbeTarget(host string, port int) (tunnelProbeTarget, bool, error) {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||||
|
host = strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[")
|
||||||
|
}
|
||||||
|
|
||||||
|
if host == "" && port == 0 {
|
||||||
|
return defaultTunnelProbeTarget(), false, nil
|
||||||
|
}
|
||||||
|
if host == "" {
|
||||||
|
return tunnelProbeTarget{}, false, errors.New("测试目标 Host 不能为空")
|
||||||
|
}
|
||||||
|
if port <= 0 || port > 65535 {
|
||||||
|
return tunnelProbeTarget{}, false, errors.New("测试目标端口必须是 1-65535")
|
||||||
|
}
|
||||||
|
if strings.Contains(host, "://") || strings.ContainsAny(host, "/?#") || strings.ContainsAny(host, " \t\r\n") {
|
||||||
|
return tunnelProbeTarget{}, false, errors.New("测试目标 Host 不能包含协议或路径")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tunnelProbeTarget{Host: host, Port: port}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTunnelProbeTargetFromRequest(req map[string]interface{}) (tunnelProbeTarget, bool, error) {
|
||||||
|
if req == nil {
|
||||||
|
return defaultTunnelProbeTarget(), false, nil
|
||||||
|
}
|
||||||
|
return normalizeTunnelProbeTarget(asString(req["probeTargetHost"]), asInt(req["probeTargetPort"], 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func effectiveTunnelProbeTarget(tunnel *model.Tunnel) tunnelProbeTarget {
|
||||||
|
if tunnel == nil {
|
||||||
|
return defaultTunnelProbeTarget()
|
||||||
|
}
|
||||||
|
return defaultTunnelProbeTarget()
|
||||||
|
}
|
||||||
|
|
||||||
|
func effectiveTunnelProbeTargetValues(host string, port int) tunnelProbeTarget {
|
||||||
|
target, configured, err := normalizeTunnelProbeTarget(host, port)
|
||||||
|
if err != nil || !configured {
|
||||||
|
return defaultTunnelProbeTarget()
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatTunnelProbeTarget(target tunnelProbeTarget) string {
|
||||||
|
if addr, err := netip.ParseAddr(target.Host); err == nil && addr.Is6() {
|
||||||
|
return fmt.Sprintf("[%s]:%d", target.Host, target.Port)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d", target.Host, target.Port)
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestNormalizeTunnelProbeTargetDefaultsWhenEmpty(t *testing.T) {
|
||||||
|
target, configured, err := normalizeTunnelProbeTarget("", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if configured {
|
||||||
|
t.Fatalf("expected empty input to be default, not configured")
|
||||||
|
}
|
||||||
|
if target.Host != defaultTunnelProbeTargetHost || target.Port != defaultTunnelProbeTargetPort {
|
||||||
|
t.Fatalf("unexpected default target: %+v", target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeTunnelProbeTargetAcceptsHostPortAndIPv6(t *testing.T) {
|
||||||
|
target, configured, err := normalizeTunnelProbeTarget(" [2001:db8::1] ", 8443)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !configured {
|
||||||
|
t.Fatalf("expected explicit target")
|
||||||
|
}
|
||||||
|
if target.Host != "2001:db8::1" || target.Port != 8443 {
|
||||||
|
t.Fatalf("unexpected normalized target: %+v", target)
|
||||||
|
}
|
||||||
|
if got := formatTunnelProbeTarget(target); got != "[2001:db8::1]:8443" {
|
||||||
|
t.Fatalf("unexpected formatted target: %s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeTunnelProbeTargetRejectsPartialAndInvalidInputs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
}{
|
||||||
|
{name: "missing host", host: "", port: 443},
|
||||||
|
{name: "missing port", host: "example.com", port: 0},
|
||||||
|
{name: "port too high", host: "example.com", port: 70000},
|
||||||
|
{name: "scheme", host: "https://example.com", port: 443},
|
||||||
|
{name: "path", host: "example.com/ping", port: 443},
|
||||||
|
{name: "space", host: "example .com", port: 443},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if _, _, err := normalizeTunnelProbeTarget(tt.host, tt.port); err == nil {
|
||||||
|
t.Fatalf("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTunnelProbeTargetFromRequest(t *testing.T) {
|
||||||
|
req := map[string]interface{}{
|
||||||
|
"probeTargetHost": "speed.example.com",
|
||||||
|
"probeTargetPort": float64(1443),
|
||||||
|
}
|
||||||
|
target, configured, err := parseTunnelProbeTargetFromRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !configured || target.Host != "speed.example.com" || target.Port != 1443 {
|
||||||
|
t.Fatalf("unexpected request target: %+v configured=%v", target, configured)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user