Source File
protocol.go
Belonging Package
github.com/pires/go-proxyproto
package proxyprotoimport ()// readBufferSize is the size used for bufio.Reader's internal buffer.//// This is kept low to reduce per-connection memory overhead. If the header is// larger than readBufferSize, the header will be decoded with multiple Read// calls. For v1 the header length is at most 108 bytes. For v2 the header// length is at most 52 bytes plus the length of the TLVs. We use 256 bytes to// accommodate for the most common cases.const readBufferSize = 256var (// DefaultReadHeaderTimeout is how long header processing waits for header to// be read from the wire, if Listener.ReaderHeaderTimeout is not set.// It's kept as a global variable so to make it easier to find and override,// e.g. go build -ldflags -X "github.com/pires/go-proxyproto.DefaultReadHeaderTimeout=1s".DefaultReadHeaderTimeout = 10 * time.Second// ErrInvalidUpstream should be returned when an upstream connection address// is not trusted, and therefore is invalid.ErrInvalidUpstream = fmt.Errorf("proxyproto: upstream connection address not trusted for PROXY information"))// Listener is used to wrap an underlying listener,// whose connections may be using the HAProxy Proxy Protocol.// If the connection is using the protocol, the RemoteAddr() will return// the correct client address. ReadHeaderTimeout will be applied to all// connections in order to prevent blocking operations. If no ReadHeaderTimeout// is set, a default of 10s will be used. This can be disabled by setting the// timeout to < 0.//// Only one of Policy or ConnPolicy should be provided. If both are provided then// a panic would occur during accept.type Listener struct {// Listener is the underlying listener.Listener net.Listener// Deprecated: use ConnPolicyFunc instead. This will be removed in future release.Policy PolicyFunc// ConnPolicy is the policy function for accepted connections.ConnPolicy ConnPolicyFunc// ValidateHeader is the validator function for the proxy header.ValidateHeader Validator// ReadHeaderTimeout is the timeout for reading the proxy header.ReadHeaderTimeout time.Duration// ReadBufferSize is the read buffer size for accepted connections. When > 0,// each accepted connection uses this size for proxy header detection; 0 means default.ReadBufferSize int}// Conn is used to wrap and underlying connection which// may be speaking the Proxy Protocol. If it is, the RemoteAddr() will// return the address of the client instead of the proxy address. Each connection// will have its own readHeaderTimeout and readDeadline set by the Accept() call.type Conn struct {readDeadline atomic.Value // time.Timeonce sync.OncereadErr errorconn net.ConnbufReader *bufio.Reader// bufferSize is set when the client overrides via WithBufferSize; nil means use default.bufferSize *intheader *HeaderProxyHeaderPolicy PolicyValidate ValidatorreadHeaderTimeout time.Duration}// Validator receives a header and decides whether it is a valid one// In case the header is not deemed valid it should return an error.type Validator func(*Header) error// ValidateHeader adds given validator for proxy headers to a connection when passed as option to NewConn().func ( Validator) func(*Conn) {return func( *Conn) {if != nil {.Validate =}}}// SetReadHeaderTimeout sets the readHeaderTimeout for a connection when passed as option to NewConn().func ( time.Duration) func(*Conn) {return func( *Conn) {if >= 0 {.readHeaderTimeout =}}}// WithBufferSize sets the size of the read buffer used for proxy header detection.// Values <= 0 are ignored and the default (256 bytes) is used. Values < 16 are// effectively 16 due to bufio's minimum. The default is tuned for typical proxy// protocol header lengths.func ( int) func(*Conn) {return func( *Conn) {if <= 0 {return}:= new(int)* =.bufferSize =.bufReader = bufio.NewReaderSize(.conn, )}}// Accept waits for and returns the next valid connection to the listener.func ( *Listener) () (net.Conn, error) {for {// Get the underlying connection., := .Listener.Accept()if != nil {return nil,}:= USEif .Policy != nil && .ConnPolicy != nil {panic("only one of policy or connpolicy must be provided.")}if .Policy != nil || .ConnPolicy != nil {if .Policy != nil {, = .Policy(.RemoteAddr())} else {, = .ConnPolicy(ConnPolicyOptions{Upstream: .RemoteAddr(),Downstream: .LocalAddr(),})}if != nil {// can't decide the policy, we can't accept the connection.if := .Close(); != nil {return nil,}if errors.Is(, ErrInvalidUpstream) {// keep listening for other connections.continue}return nil,}// Handle a connection as a regular one.if == SKIP {return , nil}}:= []func(*Conn){WithPolicy(),ValidateHeader(.ValidateHeader),}if .ReadBufferSize > 0 {= append(, WithBufferSize(.ReadBufferSize))}:= NewConn(, ...)// If the ReadHeaderTimeout for the listener is unset, use the default timeout.if .ReadHeaderTimeout == 0 {.ReadHeaderTimeout = DefaultReadHeaderTimeout}// Set the readHeaderTimeout of the new conn to the value of the listener.readHeaderTimeout = .ReadHeaderTimeoutreturn , nil}}// Close closes the underlying listener.func ( *Listener) () error {return .Listener.Close()}// Addr returns the underlying listener's network address.func ( *Listener) () net.Addr {return .Listener.Addr()}// NewConn is used to wrap a net.Conn that may be speaking the PROXY protocol// into a proxyproto.Conn.//// NOTE: NewConn may interfere with previously set ReadDeadline on the provided net.Conn,// because it sets a temporary deadline when detecting and reading the PROXY protocol header.// If you need to enforce a specific ReadDeadline on the connection, be sure to call Conn.SetReadDeadline// again after NewConn returns, to restore your desired deadline.func ( net.Conn, ...func(*Conn)) *Conn {:= bufio.NewReaderSize(, readBufferSize):= &Conn{bufReader: ,conn: ,}for , := range {()}return}// Read is check for the proxy protocol header when doing// the initial scan. If there is an error parsing the header,// it is returned and the socket is closed.func ( *Conn) ( []byte) (int, error) {// Ensure header processing runs at most once and surface any errors.if := .ensureHeaderProcessed(); != nil {return 0,}// Drain the buffer if it exists and has data.if .bufReader != nil {if .bufReader.Buffered() > 0 {, := .bufReader.Read()// Did we empty the buffer?// Buffering a net.Conn means the buffer doesn't return io.EOF until the connection returns io.EOF.// Therefore, we use Buffered() == 0 to detect if we are done with the buffer.if .bufReader.Buffered() == 0 {// Garbage collect the buffer..bufReader = nil}// Return immediately. Do not touch p.conn.// If err is EOF here, it means the connection is actually closed,// so we should return that error to the user anyway.return ,}// If buffer was empty to begin with (shouldn't happen with the >0 check// but good for safety), clear it..bufReader = nil}// From now on, read directly from the underlying connection.return .conn.Read()}// Write wraps original conn.Write.func ( *Conn) ( []byte) (int, error) {// Ensure header processing has completed before writing.if := .ensureHeaderProcessed(); != nil {return 0,}return .conn.Write()}// Close wraps original conn.Close.func ( *Conn) () error {return .conn.Close()}// ProxyHeader returns the proxy protocol header, if any. If an error occurs// while reading the proxy header, nil is returned.func ( *Conn) () *Header {// Ensure header processing runs at most once._ = .ensureHeaderProcessed()return .header}// LocalAddr returns the address of the server if the proxy// protocol is being used, otherwise just returns the address of// the socket server. In case an error happens on reading the// proxy header the original LocalAddr is returned, not the one// from the proxy header even if the proxy header itself is// syntactically correct.func ( *Conn) () net.Addr {// Ensure header processing runs at most once._ = .ensureHeaderProcessed()if .header == nil || .header.Command.IsLocal() || .readErr != nil {return .conn.LocalAddr()}return .header.DestinationAddr}// RemoteAddr returns the address of the client if the proxy// protocol is being used, otherwise just returns the address of// the socket peer. In case an error happens on reading the// proxy header the original RemoteAddr is returned, not the one// from the proxy header even if the proxy header itself is// syntactically correct.func ( *Conn) () net.Addr {// Ensure header processing runs at most once._ = .ensureHeaderProcessed()if .header == nil || .header.Command.IsLocal() || .readErr != nil {return .conn.RemoteAddr()}return .header.SourceAddr}// Raw returns the underlying connection which can be casted to// a concrete type, allowing access to specialized functions.//// Use this ONLY if you know exactly what you are doing.func ( *Conn) () net.Conn {return .conn}// TCPConn returns the underlying TCP connection,// allowing access to specialized functions.//// Use this ONLY if you know exactly what you are doing.func ( *Conn) () ( *net.TCPConn, bool) {, = .conn.(*net.TCPConn)return}// UnixConn returns the underlying Unix socket connection,// allowing access to specialized functions.//// Use this ONLY if you know exactly what you are doing.func ( *Conn) () ( *net.UnixConn, bool) {, = .conn.(*net.UnixConn)return}// UDPConn returns the underlying UDP connection,// allowing access to specialized functions.//// Use this ONLY if you know exactly what you are doing.func ( *Conn) () ( *net.UDPConn, bool) {, = .conn.(*net.UDPConn)return}// SetDeadline wraps original conn.SetDeadline.func ( *Conn) ( time.Time) error {.readDeadline.Store()return .conn.SetDeadline()}// SetReadDeadline wraps original conn.SetReadDeadline.func ( *Conn) ( time.Time) error {// Set a local var that tells us the desired deadline. This is// needed in order to reset the read deadline to the one that is// desired by the user, rather than an empty deadline..readDeadline.Store()return .conn.SetReadDeadline()}// SetWriteDeadline wraps original conn.SetWriteDeadline.func ( *Conn) ( time.Time) error {return .conn.SetWriteDeadline()}// readHeader reads the proxy protocol header from the connection.func ( *Conn) () error {// If the connection's readHeaderTimeout is more than 0,// apply a temporary deadline without extending a user-configured// deadline. If the user has no deadline, we use now + timeout.if .readHeaderTimeout > 0 {var (time.Timebool)if := .readDeadline.Load(); != nil {= .(time.Time)= !.IsZero()}:= time.Now().Add(.readHeaderTimeout)if && .Before() {// Clamp to the user's earlier deadline to avoid extending it.=}if := .conn.SetReadDeadline(); != nil {return}}, := Read(.bufReader)// If the connection's readHeaderTimeout is more than 0, undo the change to the// deadline that we made above. Because we retain the readDeadline as part of our// SetReadDeadline override, we can restore the user's deadline (if any).// Therefore, we check whether the error is a net.Timeout and if it is, we decide// the proxy proto does not exist and set the error accordingly.if .readHeaderTimeout > 0 {:= .readDeadline.Load()if == nil {= time.Time{}}if := .conn.SetReadDeadline(.(time.Time)); != nil {return}if , := .(net.Error); && .Timeout() {= ErrNoProxyProtocol}}// For the purpose of this wrapper shamefully stolen from armon/go-proxyproto// let's act as if there was no error when PROXY protocol is not present.if == ErrNoProxyProtocol {// but not if it is required that the connection has oneif .ProxyHeaderPolicy == REQUIRE {return}return nil}// proxy protocol header was foundif == nil && != nil {switch .ProxyHeaderPolicy {case REJECT:// this connection is not allowed to send onereturn ErrSuperfluousProxyHeadercase USE, REQUIRE:if .Validate != nil {= .Validate()if != nil {return}}.header =}}return}// ensureHeaderProcessed runs header processing once.func ( *Conn) () error {.once.Do(func() {.readErr = .readHeader()})if .readErr != nil {return .readErr}return nil}// ReadFrom implements the io.ReaderFrom ReadFrom method.func ( *Conn) ( io.Reader) (int64, error) {// Ensure header processing has completed before reading/writing.if := .ensureHeaderProcessed(); != nil {return 0,}if , := .conn.(io.ReaderFrom); {return .ReadFrom()}return io.Copy(.conn, )}// WriteTo implements io.WriterTo.func ( *Conn) ( io.Writer) (int64, error) {// Ensure header processing has completed before reading/writing.if := .ensureHeaderProcessed(); != nil {return 0,}// If the buffer has been drained (or cleared), copy directly from conn.if .bufReader == nil {return io.Copy(, .conn)}:= make([]byte, .bufReader.Buffered())if , := .bufReader.Read(); != nil {return 0, // this should never happen as we read buffered data.}var int64{, := .Write()+= int64()if != nil {return ,}}{, := io.Copy(, .conn)+=if != nil {return ,}}return , nil}
![]() |
The pages are generated with Golds v0.8.7. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |