pires / go-proxyproto

A Go library implementation of the PROXY protocol, versions 1 and 2.
Apache License 2.0
478 stars 107 forks source link

Suggestion: Decouple Header Reading From net.Conn #111

Open adrianosela opened 2 months ago

adrianosela commented 2 months ago

Having a sync.Once be checked in each net.Conn operation (i.e. on every Read()) seems quite inefficient. I suggest enqueuing accepted connections from the inner net.Listener's Accept(), processing/reading the header asynchronously, and having the publicly exposed net.Listener return the already-processed net.Conns...

Here's some pseudocode to illustrate what I mean:

import "net"

type listener struct {
    inner net.Listener

    in  chan net.Conn
    out chan net.Conn
}

// function that reads the proxy protocol header and returns a net.Conn
// implementation which returns the right address on RemoteAddr()
func processFunc(in net.Conn) net.Conn {
    // assume implemented
}

func NewListener(inner net.Listener) net.Listener {
    wrapped := &listener{
        inner: inner,
        in:    make(chan net.Conn, 1000),
        out:   make(chan net.Conn, 1000),
    }

    // kick-off go routine to process newly accepted connections
    go func() {
        for conn := range wrapped.in {
            conn := conn // avoid address capture
            go func() {
                processed := processFunc(conn)
                if processed != nil {
                    wrapped.out <- processed
                }
            }()
        }
    }()

    // kick-off go routine to accept new connections
    go func() {
        for {
            conn, err := inner.Accept()
            if err != nil {
                return // accept errors are not recoverable
            }
            wrapped.in <- conn
        }
    }()

    return wrapped
}

func (l *listener) Accept() (net.Conn, error) {
    return <-l.out, nil
}

func (l *listener) Close() error {
    return l.inner.Close()
}

func (l *listener) Addr() net.Addr {
    return l.inner.Addr()
}

If there's interest in this I would love to submit a PR... seems indeed more efficient!