mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-11 06:18:27 +01:00
Reading the header in Accept let a silent peer stall the listener for everyone and let a malformed header surface as an accept error. The header now parses on first Read/RemoteAddr under a once, inside the connection's own goroutine lifecycle.
103 lines
2.7 KiB
Go
103 lines
2.7 KiB
Go
package util
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// WrapProxyProtocolListener returns a listener whose accepted connections
|
|
// read and strip a HAProxy PROXY protocol v1 header line, exposing the
|
|
// advertised source address as the connection's RemoteAddr. Connections
|
|
// that do not start with a PROXY header keep their real remote address,
|
|
// so direct clients continue to work (and fail any address-based checks
|
|
// against their actual address).
|
|
//
|
|
// The header is parsed lazily on the first Read or RemoteAddr call, inside
|
|
// the connection's own read path; a silent or malformed peer occupies only
|
|
// its own connection, never the accept loop.
|
|
//
|
|
// Only enable this on listeners reachable solely through a trusted proxy:
|
|
// any direct client could otherwise spoof its reported address.
|
|
func WrapProxyProtocolListener(listener net.Listener) net.Listener {
|
|
return &proxyProtocolListener{Listener: listener}
|
|
}
|
|
|
|
type proxyProtocolListener struct {
|
|
net.Listener
|
|
}
|
|
|
|
func (l *proxyProtocolListener) Accept() (net.Conn, error) {
|
|
conn, err := l.Listener.Accept()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &proxyProtocolConn{Conn: conn}, nil
|
|
}
|
|
|
|
type proxyProtocolConn struct {
|
|
net.Conn
|
|
parseOnce sync.Once
|
|
reader *bufio.Reader
|
|
remote net.Addr
|
|
headerErr error
|
|
}
|
|
|
|
func (c *proxyProtocolConn) Read(b []byte) (int, error) {
|
|
c.parse()
|
|
if c.headerErr != nil {
|
|
return 0, c.headerErr
|
|
}
|
|
return c.reader.Read(b)
|
|
}
|
|
|
|
func (c *proxyProtocolConn) RemoteAddr() net.Addr {
|
|
c.parse()
|
|
if c.remote != nil {
|
|
return c.remote
|
|
}
|
|
return c.Conn.RemoteAddr()
|
|
}
|
|
|
|
func (c *proxyProtocolConn) parse() {
|
|
c.parseOnce.Do(func() {
|
|
c.reader = bufio.NewReader(c.Conn)
|
|
head, err := c.reader.Peek(len("PROXY "))
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
c.headerErr = err
|
|
}
|
|
return
|
|
}
|
|
if string(head) != "PROXY " {
|
|
return
|
|
}
|
|
line, err := c.reader.ReadSlice('\n')
|
|
if err != nil {
|
|
// A truncated or overlong header means the peer never sent a
|
|
// parseable line; keep the real address and let the buffered
|
|
// bytes fail the downstream protocol on their own.
|
|
c.discardHeaderLine()
|
|
return
|
|
}
|
|
fields := strings.Fields(strings.TrimRight(string(line), "\r\n"))
|
|
if len(fields) == 6 && fields[0] == "PROXY" && (fields[1] == "TCP4" || fields[1] == "TCP6") {
|
|
if srcIP := net.ParseIP(fields[2]); srcIP != nil {
|
|
if srcPort, err := strconv.Atoi(fields[4]); err == nil {
|
|
c.remote = &net.TCPAddr{IP: srcIP, Port: srcPort}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// discardHeaderLine drops the remainder of a malformed PROXY line so the
|
|
// following bytes still reach the wrapped connection.
|
|
func (c *proxyProtocolConn) discardHeaderLine() {
|
|
discard := make([]byte, c.reader.Buffered())
|
|
_, _ = io.ReadFull(c.reader, discard)
|
|
}
|