package tcp import ( "bytes" "errors" "fmt" "io" "net" "testing" "time" "github.com/pires/go-proxyproto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v2/pkg/config/dynamic" ) func fakeRedis(t *testing.T, listener net.Listener) { t.Helper() for { conn, err := listener.Accept() fmt.Println("Accept on server") require.NoError(t, err) for { withErr := false buf := make([]byte, 64) if _, err := conn.Read(buf); err != nil { withErr = true } if string(buf[:4]) == "ping" { time.Sleep(1 * time.Millisecond) if _, err := conn.Write([]byte("PONG")); err != nil { _ = conn.Close() return } } if withErr { _ = conn.Close() return } } } } func TestCloseWrite(t *testing.T) { backendListener, err := net.Listen("tcp", ":0") require.NoError(t, err) go fakeRedis(t, backendListener) _, port, err := net.SplitHostPort(backendListener.Addr().String()) require.NoError(t, err) proxy, err := NewProxy(":"+port, 10*time.Millisecond, nil) require.NoError(t, err) proxyListener, err := net.Listen("tcp", ":0") require.NoError(t, err) go func() { for { conn, err := proxyListener.Accept() require.NoError(t, err) proxy.ServeTCP(conn.(*net.TCPConn)) } }() _, port, err = net.SplitHostPort(proxyListener.Addr().String()) require.NoError(t, err) conn, err := net.Dial("tcp", ":"+port) require.NoError(t, err) _, err = conn.Write([]byte("ping\n")) require.NoError(t, err) err = conn.(*net.TCPConn).CloseWrite() require.NoError(t, err) var buf []byte buffer := bytes.NewBuffer(buf) n, err := io.Copy(buffer, conn) require.NoError(t, err) require.Equal(t, int64(4), n) require.Equal(t, "PONG", buffer.String()) } func TestProxyProtocol(t *testing.T) { testCases := []struct { desc string version int }{ { desc: "PROXY protocol v1", version: 1, }, { desc: "PROXY protocol v2", version: 2, }, } for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { backendListener, err := net.Listen("tcp", ":0") require.NoError(t, err) var version int proxyBackendListener := proxyproto.Listener{ Listener: backendListener, ValidateHeader: func(h *proxyproto.Header) error { version = int(h.Version) return nil }, Policy: func(upstream net.Addr) (proxyproto.Policy, error) { switch test.version { case 1, 2: return proxyproto.USE, nil default: return proxyproto.REQUIRE, errors.New("unsupported version") } }, } defer proxyBackendListener.Close() go fakeRedis(t, &proxyBackendListener) _, port, err := net.SplitHostPort(proxyBackendListener.Addr().String()) require.NoError(t, err) proxy, err := NewProxy(":"+port, 10*time.Millisecond, &dynamic.ProxyProtocol{Version: test.version}) require.NoError(t, err) proxyListener, err := net.Listen("tcp", ":0") require.NoError(t, err) go func() { for { conn, err := proxyListener.Accept() require.NoError(t, err) proxy.ServeTCP(conn.(*net.TCPConn)) } }() _, port, err = net.SplitHostPort(proxyListener.Addr().String()) require.NoError(t, err) conn, err := net.Dial("tcp", ":"+port) require.NoError(t, err) _, err = conn.Write([]byte("ping\n")) require.NoError(t, err) err = conn.(*net.TCPConn).CloseWrite() require.NoError(t, err) var buf []byte buffer := bytes.NewBuffer(buf) n, err := io.Copy(buffer, conn) require.NoError(t, err) assert.Equal(t, int64(4), n) assert.Equal(t, "PONG", buffer.String()) assert.Equal(t, test.version, version) }) } } func TestLookupAddress(t *testing.T) { testCases := []struct { desc string address string expectAddr assert.ComparisonAssertionFunc expectRefresh assert.ValueAssertionFunc }{ { desc: "IP doesn't need refresh", address: "8.8.4.4:53", expectAddr: assert.Equal, expectRefresh: assert.NotNil, }, { desc: "Hostname needs refresh", address: "dns.google:53", expectAddr: assert.NotEqual, expectRefresh: assert.Nil, }, } for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() proxy, err := NewProxy(test.address, 10*time.Millisecond, nil) require.NoError(t, err) test.expectRefresh(t, proxy.tcpAddr) conn, err := proxy.dialBackend() require.NoError(t, err) test.expectAddr(t, test.address, conn.RemoteAddr().String()) }) } }