mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-02 08:56:38 +08:00
162 lines
4.1 KiB
Go
162 lines
4.1 KiB
Go
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 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") || isTunnelProbeTargetSchemeLikeHost(host) {
|
|
return tunnelProbeTarget{}, false, errors.New("测试目标 Host 不能包含协议或路径")
|
|
}
|
|
if normalized, ok := normalizeTunnelProbeTargetHost(host); ok {
|
|
host = normalized
|
|
} else {
|
|
return tunnelProbeTarget{}, false, errors.New("测试目标 Host 格式无效")
|
|
}
|
|
|
|
return tunnelProbeTarget{Host: host, Port: port}, true, nil
|
|
}
|
|
|
|
func normalizeTunnelProbeTargetHost(host string) (string, bool) {
|
|
if strings.HasPrefix(host, "[") || strings.HasSuffix(host, "]") {
|
|
if !strings.HasPrefix(host, "[") || !strings.HasSuffix(host, "]") {
|
|
return "", false
|
|
}
|
|
inner := strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[")
|
|
addr, err := netip.ParseAddr(inner)
|
|
if err != nil || !addr.Is6() {
|
|
return "", false
|
|
}
|
|
return inner, true
|
|
}
|
|
|
|
if addr, err := netip.ParseAddr(host); err == nil {
|
|
return addr.String(), true
|
|
}
|
|
if strings.Contains(host, ":") || isTunnelProbeTargetIPv4Like(host) {
|
|
return "", false
|
|
}
|
|
if !isValidTunnelProbeTargetHost(host) {
|
|
return "", false
|
|
}
|
|
return host, true
|
|
}
|
|
|
|
func isValidTunnelProbeTargetHost(host string) bool {
|
|
if host == "" || len(host) > 253 {
|
|
return false
|
|
}
|
|
for _, label := range strings.Split(host, ".") {
|
|
if len(label) == 0 || len(label) > 63 || label[0] == '-' || label[len(label)-1] == '-' {
|
|
return false
|
|
}
|
|
for _, r := range label {
|
|
if !isASCIILetter(r) && !isASCIIDigit(r) && r != '-' {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isTunnelProbeTargetIPv4Like(host string) bool {
|
|
if host == "" {
|
|
return false
|
|
}
|
|
for _, r := range host {
|
|
if !isASCIIDigit(r) && r != '.' {
|
|
return false
|
|
}
|
|
}
|
|
return strings.Contains(host, ".")
|
|
}
|
|
|
|
func isTunnelProbeTargetSchemeLikeHost(host string) bool {
|
|
if _, err := netip.ParseAddr(host); err == nil {
|
|
return false
|
|
}
|
|
|
|
colon := strings.IndexByte(host, ':')
|
|
if colon <= 0 {
|
|
return false
|
|
}
|
|
for i, r := range host[:colon] {
|
|
if i == 0 {
|
|
if !isASCIILetter(r) {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
if !isASCIILetter(r) && !isASCIIDigit(r) && r != '+' && r != '-' && r != '.' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isASCIILetter(r rune) bool {
|
|
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
|
}
|
|
|
|
func isASCIIDigit(r rune) bool {
|
|
return r >= '0' && r <= '9'
|
|
}
|
|
|
|
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 effectiveTunnelProbeTargetValues(tunnel.ProbeTargetHost, tunnel.ProbeTargetPort)
|
|
}
|
|
|
|
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)
|
|
}
|