Files
coredns/plugin/pkg/proxyproto/tlv_test.go

519 lines
16 KiB
Go
Raw Normal View History

package proxyproto
import (
"bytes"
"io"
"net"
"testing"
"time"
proxyproto "github.com/pires/go-proxyproto"
)
// TestV1HeaderNoTLVs verifies PROXY protocol v1 (which has no TLVs) is handled
// cleanly: capture is a no-op, with no error or panic.
func TestV1HeaderNoTLVs(t *testing.T) {
h := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.10"), Port: 40000},
DestinationAddr: &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 53},
}
if got := rawTLVs(h); got != nil {
t.Fatalf("rawTLVs(v1) = %v, want nil", got)
}
pc := &PacketConn{CaptureTLVs: true}
if got := pc.tlvsFor(h); got != nil {
t.Fatalf("tlvsFor(v1) = %v, want nil", got)
}
if tlvs, err := h.TLVs(); err != nil || len(tlvs) != 0 {
t.Fatalf("v1 TLVs() = %v, %v; want empty, nil", tlvs, err)
}
}
// TestAddrFromConnCommand covers both PROXY and LOCAL commands: PROXY publishes
// the TLVs, LOCAL must not (the PROXY protocol spec requires discarding the
// protocol block for LOCAL, even though go-proxyproto retains the parsed TLVs).
func TestAddrFromConnCommand(t *testing.T) {
tlvs := []proxyproto.TLV{{Type: 0xEA, Value: []byte{0x01, 0x02}}}
tests := []struct {
name string
command proxyproto.ProtocolVersionAndCommand
wantTLV bool
}{
{"PROXY", proxyproto.PROXY, true},
{"LOCAL", proxyproto.LOCAL, false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
h := &proxyproto.Header{
Version: 2,
Command: tc.command,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.10"), Port: 40000},
DestinationAddr: &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 53},
}
if err := h.SetTLVs(tlvs); err != nil {
t.Fatalf("SetTLVs: %v", err)
}
raw, _ := h.Format()
pc := proxyproto.NewConn(&preloadedConn{buf: raw})
defer pc.Close()
a := AddrFromConn(pc)
if tc.wantTLV {
if a == nil || len(a.RawTLVs()) == 0 {
t.Fatalf("AddrFromConn(%s) did not return TLVs", tc.name)
}
} else if a != nil {
t.Fatalf("AddrFromConn(%s) = %v, want nil (TLVs must not be published)", tc.name, a)
}
})
}
}
// fakeListener returns a preset sequence of conns from Accept.
type fakeListener struct {
conns []net.Conn
i int
addr net.Addr
closed bool
}
func (l *fakeListener) Accept() (net.Conn, error) {
if l.i >= len(l.conns) {
return nil, net.ErrClosed
}
c := l.conns[l.i]
l.i++
return c, nil
}
func (l *fakeListener) Close() error { l.closed = true; return nil }
func (l *fakeListener) Addr() net.Addr {
if l.addr != nil {
return l.addr
}
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 53}
}
func TestNewTLVListenerAcceptWrapsProxyConn(t *testing.T) {
src := &net.TCPAddr{IP: net.ParseIP("192.168.1.10"), Port: 40000}
dst := &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 53}
h := &proxyproto.Header{
Version: 2, Command: proxyproto.PROXY, TransportProtocol: proxyproto.TCPv4,
SourceAddr: src, DestinationAddr: dst,
}
if err := h.SetTLVs([]proxyproto.TLV{{Type: 0xEA, Value: []byte{0x01, 0x02}}}); err != nil {
t.Fatalf("SetTLVs: %v", err)
}
raw, _ := h.Format()
pc := proxyproto.NewConn(&preloadedConn{buf: raw})
// Inject a fake inner listener that hands back the *proxyproto.Conn, so we
// test TLVListener.Accept's wrapping without driving the real proxyproto
// Listener's own header/policy handling.
l := &TLVListener{inner: &fakeListener{conns: []net.Conn{pc}}}
defer l.Close()
_ = l.Addr()
c, err := l.Accept()
if err != nil {
t.Fatalf("Accept: %v", err)
}
a, ok := c.RemoteAddr().(*Addr)
if !ok {
t.Fatalf("Accept conn RemoteAddr type = %T, want *Addr", c.RemoteAddr())
}
got, err := proxyproto.SplitTLVs(a.RawTLVs())
if err != nil || len(got) != 1 || !bytes.Equal(got[0].Value, []byte{0x01, 0x02}) {
t.Fatalf("TLV mismatch: %+v err=%v", got, err)
}
c.Close()
}
func TestTLVListenerAcceptPassesThroughNonProxyConn(t *testing.T) {
// A non-*proxyproto.Conn is returned unchanged.
plain := plainRemoteConn{src: &net.TCPAddr{IP: net.ParseIP("198.51.100.1"), Port: 7000}}
l := &TLVListener{inner: &fakeListener{conns: []net.Conn{plain}}}
c, err := l.Accept()
if err != nil {
t.Fatalf("Accept: %v", err)
}
if _, ok := c.RemoteAddr().(*Addr); ok {
t.Fatal("non-proxy conn should pass through without *Addr")
}
}
func TestNewTLVListenerConstructor(t *testing.T) {
// NewTLVListener returns a *TLVListener wrapping a proxyproto.Listener.
base := &fakeListener{addr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9953}}
l := NewTLVListener(base, nil)
if l.Addr().String() != base.Addr().String() {
t.Fatalf("Addr = %s, want %s", l.Addr(), base.Addr())
}
if err := l.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
if !base.closed {
t.Fatal("expected inner listener to be closed")
}
}
func TestNewAddr(t *testing.T) {
u := &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 53}
r := &net.TCPAddr{IP: net.ParseIP("192.168.1.10"), Port: 40000}
a := NewAddr(u, r, []byte{0xaa})
if a.Network() != u.Network() {
t.Errorf("Network = %q, want %q", a.Network(), u.Network())
}
if a.String() != r.String() {
t.Errorf("String = %q, want %q", a.String(), r.String())
}
if !bytes.Equal(a.RawTLVs(), []byte{0xaa}) {
t.Errorf("RawTLVs = %v, want [aa]", a.RawTLVs())
}
}
// tlsLikeConn wraps a net.Conn and exposes NetConn, mimicking *tls.Conn so the
// netConner branch of underlyingProxyConn is exercised.
type tlsLikeConn struct {
net.Conn
inner net.Conn
}
func (c *tlsLikeConn) NetConn() net.Conn { return c.inner }
func TestAddrFromConnUnwrapsTLSLayer(t *testing.T) {
src := &net.TCPAddr{IP: net.ParseIP("192.168.1.10"), Port: 40000}
dst := &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 53}
h := &proxyproto.Header{
Version: 2, Command: proxyproto.PROXY, TransportProtocol: proxyproto.TCPv4,
SourceAddr: src, DestinationAddr: dst,
}
if err := h.SetTLVs([]proxyproto.TLV{{Type: 0xEA, Value: []byte{0x07}}}); err != nil {
t.Fatalf("SetTLVs: %v", err)
}
raw, _ := h.Format()
pc := proxyproto.NewConn(&preloadedConn{buf: raw})
// tlsLike -> *proxyproto.Conn. RemoteAddr on the plain tls-like wrapper does
// NOT promote the proxy header, so AddrFromConn must fall through to the
// NetConn-unwrap path to reach the *proxyproto.Conn.
wrapped := &tlsLikeConn{Conn: plainRemoteConn{src: dst}, inner: pc}
a := AddrFromConn(wrapped)
if a == nil {
t.Fatal("AddrFromConn did not unwrap tls-like layer")
}
got, err := proxyproto.SplitTLVs(a.RawTLVs())
if err != nil || len(got) != 1 || !bytes.Equal(got[0].Value, []byte{0x07}) {
t.Fatalf("TLV mismatch after unwrap: %+v err=%v", got, err)
}
pc.Close()
}
func TestAddrFromConnUnknownConn(t *testing.T) {
// A conn that is neither *proxyproto.Conn, *tlvConn, nor a netConner, and
// whose RemoteAddr is a plain addr: AddrFromConn returns nil.
if a := AddrFromConn(plainRemoteConn{src: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5}}); a != nil {
t.Fatalf("AddrFromConn(unknown) = %v, want nil", a)
}
}
// plainRemoteConn is a net.Conn whose RemoteAddr is a plain *net.TCPAddr.
type plainRemoteConn struct {
net.Conn
src net.Addr
}
func (c plainRemoteConn) RemoteAddr() net.Addr { return c.src }
// preloadedConn is a net.Conn whose Read drains a fixed byte buffer (the PPv2
// header), avoiding net.Pipe synchronization in unit tests.
type preloadedConn struct {
net.Conn
buf []byte
off int
remote net.Addr
}
func (c *preloadedConn) Read(p []byte) (int, error) {
if c.off >= len(c.buf) {
return 0, io.EOF
}
n := copy(p, c.buf[c.off:])
c.off += n
return n, nil
}
func (c *preloadedConn) Write(p []byte) (int, error) { return len(p), nil }
func (c *preloadedConn) Close() error { return nil }
func (c *preloadedConn) RemoteAddr() net.Addr {
if c.remote != nil {
return c.remote
}
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 1}
}
func (c *preloadedConn) LocalAddr() net.Addr {
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 53}
}
func (c *preloadedConn) SetDeadline(time.Time) error { return nil }
func (c *preloadedConn) SetReadDeadline(time.Time) error { return nil }
func (c *preloadedConn) SetWriteDeadline(time.Time) error { return nil }
// ppv2UDPPacketWithTLVs builds a valid PPv2 UDP datagram carrying the given
// TLVs followed by the DNS payload, using the library's own formatter.
func ppv2UDPPacketWithTLVs(t *testing.T, src, dst *net.UDPAddr, tlvs []proxyproto.TLV, payload []byte) []byte {
t.Helper()
h := &proxyproto.Header{
Version: 2,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.UDPv4,
SourceAddr: src,
DestinationAddr: dst,
}
if len(tlvs) > 0 {
if err := h.SetTLVs(tlvs); err != nil {
t.Fatalf("SetTLVs: %v", err)
}
}
raw, err := h.Format()
if err != nil {
t.Fatalf("Format: %v", err)
}
return append(raw, payload...)
}
func usePolicy(proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) {
return proxyproto.USE, nil
}
func TestReadFromCapturesTLVsWhenEnabled(t *testing.T) {
src := udpAddr("192.168.1.10", 40000)
dst := udpAddr("10.0.0.1", 53)
payload := []byte("dns-payload")
tlvs := []proxyproto.TLV{
{Type: proxyproto.PP2_TYPE_AUTHORITY, Value: []byte("example.org")},
{Type: 0xEA, Value: []byte{0x01, 0x02, 0x03}},
}
packet := ppv2UDPPacketWithTLVs(t, src, dst, tlvs, payload)
pc := &PacketConn{PacketConn: &singlePacketConn{}, ConnPolicy: usePolicy, CaptureTLVs: true}
n, addr, err := pc.readFrom(packet, udpAddr("203.0.113.9", 5000))
if err != nil {
t.Fatalf("readFrom: %v", err)
}
if !bytes.Equal(packet[:n], payload) {
t.Fatalf("payload = %q, want %q", packet[:n], payload)
}
a, ok := addr.(*Addr)
if !ok {
t.Fatalf("addr type = %T, want *Addr", addr)
}
raw := a.RawTLVs()
if len(raw) == 0 {
t.Fatal("expected raw TLVs to be captured")
}
// Round-trip: the captured bytes must re-split to the TLVs we sent.
got, err := proxyproto.SplitTLVs(raw)
if err != nil {
t.Fatalf("SplitTLVs: %v", err)
}
if len(got) != len(tlvs) {
t.Fatalf("got %d TLVs, want %d", len(got), len(tlvs))
}
for i, want := range tlvs {
if got[i].Type != want.Type || !bytes.Equal(got[i].Value, want.Value) {
t.Errorf("TLV %d = {%#x,%v}, want {%#x,%v}", i, got[i].Type, got[i].Value, want.Type, want.Value)
}
}
}
func TestReadFromSkipsTLVsWhenDisabled(t *testing.T) {
src := udpAddr("192.168.1.10", 40000)
dst := udpAddr("10.0.0.1", 53)
tlvs := []proxyproto.TLV{{Type: 0xEA, Value: []byte{0x09}}}
packet := ppv2UDPPacketWithTLVs(t, src, dst, tlvs, []byte("x"))
pc := &PacketConn{PacketConn: &singlePacketConn{}, ConnPolicy: usePolicy} // CaptureTLVs defaults to false
_, addr, err := pc.readFrom(packet, udpAddr("203.0.113.9", 5000))
if err != nil {
t.Fatalf("readFrom: %v", err)
}
a, ok := addr.(*Addr)
if !ok {
t.Fatalf("addr type = %T, want *Addr", addr)
}
if a.RawTLVs() != nil {
t.Fatalf("expected no TLVs captured when disabled, got %v", a.RawTLVs())
}
}
func TestReadFromNoTLVsPresent(t *testing.T) {
src := udpAddr("192.168.1.10", 40000)
dst := udpAddr("10.0.0.1", 53)
packet := ppv2UDPPacketWithTLVs(t, src, dst, nil, []byte("x"))
pc := &PacketConn{PacketConn: &singlePacketConn{}, ConnPolicy: usePolicy, CaptureTLVs: true}
_, addr, err := pc.readFrom(packet, udpAddr("203.0.113.9", 5000))
if err != nil {
t.Fatalf("readFrom: %v", err)
}
a, ok := addr.(*Addr)
if !ok {
t.Fatalf("addr type = %T, want *Addr", addr)
}
if a.RawTLVs() != nil {
t.Fatalf("expected nil TLVs when header has none, got %v", a.RawTLVs())
}
}
func TestRawTLVsHelper(t *testing.T) {
// nil header -> nil
if got := rawTLVs(nil); got != nil {
t.Fatalf("rawTLVs(nil) = %v, want nil", got)
}
// header with no TLVs -> nil
h := &proxyproto.Header{Version: 2}
if got := rawTLVs(h); got != nil {
t.Fatalf("rawTLVs(no tlvs) = %v, want nil", got)
}
// header with TLVs -> re-splittable bytes
tlvs := []proxyproto.TLV{{Type: proxyproto.PP2_TYPE_UNIQUE_ID, Value: []byte("abc")}}
if err := h.SetTLVs(tlvs); err != nil {
t.Fatalf("SetTLVs: %v", err)
}
raw := rawTLVs(h)
got, err := proxyproto.SplitTLVs(raw)
if err != nil {
t.Fatalf("SplitTLVs: %v", err)
}
if len(got) != 1 || got[0].Type != proxyproto.PP2_TYPE_UNIQUE_ID || !bytes.Equal(got[0].Value, []byte("abc")) {
t.Fatalf("round-trip mismatch: %+v", got)
}
}
func TestTLVConnRemoteAddrCarriesTLVs(t *testing.T) {
src := &net.TCPAddr{IP: net.ParseIP("192.168.1.10"), Port: 40000}
dst := &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 53}
tlvs := []proxyproto.TLV{{Type: 0xEA, Value: []byte{0xaa, 0xbb}}}
h := &proxyproto.Header{
Version: 2,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: src,
DestinationAddr: dst,
}
if err := h.SetTLVs(tlvs); err != nil {
t.Fatalf("SetTLVs: %v", err)
}
raw, err := h.Format()
if err != nil {
t.Fatalf("Format: %v", err)
}
client, server := net.Pipe()
go func() {
client.Write(raw)
// Keep the pipe open; ProxyHeader only needs the header bytes.
}()
pc := proxyproto.NewConn(server)
tc := &tlvConn{Conn: pc, pc: pc}
addr := tc.RemoteAddr()
a, ok := addr.(*Addr)
if !ok {
t.Fatalf("RemoteAddr type = %T, want *Addr", addr)
}
if a.String() != src.String() {
t.Errorf("RemoteAddr = %s, want %s", a.String(), src)
}
got, err := proxyproto.SplitTLVs(a.RawTLVs())
if err != nil {
t.Fatalf("SplitTLVs: %v", err)
}
if len(got) != 1 || got[0].Type != 0xEA || !bytes.Equal(got[0].Value, []byte{0xaa, 0xbb}) {
t.Fatalf("TLV round-trip mismatch: %+v", got)
}
tc.Close()
}
func TestTLVConnRemoteAddrNoHeader(t *testing.T) {
// A connection with no PROXY header: RemoteAddr falls back to the plain
// address, not an *Addr.
client, server := net.Pipe()
go func() {
client.Write([]byte("not a proxy header"))
}()
pc := proxyproto.NewConn(server)
tc := &tlvConn{Conn: pc, pc: pc}
if _, ok := tc.RemoteAddr().(*Addr); ok {
t.Fatal("expected plain addr when no PROXY header present")
}
tc.Close()
}
// delegatingConn embeds a net.Conn and promotes its RemoteAddr, mimicking the
// wrapper chain (tls.Conn, connection limiter) the DoH path sees.
type delegatingConn struct {
net.Conn
}
func TestAddrFromConnDirect(t *testing.T) {
src := &net.TCPAddr{IP: net.ParseIP("192.168.1.10"), Port: 40000}
dst := &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 53}
h := &proxyproto.Header{
Version: 2, Command: proxyproto.PROXY, TransportProtocol: proxyproto.TCPv4,
SourceAddr: src, DestinationAddr: dst,
}
if err := h.SetTLVs([]proxyproto.TLV{{Type: 0xEA, Value: []byte{0x01}}}); err != nil {
t.Fatalf("SetTLVs: %v", err)
}
raw, _ := h.Format()
client, server := net.Pipe()
go func() { client.Write(raw) }()
pc := proxyproto.NewConn(server)
// Direct *proxyproto.Conn.
if a := AddrFromConn(pc); a == nil || len(a.RawTLVs()) == 0 {
t.Fatal("AddrFromConn(*proxyproto.Conn) did not recover TLVs")
}
pc.Close()
}
func TestAddrFromConnThroughWrapper(t *testing.T) {
src := &net.TCPAddr{IP: net.ParseIP("192.168.1.10"), Port: 40000}
dst := &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 53}
h := &proxyproto.Header{
Version: 2, Command: proxyproto.PROXY, TransportProtocol: proxyproto.TCPv4,
SourceAddr: src, DestinationAddr: dst,
}
if err := h.SetTLVs([]proxyproto.TLV{{Type: 0xEA, Value: []byte{0x02, 0x03}}}); err != nil {
t.Fatalf("SetTLVs: %v", err)
}
raw, _ := h.Format()
client, server := net.Pipe()
go func() { client.Write(raw) }()
pc := proxyproto.NewConn(server)
tc := &tlvConn{Conn: pc, pc: pc}
// Wrap in a conn that only promotes RemoteAddr (like tls.Conn / limiter).
wrapped := &delegatingConn{Conn: tc}
a := AddrFromConn(wrapped)
if a == nil {
t.Fatal("AddrFromConn through wrapper returned nil")
}
got, err := proxyproto.SplitTLVs(a.RawTLVs())
if err != nil || len(got) != 1 || !bytes.Equal(got[0].Value, []byte{0x02, 0x03}) {
t.Fatalf("TLV mismatch through wrapper: %+v err=%v", got, err)
}
tc.Close()
}
func TestAddrFromConnNil(t *testing.T) {
if a := AddrFromConn(nil); a != nil {
t.Fatalf("AddrFromConn(nil) = %v, want nil", a)
}
}