mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
505 lines
16 KiB
505 lines
16 KiB
// Copyright 2020 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package proxyprotocol |
|
|
|
import ( |
|
"bufio" |
|
"bytes" |
|
"encoding/binary" |
|
"io" |
|
"net" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
"time" |
|
|
|
"code.gitea.io/gitea/modules/log" |
|
) |
|
|
|
var ( |
|
// v1Prefix is the string we look for at the start of a connection |
|
// to check if this connection is using the proxy protocol |
|
v1Prefix = []byte("PROXY ") |
|
v1PrefixLen = len(v1Prefix) |
|
v2Prefix = []byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A") |
|
v2PrefixLen = len(v2Prefix) |
|
) |
|
|
|
// Conn is used to wrap and underlying connection which is speaking the |
|
// Proxy Protocol. RemoteAddr() will return the address of the client |
|
// instead of the proxy address. |
|
type Conn struct { |
|
bufReader *bufio.Reader |
|
conn net.Conn |
|
localAddr net.Addr |
|
remoteAddr net.Addr |
|
once sync.Once |
|
proxyHeaderTimeout time.Duration |
|
acceptUnknown bool |
|
} |
|
|
|
// NewConn is used to wrap a net.Conn speaking the proxy protocol into |
|
// a proxyprotocol.Conn |
|
func NewConn(conn net.Conn, timeout time.Duration) *Conn { |
|
pConn := &Conn{ |
|
bufReader: bufio.NewReader(conn), |
|
conn: conn, |
|
proxyHeaderTimeout: timeout, |
|
} |
|
return pConn |
|
} |
|
|
|
// Read reads data from the connection. |
|
// It will initially read the proxy protocol header. |
|
// If there is an error parsing the header, it is returned and the socket is closed. |
|
func (p *Conn) Read(b []byte) (int, error) { |
|
if err := p.readProxyHeaderOnce(); err != nil { |
|
return 0, err |
|
} |
|
return p.bufReader.Read(b) |
|
} |
|
|
|
// ReadFrom reads data from a provided reader and copies it to the connection. |
|
func (p *Conn) ReadFrom(r io.Reader) (int64, error) { |
|
if err := p.readProxyHeaderOnce(); err != nil { |
|
return 0, err |
|
} |
|
if rf, ok := p.conn.(io.ReaderFrom); ok { |
|
return rf.ReadFrom(r) |
|
} |
|
return io.Copy(p.conn, r) |
|
} |
|
|
|
// WriteTo reads data from the connection and writes it to the writer. |
|
// It will initially read the proxy protocol header. |
|
// If there is an error parsing the header, it is returned and the socket is closed. |
|
func (p *Conn) WriteTo(w io.Writer) (int64, error) { |
|
if err := p.readProxyHeaderOnce(); err != nil { |
|
return 0, err |
|
} |
|
return p.bufReader.WriteTo(w) |
|
} |
|
|
|
// Write writes data to the connection. |
|
// Write can be made to time out and return an error after a fixed |
|
// time limit; see SetDeadline and SetWriteDeadline. |
|
func (p *Conn) Write(b []byte) (int, error) { |
|
if err := p.readProxyHeaderOnce(); err != nil { |
|
return 0, err |
|
} |
|
return p.conn.Write(b) |
|
} |
|
|
|
// Close closes the connection. |
|
// Any blocked Read or Write operations will be unblocked and return errors. |
|
func (p *Conn) Close() error { |
|
return p.conn.Close() |
|
} |
|
|
|
// LocalAddr returns the local network address. |
|
func (p *Conn) LocalAddr() net.Addr { |
|
_ = p.readProxyHeaderOnce() |
|
if p.localAddr != nil { |
|
return p.localAddr |
|
} |
|
return p.conn.LocalAddr() |
|
} |
|
|
|
// RemoteAddr returns the address of the client if the proxy |
|
// protocol is being used, otherwise just returns the address of |
|
// the socket peer. If there is an error parsing the header, the |
|
// address of the client is not returned, and the socket is closed. |
|
// One implication of this is that the call could block if the |
|
// client is slow. Using a Deadline is recommended if this is called |
|
// before Read() |
|
func (p *Conn) RemoteAddr() net.Addr { |
|
_ = p.readProxyHeaderOnce() |
|
if p.remoteAddr != nil { |
|
return p.remoteAddr |
|
} |
|
return p.conn.RemoteAddr() |
|
} |
|
|
|
// SetDeadline sets the read and write deadlines associated |
|
// with the connection. It is equivalent to calling both |
|
// SetReadDeadline and SetWriteDeadline. |
|
// |
|
// A deadline is an absolute time after which I/O operations |
|
// fail instead of blocking. The deadline applies to all future |
|
// and pending I/O, not just the immediately following call to |
|
// Read or Write. After a deadline has been exceeded, the |
|
// connection can be refreshed by setting a deadline in the future. |
|
// |
|
// If the deadline is exceeded a call to Read or Write or to other |
|
// I/O methods will return an error that wraps os.ErrDeadlineExceeded. |
|
// This can be tested using errors.Is(err, os.ErrDeadlineExceeded). |
|
// The error's Timeout method will return true, but note that there |
|
// are other possible errors for which the Timeout method will |
|
// return true even if the deadline has not been exceeded. |
|
// |
|
// An idle timeout can be implemented by repeatedly extending |
|
// the deadline after successful Read or Write calls. |
|
// |
|
// A zero value for t means I/O operations will not time out. |
|
func (p *Conn) SetDeadline(t time.Time) error { |
|
return p.conn.SetDeadline(t) |
|
} |
|
|
|
// SetReadDeadline sets the deadline for future Read calls |
|
// and any currently-blocked Read call. |
|
// A zero value for t means Read will not time out. |
|
func (p *Conn) SetReadDeadline(t time.Time) error { |
|
return p.conn.SetReadDeadline(t) |
|
} |
|
|
|
// SetWriteDeadline sets the deadline for future Write calls |
|
// and any currently-blocked Write call. |
|
// Even if write times out, it may return n > 0, indicating that |
|
// some of the data was successfully written. |
|
// A zero value for t means Write will not time out. |
|
func (p *Conn) SetWriteDeadline(t time.Time) error { |
|
return p.conn.SetWriteDeadline(t) |
|
} |
|
|
|
// readProxyHeaderOnce will ensure that the proxy header has been read |
|
func (p *Conn) readProxyHeaderOnce() (err error) { |
|
p.once.Do(func() { |
|
if err = p.readProxyHeader(); err != nil && err != io.EOF { |
|
log.Error("Failed to read proxy prefix: %v", err) |
|
p.Close() |
|
p.bufReader = bufio.NewReader(p.conn) |
|
} |
|
}) |
|
return err |
|
} |
|
|
|
func (p *Conn) readProxyHeader() error { |
|
if p.proxyHeaderTimeout != 0 { |
|
readDeadLine := time.Now().Add(p.proxyHeaderTimeout) |
|
_ = p.conn.SetReadDeadline(readDeadLine) |
|
defer func() { |
|
_ = p.conn.SetReadDeadline(time.Time{}) |
|
}() |
|
} |
|
|
|
inp, err := p.bufReader.Peek(v1PrefixLen) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if bytes.Equal(inp, v1Prefix) { |
|
return p.readV1ProxyHeader() |
|
} |
|
|
|
inp, err = p.bufReader.Peek(v2PrefixLen) |
|
if err != nil { |
|
return err |
|
} |
|
if bytes.Equal(inp, v2Prefix) { |
|
return p.readV2ProxyHeader() |
|
} |
|
|
|
return &ErrBadHeader{inp} |
|
} |
|
|
|
func (p *Conn) readV2ProxyHeader() error { |
|
// The binary header format starts with a constant 12 bytes block containing the |
|
// protocol signature : |
|
// |
|
// \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A |
|
// |
|
// Note that this block contains a null byte at the 5th position, so it must not |
|
// be handled as a null-terminated string. |
|
|
|
if _, err := p.bufReader.Discard(v2PrefixLen); err != nil { |
|
// This shouldn't happen as we have already asserted that there should be enough in the buffer |
|
return err |
|
} |
|
|
|
// The next byte (the 13th one) is the protocol version and command. |
|
version, err := p.bufReader.ReadByte() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// The 14th byte contains the transport protocol and address family.otocol. |
|
familyByte, err := p.bufReader.ReadByte() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// The 15th and 16th bytes is the address length in bytes in network endian order. |
|
var addressLen uint16 |
|
if err := binary.Read(p.bufReader, binary.BigEndian, &addressLen); err != nil { |
|
return err |
|
} |
|
|
|
// Now handle the version byte: (14th byte). |
|
// The highest four bits contains the version. As of this specification, it must |
|
// always be sent as \x2 and the receiver must only accept this value. |
|
if version>>4 != 0x2 { |
|
return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} |
|
} |
|
|
|
// The lowest four bits represents the command : |
|
switch version & 0xf { |
|
case 0x0: |
|
// - \x0 : LOCAL : the connection was established on purpose by the proxy |
|
// without being relayed. The connection endpoints are the sender and the |
|
// receiver. Such connections exist when the proxy sends health-checks to the |
|
// server. The receiver must accept this connection as valid and must use the |
|
// real connection endpoints and discard the protocol block including the |
|
// family which is ignored. |
|
|
|
// We therefore ignore the 14th, 15th and 16th bytes |
|
p.remoteAddr = p.conn.LocalAddr() |
|
p.localAddr = p.conn.RemoteAddr() |
|
return nil |
|
case 0x1: |
|
// - \x1 : PROXY : the connection was established on behalf of another node, |
|
// and reflects the original connection endpoints. The receiver must then use |
|
// the information provided in the protocol block to get original the address. |
|
default: |
|
// - other values are unassigned and must not be emitted by senders. Receivers |
|
// must drop connections presenting unexpected values here. |
|
return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} |
|
} |
|
|
|
// Now handle the familyByte byte: (15th byte). |
|
// The highest 4 bits contain the address family, the lowest 4 bits contain the protocol |
|
|
|
// The address family maps to the original socket family without necessarily |
|
// matching the values internally used by the system. It may be one of : |
|
// |
|
// - 0x0 : AF_UNSPEC : the connection is forwarded for an unknown, unspecified |
|
// or unsupported protocol. The sender should use this family when sending |
|
// LOCAL commands or when dealing with unsupported protocol families. The |
|
// receiver is free to accept the connection anyway and use the real endpoint |
|
// addresses or to reject it. The receiver should ignore address information. |
|
// |
|
// - 0x1 : AF_INET : the forwarded connection uses the AF_INET address family |
|
// (IPv4). The addresses are exactly 4 bytes each in network byte order, |
|
// followed by transport protocol information (typically ports). |
|
// |
|
// - 0x2 : AF_INET6 : the forwarded connection uses the AF_INET6 address family |
|
// (IPv6). The addresses are exactly 16 bytes each in network byte order, |
|
// followed by transport protocol information (typically ports). |
|
// |
|
// - 0x3 : AF_UNIX : the forwarded connection uses the AF_UNIX address family |
|
// (UNIX). The addresses are exactly 108 bytes each. |
|
// |
|
// - other values are unspecified and must not be emitted in version 2 of this |
|
// protocol and must be rejected as invalid by receivers. |
|
|
|
// The transport protocol is specified in the lowest 4 bits of the 14th byte : |
|
// |
|
// - 0x0 : UNSPEC : the connection is forwarded for an unknown, unspecified |
|
// or unsupported protocol. The sender should use this family when sending |
|
// LOCAL commands or when dealing with unsupported protocol families. The |
|
// receiver is free to accept the connection anyway and use the real endpoint |
|
// addresses or to reject it. The receiver should ignore address information. |
|
// |
|
// - 0x1 : STREAM : the forwarded connection uses a SOCK_STREAM protocol (eg: |
|
// TCP or UNIX_STREAM). When used with AF_INET/AF_INET6 (TCP), the addresses |
|
// are followed by the source and destination ports represented on 2 bytes |
|
// each in network byte order. |
|
// |
|
// - 0x2 : DGRAM : the forwarded connection uses a SOCK_DGRAM protocol (eg: |
|
// UDP or UNIX_DGRAM). When used with AF_INET/AF_INET6 (UDP), the addresses |
|
// are followed by the source and destination ports represented on 2 bytes |
|
// each in network byte order. |
|
// |
|
// - other values are unspecified and must not be emitted in version 2 of this |
|
// protocol and must be rejected as invalid by receivers. |
|
|
|
if familyByte>>4 == 0x0 || familyByte&0xf == 0x0 { |
|
// - hi 0x0 : AF_UNSPEC : the connection is forwarded for an unknown address type |
|
// or |
|
// - lo 0x0 : UNSPEC : the connection is forwarded for an unspecified protocol |
|
if !p.acceptUnknown { |
|
p.conn.Close() |
|
return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} |
|
} |
|
p.remoteAddr = p.conn.LocalAddr() |
|
p.localAddr = p.conn.RemoteAddr() |
|
_, err = p.bufReader.Discard(int(addressLen)) |
|
return err |
|
} |
|
|
|
// other address or protocol |
|
if (familyByte>>4) > 0x3 || (familyByte&0xf) > 0x2 { |
|
return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} |
|
} |
|
|
|
// Handle AF_UNIX addresses |
|
if familyByte>>4 == 0x3 { |
|
// - \x31 : UNIX stream : the forwarded connection uses SOCK_STREAM over the |
|
// AF_UNIX protocol family. Address length is 2*108 = 216 bytes. |
|
// - \x32 : UNIX datagram : the forwarded connection uses SOCK_DGRAM over the |
|
// AF_UNIX protocol family. Address length is 2*108 = 216 bytes. |
|
if addressLen != 216 { |
|
return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} |
|
} |
|
remoteName := make([]byte, 108) |
|
localName := make([]byte, 108) |
|
if _, err := p.bufReader.Read(remoteName); err != nil { |
|
return err |
|
} |
|
if _, err := p.bufReader.Read(localName); err != nil { |
|
return err |
|
} |
|
protocol := "unix" |
|
if familyByte&0xf == 2 { |
|
protocol = "unixgram" |
|
} |
|
|
|
p.remoteAddr = &net.UnixAddr{ |
|
Name: string(remoteName), |
|
Net: protocol, |
|
} |
|
p.localAddr = &net.UnixAddr{ |
|
Name: string(localName), |
|
Net: protocol, |
|
} |
|
return nil |
|
} |
|
|
|
var remoteIP []byte |
|
var localIP []byte |
|
var remotePort uint16 |
|
var localPort uint16 |
|
|
|
if familyByte>>4 == 0x1 { |
|
// AF_INET |
|
// - \x11 : TCP over IPv4 : the forwarded connection uses TCP over the AF_INET |
|
// protocol family. Address length is 2*4 + 2*2 = 12 bytes. |
|
// - \x12 : UDP over IPv4 : the forwarded connection uses UDP over the AF_INET |
|
// protocol family. Address length is 2*4 + 2*2 = 12 bytes. |
|
if addressLen != 12 { |
|
return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} |
|
} |
|
|
|
remoteIP = make([]byte, 4) |
|
localIP = make([]byte, 4) |
|
} else { |
|
// AF_INET6 |
|
// - \x21 : TCP over IPv6 : the forwarded connection uses TCP over the AF_INET6 |
|
// protocol family. Address length is 2*16 + 2*2 = 36 bytes. |
|
// - \x22 : UDP over IPv6 : the forwarded connection uses UDP over the AF_INET6 |
|
// protocol family. Address length is 2*16 + 2*2 = 36 bytes. |
|
if addressLen != 36 { |
|
return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} |
|
} |
|
|
|
remoteIP = make([]byte, 16) |
|
localIP = make([]byte, 16) |
|
} |
|
|
|
if _, err := p.bufReader.Read(remoteIP); err != nil { |
|
return err |
|
} |
|
if _, err := p.bufReader.Read(localIP); err != nil { |
|
return err |
|
} |
|
if err := binary.Read(p.bufReader, binary.BigEndian, &remotePort); err != nil { |
|
return err |
|
} |
|
if err := binary.Read(p.bufReader, binary.BigEndian, &localPort); err != nil { |
|
return err |
|
} |
|
|
|
if familyByte&0xf == 1 { |
|
p.remoteAddr = &net.TCPAddr{ |
|
IP: remoteIP, |
|
Port: int(remotePort), |
|
} |
|
p.localAddr = &net.TCPAddr{ |
|
IP: localIP, |
|
Port: int(localPort), |
|
} |
|
} else { |
|
p.remoteAddr = &net.UDPAddr{ |
|
IP: remoteIP, |
|
Port: int(remotePort), |
|
} |
|
p.localAddr = &net.UDPAddr{ |
|
IP: localIP, |
|
Port: int(localPort), |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func (p *Conn) readV1ProxyHeader() error { |
|
// Read until a newline |
|
header, err := p.bufReader.ReadString('\n') |
|
if err != nil { |
|
p.conn.Close() |
|
return err |
|
} |
|
|
|
if header[len(header)-2] != '\r' { |
|
return &ErrBadHeader{[]byte(header)} |
|
} |
|
|
|
// Strip the carriage return and new line |
|
header = header[:len(header)-2] |
|
|
|
// Split on spaces, should be (PROXY <type> <remote addr> <local addr> <remote port> <local port>) |
|
parts := strings.Split(header, " ") |
|
if len(parts) < 2 { |
|
p.conn.Close() |
|
return &ErrBadHeader{[]byte(header)} |
|
} |
|
|
|
// Verify the type is known |
|
switch parts[1] { |
|
case "UNKNOWN": |
|
if !p.acceptUnknown || len(parts) != 2 { |
|
p.conn.Close() |
|
return &ErrBadHeader{[]byte(header)} |
|
} |
|
p.remoteAddr = p.conn.LocalAddr() |
|
p.localAddr = p.conn.RemoteAddr() |
|
return nil |
|
case "TCP4": |
|
case "TCP6": |
|
default: |
|
p.conn.Close() |
|
return &ErrBadAddressType{parts[1]} |
|
} |
|
|
|
if len(parts) != 6 { |
|
p.conn.Close() |
|
return &ErrBadHeader{[]byte(header)} |
|
} |
|
|
|
// Parse out the remote address |
|
ip := net.ParseIP(parts[2]) |
|
if ip == nil { |
|
p.conn.Close() |
|
return &ErrBadRemote{parts[2], parts[4]} |
|
} |
|
port, err := strconv.Atoi(parts[4]) |
|
if err != nil { |
|
p.conn.Close() |
|
return &ErrBadRemote{parts[2], parts[4]} |
|
} |
|
p.remoteAddr = &net.TCPAddr{IP: ip, Port: port} |
|
|
|
// Parse out the destination address |
|
ip = net.ParseIP(parts[3]) |
|
if ip == nil { |
|
p.conn.Close() |
|
return &ErrBadLocal{parts[3], parts[5]} |
|
} |
|
port, err = strconv.Atoi(parts[5]) |
|
if err != nil { |
|
p.conn.Close() |
|
return &ErrBadLocal{parts[3], parts[5]} |
|
} |
|
p.localAddr = &net.TCPAddr{IP: ip, Port: port} |
|
|
|
return nil |
|
}
|
|
|