Golang http client with proxy protocol support

Rajat Jindal

Mar 11th 2021, 09:30AM

The proxy protocol is an internet protocol that is used to carry connection information from the source requesting the connection to the destination for which the connection was requested.

During one of the project, I needed to send traffic directly to the nginx which was running in proxy protocol mode. Now if I sent the request directly, it would not accept the request and throw following error

error connecting to the server, did you send request to the right port

after some research I came across the nice project go-proxyproto which provides a way to format proxy protocol header for consumption in http client.

a simplified example in their repo is:

package main

import (
    "io"
    "log"
    "net"

    proxyproto "github.com/pires/go-proxyproto"
)

func chkErr(err error) {
    if err != nil {
        log.Fatalf("Error: %s", err.Error())
    }
}

func main() {
    // Dial some proxy listener e.g. https://github.com/mailgun/proxyproto
    target, err := net.ResolveTCPAddr("tcp", "127.0.0.1:2319")
    chkErr(err)

    conn, err := net.DialTCP("tcp", nil, target)
    chkErr(err)

    defer conn.Close()

    // Create a proxyprotocol header or use HeaderProxyFromAddrs() if you
    // have two conn's
    header := &proxyproto.Header{
        Version:            1,
        Command:            proxyproto.PROXY,
        TransportProtocol:  proxyproto.TCPv4,
        SourceAddr: &net.TCPAddr{
            IP:   net.ParseIP("10.1.1.1"),
            Port: 1000,
        },
        DestinationAddr: &net.TCPAddr{
            IP:   net.ParseIP("20.2.2.2"),
            Port: 2000,
        },
    }
    // After the connection was created write the proxy headers first
    _, err = header.WriteTo(conn)
    chkErr(err)
    // Then your data... e.g.:
    _, err = io.WriteString(conn, "HELO")
    chkErr(err)
}

Now, for my project, although I have control of http client object, I didn't wanted to go to level of using net.DialTCP or net.ResolveTCPAddr directly in my code.

so I ended up doing following

.
.
.
tr := &http.Transport{}
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
    conn, err := (&net.Dialer{}).Dial(network, addr)
    if err != nil {
        return nil, err
    }

    header := &proxyproto.Header{
        Version: 1,
        Command: proxyproto.PROXY,
        TransportProtocol: proxyproto.TCPv4,
        SourceAddr: conn.LocalAddr(),
        DestinationAddr: conn.RemoteAddr(),
    }

    _, err = header.WriteTo(conn)
    if err != nil {
        return nil, err
    }

    return conn, nil
}

once I ran it, I was able to send traffic directly to nginx successfully.

Conclusion

the nice thing about golang is that you can do as deep as you want to go, and stay as highlevel as you want to. It is easy to try out different things without having to worry about anything else.