Golang http client with proxy protocol support

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

1error 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:

 1package main
 2
 3import (
 4	"io"
 5	"log"
 6	"net"
 7
 8	proxyproto "github.com/pires/go-proxyproto"
 9)
10
11func chkErr(err error) {
12	if err != nil {
13		log.Fatalf("Error: %s", err.Error())
14	}
15}
16
17func main() {
18	// Dial some proxy listener e.g. https://github.com/mailgun/proxyproto
19	target, err := net.ResolveTCPAddr("tcp", "127.0.0.1:2319")
20	chkErr(err)
21
22	conn, err := net.DialTCP("tcp", nil, target)
23	chkErr(err)
24
25	defer conn.Close()
26
27	// Create a proxyprotocol header or use HeaderProxyFromAddrs() if you
28	// have two conn's
29	header := &proxyproto.Header{
30		Version:            1,
31		Command:            proxyproto.PROXY,
32		TransportProtocol:  proxyproto.TCPv4,
33		SourceAddr: &net.TCPAddr{
34			IP:   net.ParseIP("10.1.1.1"),
35			Port: 1000,
36		},
37		DestinationAddr: &net.TCPAddr{
38			IP:   net.ParseIP("20.2.2.2"),
39			Port: 2000,
40		},
41	}
42	// After the connection was created write the proxy headers first
43	_, err = header.WriteTo(conn)
44	chkErr(err)
45	// Then your data... e.g.:
46	_, err = io.WriteString(conn, "HELO")
47	chkErr(err)
48}
49

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

 1.
 2.
 3.
 4tr := &http.Transport{}
 5tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
 6	conn, err := (&net.Dialer{}).Dial(network, addr)
 7	if err != nil {
 8		return nil, err
 9	}
10
11	header := &proxyproto.Header{
12		Version: 1,
13		Command: proxyproto.PROXY,
14		TransportProtocol: proxyproto.TCPv4,
15		SourceAddr: conn.LocalAddr(),
16		DestinationAddr: conn.RemoteAddr(),
17	}
18
19	_, err = header.WriteTo(conn)
20	if err != nil {
21		return nil, err
22	}
23
24	return conn, nil
25}
26

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.