ef58909fb7
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).
225 lines
4.5 KiB
Go
225 lines
4.5 KiB
Go
package net
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
|
|
xio "github.com/go-gost/x/internal/io"
|
|
)
|
|
|
|
func TestIsIPv4(t *testing.T) {
|
|
tests := []struct {
|
|
address string
|
|
want bool
|
|
}{
|
|
{"192.168.1.1", true},
|
|
{"10.0.0.1", true},
|
|
{"127.0.0.1:8080", true},
|
|
{"::1", false},
|
|
{"[::1]:8080", false},
|
|
{"", false},
|
|
{":8080", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.address, func(t *testing.T) {
|
|
if got := IsIPv4(tt.address); got != tt.want {
|
|
t.Errorf("IsIPv4(%q) = %v, want %v", tt.address, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// mockConn implements net.Conn for testing
|
|
type mockConn struct {
|
|
net.Conn
|
|
readErr error
|
|
writeErr error
|
|
}
|
|
|
|
func (m *mockConn) Read(p []byte) (int, error) {
|
|
if m.readErr != nil {
|
|
return 0, m.readErr
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
func (m *mockConn) Write(p []byte) (int, error) {
|
|
if m.writeErr != nil {
|
|
return 0, m.writeErr
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func (m *mockConn) Close() error { return nil }
|
|
|
|
type mockAddr struct{}
|
|
|
|
func (m mockAddr) Network() string { return "tcp" }
|
|
func (m mockAddr) String() string { return "127.0.0.1:0" }
|
|
|
|
// mockReadWriteCloser implements io.ReadWriteCloser + CloseRead + CloseWrite
|
|
type mockReadWriteCloser struct {
|
|
data []byte
|
|
readPos int
|
|
writeBuf []byte
|
|
closed bool
|
|
readClosed bool
|
|
writeClosed bool
|
|
readErr error
|
|
writeErr error
|
|
writeReadErr error
|
|
deadlineFunc func() error
|
|
}
|
|
|
|
func (m *mockReadWriteCloser) Read(p []byte) (int, error) {
|
|
if m.readClosed {
|
|
return 0, net.ErrClosed
|
|
}
|
|
if m.readErr != nil {
|
|
return 0, m.readErr
|
|
}
|
|
if m.readPos >= len(m.data) {
|
|
return 0, io.EOF
|
|
}
|
|
n := copy(p, m.data[m.readPos:])
|
|
m.readPos += n
|
|
return n, nil
|
|
}
|
|
|
|
func (m *mockReadWriteCloser) Write(p []byte) (int, error) {
|
|
if m.writeClosed {
|
|
return 0, net.ErrClosed
|
|
}
|
|
if m.writeErr != nil {
|
|
return 0, m.writeErr
|
|
}
|
|
m.writeBuf = append(m.writeBuf, p...)
|
|
return len(p), nil
|
|
}
|
|
|
|
func (m *mockReadWriteCloser) Close() error {
|
|
m.closed = true
|
|
return nil
|
|
}
|
|
|
|
func (m *mockReadWriteCloser) CloseRead() error {
|
|
m.readClosed = true
|
|
return nil
|
|
}
|
|
|
|
func (m *mockReadWriteCloser) CloseWrite() error {
|
|
m.writeClosed = true
|
|
return xio.ErrUnsupported
|
|
}
|
|
|
|
func (m *mockReadWriteCloser) SetReadDeadline(t interface{}) error {
|
|
if m.deadlineFunc != nil {
|
|
return m.deadlineFunc()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestNewReadWriteConn(t *testing.T) {
|
|
r := &mockReadWriteCloser{data: []byte("read data")}
|
|
w := &mockReadWriteCloser{}
|
|
c := &mockConn{}
|
|
|
|
rwc := NewReadWriteConn(r, w, c)
|
|
|
|
// Test Read
|
|
buf := make([]byte, 9)
|
|
n, err := rwc.Read(buf)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if n != 9 || string(buf[:n]) != "read data" {
|
|
t.Errorf("expected 'read data', got %q", string(buf[:n]))
|
|
}
|
|
|
|
// Test Write
|
|
n, err = rwc.Write([]byte("write data"))
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if n != 10 {
|
|
t.Errorf("expected 10 bytes written, got %d", n)
|
|
}
|
|
}
|
|
|
|
func Test_readWriteConn_CloseRead_CloseWrite(t *testing.T) {
|
|
r := &mockReadWriteCloser{}
|
|
w := &mockReadWriteCloser{}
|
|
c := &mockConn{}
|
|
|
|
conn := NewReadWriteConn(r, w, c)
|
|
|
|
if cr, ok := conn.(xio.CloseRead); ok {
|
|
err := cr.CloseRead()
|
|
if err == nil {
|
|
t.Error("expected error when CloseRead on conn without CloseRead")
|
|
}
|
|
} else {
|
|
t.Error("should implement CloseRead")
|
|
}
|
|
|
|
if cw, ok := conn.(xio.CloseWrite); ok {
|
|
err := cw.CloseWrite()
|
|
if err == nil {
|
|
t.Error("expected error when CloseWrite on conn without CloseWrite")
|
|
}
|
|
} else {
|
|
t.Error("should implement CloseWrite")
|
|
}
|
|
}
|
|
|
|
// closeReadWriteConn implements both CloseRead and CloseWrite
|
|
type closeReadWriteConn struct {
|
|
net.Conn
|
|
}
|
|
|
|
func (c *closeReadWriteConn) CloseRead() error { return nil }
|
|
func (c *closeReadWriteConn) CloseWrite() error { return nil }
|
|
func (c *closeReadWriteConn) Close() error { return nil }
|
|
|
|
func Test_readWriteConn_CloseRead_CloseWrite_supported(t *testing.T) {
|
|
r := &mockReadWriteCloser{}
|
|
w := &mockReadWriteCloser{}
|
|
c := &closeReadWriteConn{}
|
|
|
|
conn := NewReadWriteConn(r, w, c)
|
|
|
|
if cr, ok := conn.(xio.CloseRead); ok {
|
|
err := cr.CloseRead()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
} else {
|
|
t.Error("should implement CloseRead")
|
|
}
|
|
|
|
if cw, ok := conn.(xio.CloseWrite); ok {
|
|
err := cw.CloseWrite()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
} else {
|
|
t.Error("should implement CloseWrite")
|
|
}
|
|
}
|
|
|
|
// errReadWriteCloser returns errors on read/write, for error path testing
|
|
type errReadWriteCloser struct {
|
|
*mockReadWriteCloser
|
|
}
|
|
|
|
func (m *errReadWriteCloser) Read(p []byte) (int, error) {
|
|
return 0, errors.New("read error")
|
|
}
|
|
|
|
func (m *errReadWriteCloser) Write(p []byte) (int, error) {
|
|
return 0, errors.New("write error")
|
|
}
|