fix(dialer): correct inverted setMark on FreeBSD/OpenBSD
Fix setMark early-return conditions that were inverted relative to Linux: on FreeBSD (SO_USER_COOKIE) and OpenBSD (SO_RTABLE), a non-zero mark was short-circuited instead of applied, disabling socket marking entirely. Also fix GetClientIP to trim leading whitespace from X-Forwarded-For entries (per RFC 7239), and fix Body.Read to subtract len(b) instead of n from recordSize when a single read exceeds the remaining quota. Add unit tests for internal/net/ (addr, dialer, http, ip, net, pipe, transport, resolve, proxyproto, udp).
This commit is contained in:
@@ -17,7 +17,7 @@ func GetClientIP(req *http.Request) net.IP {
|
||||
if sip == "" {
|
||||
ss := strings.Split(req.Header.Get("X-Forwarded-For"), ",")
|
||||
if len(ss) > 0 && ss[0] != "" {
|
||||
sip = ss[0]
|
||||
sip = strings.TrimSpace(ss[0])
|
||||
}
|
||||
}
|
||||
if sip == "" {
|
||||
@@ -53,7 +53,7 @@ func (p *Body) Read(b []byte) (n int, err error) {
|
||||
b = b[:p.recordSize]
|
||||
}
|
||||
p.buf.Write(b)
|
||||
p.recordSize -= n
|
||||
p.recordSize -= len(b)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetClientIP(t *testing.T) {
|
||||
// nil request
|
||||
if ip := GetClientIP(nil); ip != nil {
|
||||
t.Errorf("expected nil for nil request, got %v", ip)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
headers map[string]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "CF-Connecting-IP takes priority",
|
||||
headers: map[string]string{"CF-Connecting-IP": "1.2.3.4", "X-Forwarded-For": "5.6.7.8", "X-Real-Ip": "9.10.11.12"},
|
||||
expected: "1.2.3.4",
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-For fallback single",
|
||||
headers: map[string]string{"X-Forwarded-For": "10.0.0.1"},
|
||||
expected: "10.0.0.1",
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-For fallback with multiple",
|
||||
headers: map[string]string{"X-Forwarded-For": "10.0.0.1, 10.0.0.2, 10.0.0.3"},
|
||||
expected: "10.0.0.1",
|
||||
},
|
||||
{
|
||||
name: "X-Real-Ip fallback",
|
||||
headers: map[string]string{"X-Real-Ip": "172.16.0.1"},
|
||||
expected: "172.16.0.1",
|
||||
},
|
||||
{
|
||||
name: "no headers",
|
||||
headers: map[string]string{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "empty X-Forwarded-For",
|
||||
headers: map[string]string{"X-Forwarded-For": ""},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-For with leading space",
|
||||
headers: map[string]string{"X-Forwarded-For": " 10.0.0.1, 10.0.0.2"},
|
||||
expected: "10.0.0.1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "http://example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for k, v := range tt.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
got := GetClientIP(req)
|
||||
if tt.expected == "" {
|
||||
if got != nil {
|
||||
t.Errorf("expected nil, got %v", got)
|
||||
}
|
||||
} else {
|
||||
if got == nil || got.String() != tt.expected {
|
||||
t.Errorf("expected %s, got %v", tt.expected, got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBody_Read(t *testing.T) {
|
||||
data := "hello world, this is a test message"
|
||||
reader := io.NopCloser(strings.NewReader(data))
|
||||
|
||||
body := NewBody(reader, 10)
|
||||
buf := make([]byte, 5)
|
||||
|
||||
// First read - 5 bytes
|
||||
n, err := body.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 5 {
|
||||
t.Errorf("expected 5 bytes, got %d", n)
|
||||
}
|
||||
|
||||
// Second read - another 5 bytes (fills record size)
|
||||
buf2 := make([]byte, 5)
|
||||
n, err = body.Read(buf2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 5 {
|
||||
t.Errorf("expected 5 bytes, got %d", n)
|
||||
}
|
||||
|
||||
// Read remaining - record size exceeded, content not recorded
|
||||
buf3 := make([]byte, 100)
|
||||
n, err = body.Read(buf3)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
content := body.Content()
|
||||
if len(content) != 10 {
|
||||
t.Errorf("content capped at 10, got %d bytes", len(content))
|
||||
}
|
||||
|
||||
if body.Length() != int64(len(data)) {
|
||||
t.Errorf("expected length %d, got %d", len(data), body.Length())
|
||||
}
|
||||
|
||||
if err := body.Close(); err != nil {
|
||||
t.Errorf("unexpected error on close: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBody_ReadLargerThanRecordSize(t *testing.T) {
|
||||
// Test the branch where a single read returns more bytes than remaining recordSize
|
||||
data := "hello world!"
|
||||
reader := io.NopCloser(strings.NewReader(data))
|
||||
|
||||
// recordSize=3, but we read in 8-byte chunks → n(8) > recordSize(3)
|
||||
body := NewBody(reader, 3)
|
||||
buf := make([]byte, 8)
|
||||
n, err := body.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 8 {
|
||||
t.Errorf("expected 8 bytes, got %d", n)
|
||||
}
|
||||
// Content should be capped at 3 bytes (recordSize)
|
||||
content := body.Content()
|
||||
if len(content) != 3 {
|
||||
t.Errorf("expected 3 bytes recorded, got %d: %q", len(content), string(content))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBody_NoRecord(t *testing.T) {
|
||||
data := "hello world"
|
||||
reader := io.NopCloser(strings.NewReader(data))
|
||||
|
||||
body := NewBody(reader, 0)
|
||||
buf := make([]byte, 100)
|
||||
n, err := body.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if body.Length() != int64(n) {
|
||||
t.Errorf("expected length %d, got %d", n, body.Length())
|
||||
}
|
||||
|
||||
content := body.Content()
|
||||
if len(content) != 0 {
|
||||
t.Errorf("expected empty content when recordSize=0, got %v", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBody_Content(t *testing.T) {
|
||||
body := &Body{buf: *bytes.NewBufferString("recorded")}
|
||||
content := body.Content()
|
||||
if string(content) != "recorded" {
|
||||
t.Errorf("expected 'recorded', got '%s'", string(content))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBody_Length(t *testing.T) {
|
||||
body := &Body{length: 42}
|
||||
if body.Length() != 42 {
|
||||
t.Errorf("expected 42, got %d", body.Length())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user