Security: - Enforce client_max_body_size in HTTP/2 and HTTP/3 adapters before buffering - Use trusted-proxy-aware client IP extraction for access control and rate limiting - Include Host and Vary headers in proxy cache key to prevent cache poisoning - Harden static file path traversal check with filepath.Clean + prefix validation - Honor proxy_ssl config for WebSocket upstream TLS Proxy/handler bugs: - Decrement WebSocket connection count immediately on return (not deferred in retry loop) - Remove ineffective headersPool - Coalesce concurrent background cache refreshes with singleflight Dev/build: - Fix Makefile run and test-config targets - Remove broken wget healthcheck from docker-compose - Add missing integration build tags - Fix go vet warnings in tests CI: - Run golangci-lint and integration tests in Gitea Actions Refs: docs/superpowers/plans/2026-06-17-fix-review-issues.md
162 lines
3.6 KiB
Go
162 lines
3.6 KiB
Go
// Package netutil 提供网络工具功能的测试。
|
||
//
|
||
// 该文件测试网络工具模块的 IP 相关功能,包括:
|
||
// - 客户端 IP 提取
|
||
// - X-Forwarded-For 头解析
|
||
// - X-Real-IP 头解析
|
||
// - 远程地址解析
|
||
//
|
||
// 作者:xfy
|
||
package netutil
|
||
|
||
import (
|
||
"net"
|
||
"testing"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
)
|
||
|
||
func TestExtractClientIP(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
xff string
|
||
xri string
|
||
remoteAddr string
|
||
want string
|
||
}{
|
||
{
|
||
name: "X-Forwarded-For with single IP",
|
||
xff: "192.168.1.100",
|
||
want: "192.168.1.100",
|
||
},
|
||
{
|
||
name: "X-Forwarded-For with multiple IPs",
|
||
xff: "192.168.1.100, 10.0.0.1, 172.16.0.1",
|
||
want: "192.168.1.100",
|
||
},
|
||
{
|
||
name: "X-Real-IP only",
|
||
xri: "192.168.1.200",
|
||
want: "192.168.1.200",
|
||
},
|
||
{
|
||
name: "RemoteAddr fallback",
|
||
remoteAddr: "192.168.1.1:12345",
|
||
want: "0.0.0.0", // fasthttp 默认初始化为 0.0.0.0
|
||
},
|
||
{
|
||
name: "X-Forwarded-For takes precedence over X-Real-IP",
|
||
xff: "192.168.1.100",
|
||
xri: "192.168.1.200",
|
||
want: "192.168.1.100",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Init(&fasthttp.Request{}, nil, nil)
|
||
|
||
if tt.xff != "" {
|
||
ctx.Request.Header.Set("X-Forwarded-For", tt.xff)
|
||
}
|
||
if tt.xri != "" {
|
||
ctx.Request.Header.Set("X-Real-IP", tt.xri)
|
||
}
|
||
|
||
got := ExtractClientIP(ctx)
|
||
if got != tt.want {
|
||
t.Errorf("ExtractClientIP() = %q, want %q", got, tt.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestExtractClientIPNet(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
xff string
|
||
xri string
|
||
want net.IP
|
||
}{
|
||
{
|
||
name: "X-Forwarded-For valid IP",
|
||
xff: "192.168.1.100",
|
||
want: net.ParseIP("192.168.1.100"),
|
||
},
|
||
{
|
||
name: "X-Forwarded-For invalid IP",
|
||
xff: "invalid-ip",
|
||
want: net.ParseIP("0.0.0.0"), // fasthttp 默认 RemoteAddr
|
||
},
|
||
{
|
||
name: "X-Real-IP valid IP",
|
||
xri: "192.168.1.200",
|
||
want: net.ParseIP("192.168.1.200"),
|
||
},
|
||
{
|
||
name: "No headers",
|
||
want: net.ParseIP("0.0.0.0"), // fasthttp 默认 RemoteAddr
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Init(&fasthttp.Request{}, nil, nil)
|
||
|
||
if tt.xff != "" {
|
||
ctx.Request.Header.Set("X-Forwarded-For", tt.xff)
|
||
}
|
||
if tt.xri != "" {
|
||
ctx.Request.Header.Set("X-Real-IP", tt.xri)
|
||
}
|
||
|
||
got := ExtractClientIPNet(ctx)
|
||
if !got.Equal(tt.want) {
|
||
t.Errorf("ExtractClientIPNet() = %v, want %v", got, tt.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestGetRemoteAddrIP(_ *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Init(&fasthttp.Request{}, nil, nil)
|
||
|
||
// Without setting remote addr, should return nil
|
||
got := GetRemoteAddrIP(ctx)
|
||
// The result depends on how fasthttp initializes the remote addr
|
||
// Just verify it doesn't panic
|
||
_ = got
|
||
}
|
||
|
||
func TestExtractClientIPWithTrustedProxies(t *testing.T) {
|
||
trusted := []net.IPNet{mustParseCIDR(t, "10.0.0.0/8")}
|
||
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.SetRemoteAddr(&net.TCPAddr{IP: net.ParseIP("10.0.0.1")})
|
||
ctx.Request.Header.Set("X-Forwarded-For", "1.2.3.4, 10.0.0.2")
|
||
|
||
ip := ExtractClientIPWithTrustedProxies(ctx, trusted)
|
||
if ip == nil || ip.String() != "1.2.3.4" {
|
||
t.Fatalf("expected 1.2.3.4, got %v", ip)
|
||
}
|
||
|
||
// Untrusted proxy: ignore XFF.
|
||
ctx.SetRemoteAddr(&net.TCPAddr{IP: net.ParseIP("192.168.1.1")})
|
||
ip = ExtractClientIPWithTrustedProxies(ctx, trusted)
|
||
if ip == nil || ip.String() != "192.168.1.1" {
|
||
t.Fatalf("expected 192.168.1.1, got %v", ip)
|
||
}
|
||
}
|
||
|
||
func mustParseCIDR(t *testing.T, s string) net.IPNet {
|
||
t.Helper()
|
||
_, ipNet, err := net.ParseCIDR(s)
|
||
if err != nil {
|
||
t.Fatalf("failed to parse CIDR %s: %v", s, err)
|
||
}
|
||
return *ipNet
|
||
}
|