From b8e64a88af013ca6a32d0a1f005fc20304887605 Mon Sep 17 00:00:00 2001 From: DiheChen Date: Sun, 8 Nov 2026 22:45:54 +0800 Subject: [PATCH] feat: accept single addresses and IPv4-mapped ranges as trusted proxies --- internal/bootstrap/config.go | 24 +++++++++++++++++++++--- internal/bootstrap/config_test.go | 14 +++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/internal/bootstrap/config.go b/internal/bootstrap/config.go index 04d63b5..35e760f 100644 --- a/internal/bootstrap/config.go +++ b/internal/bootstrap/config.go @@ -155,15 +155,33 @@ func parseTrustedProxies(value string) ([]netip.Prefix, error) { if item == "" { return nil, errors.New("ALISTEN_TRUSTED_PROXIES 中有空项") } - prefix, err := netip.ParsePrefix(item) + prefix, err := parseTrustedProxy(item) if err != nil { - return nil, fmt.Errorf("ALISTEN_TRUSTED_PROXIES 中的 %q 不是 CIDR:%w", item, err) + return nil, fmt.Errorf("ALISTEN_TRUSTED_PROXIES 中的 %q 不是 IP 地址或 CIDR:%w", item, err) } - prefixes = append(prefixes, prefix.Masked()) + prefixes = append(prefixes, prefix) } return prefixes, nil } +// 单个地址视为只含它自己的网段。clientIP 先把 IPv4 映射的 IPv6 地址还原成 IPv4 再匹配,所以 ::ffff: 开头的网段换成等价的 +// IPv4 网段,否则永远匹配不上。带 zone 的地址永远匹配不上网段,按不合法处理。 +func parseTrustedProxy(item string) (netip.Prefix, error) { + prefix, err := netip.ParsePrefix(item) + if err != nil { + addr, addrErr := netip.ParseAddr(item) + if addrErr != nil || addr.Zone() != "" { + return netip.Prefix{}, err + } + prefix = netip.PrefixFrom(addr, addr.BitLen()) + } + prefix = prefix.Masked() + if prefix.Addr().Is4In6() { + prefix = netip.PrefixFrom(prefix.Addr().Unmap(), prefix.Bits()-96) + } + return prefix, nil +} + func canonicalPort(port string) bool { n, err := strconv.Atoi(port) return err == nil && n >= 1 && n <= 65535 && strconv.Itoa(n) == port diff --git a/internal/bootstrap/config_test.go b/internal/bootstrap/config_test.go index 62c4cfe..5576082 100644 --- a/internal/bootstrap/config_test.go +++ b/internal/bootstrap/config_test.go @@ -100,7 +100,19 @@ func TestParseTrustedProxies(t *testing.T) { if got, err := parseTrustedProxies(""); err != nil || got != nil { t.Errorf("空值 = %v, %v,期望没有受信任的代理", got, err) } - for _, bad := range []string{"10.42.0.1", "10.42.0.0/16,", "not-a-cidr"} { + // 单个地址只含它自己;客户端地址先还原成 IPv4 再匹配,所以 IPv4 映射的网段换成等价的 IPv4 网段。 + for value, want := range map[string]string{ + "10.42.0.1": "10.42.0.1/32", + "fd00::1": "fd00::1/128", + "::ffff:10.42.0.0/112": "10.42.0.0/16", + "::ffff:10.42.0.1": "10.42.0.1/32", + "::ffff:0.0.0.0/96": "0.0.0.0/0", + } { + if got, err := parseTrustedProxies(value); err != nil || !slices.Equal(got, []netip.Prefix{netip.MustParsePrefix(want)}) { + t.Errorf("parseTrustedProxies(%q) = %v, %v,期望 %s", value, got, err, want) + } + } + for _, bad := range []string{"10.42.0.0/16,", "not-a-cidr", "10.42.0.1/33", "fe80::1%eth0", "10.42.0.256"} { if _, err := parseTrustedProxies(bad); err == nil { t.Errorf("parseTrustedProxies(%q) 应报错", bad) }