Browse Source

Merge pull request #1391 from slingamn/proxy.3

fix #1389
tags/v2.5.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
f30bd2cff0
No account linked to committer's email address
6 changed files with 151 additions and 39 deletions
  1. 5
    4
      default.yaml
  2. 3
    2
      irc/config.go
  3. 3
    1
      irc/gateways.go
  4. 1
    1
      irc/server.go
  5. 134
    27
      irc/utils/proxy.go
  6. 5
    4
      traditional.yaml

+ 5
- 4
default.yaml View File

@@ -52,10 +52,11 @@ server:
52 52
             tls:
53 53
                 cert: fullchain.pem
54 54
                 key: privkey.pem
55
-                # 'proxy' should typically be false. It's only for Kubernetes-style load
56
-                # balancing that does not terminate TLS, but sends an initial PROXY line
57
-                # in plaintext.
58
-                proxy: false
55
+            # 'proxy' should typically be false. It's for cloud load balancers that
56
+            # always send PROXY headers ahead of the connection (e.g., a v1 header
57
+            # ahead of unterminated TLS, or a v2 binary header) that MUST be present
58
+            # and cannot be processed on an optional basis.
59
+            proxy: false
59 60
 
60 61
         # Example of a Unix domain socket for proxying:
61 62
         # "/tmp/oragono_sock":

+ 3
- 2
irc/config.go View File

@@ -49,12 +49,13 @@ import (
49 49
 type TLSListenConfig struct {
50 50
 	Cert  string
51 51
 	Key   string
52
-	Proxy bool
52
+	Proxy bool // XXX: legacy key: it's preferred to specify this directly in listenerConfigBlock
53 53
 }
54 54
 
55 55
 // This is the YAML-deserializable type of the value of the `Server.Listeners` map
56 56
 type listenerConfigBlock struct {
57 57
 	TLS       TLSListenConfig
58
+	Proxy     bool
58 59
 	Tor       bool
59 60
 	STSOnly   bool `yaml:"sts-only"`
60 61
 	WebSocket bool
@@ -829,8 +830,8 @@ func (conf *Config) prepareListeners() (err error) {
829 830
 				return err
830 831
 			}
831 832
 			lconf.TLSConfig = tlsConfig
832
-			lconf.RequireProxy = block.TLS.Proxy
833 833
 		}
834
+		lconf.RequireProxy = block.TLS.Proxy || block.Proxy
834 835
 		lconf.WebSocket = block.WebSocket
835 836
 		conf.Server.trueListeners[addr] = lconf
836 837
 	}

+ 3
- 1
irc/gateways.go View File

@@ -121,9 +121,11 @@ func handleProxyCommand(server *Server, client *Client, session *Session, line s
121 121
 		}
122 122
 	}()
123 123
 
124
-	ip, err := utils.ParseProxyLine(line)
124
+	ip, err := utils.ParseProxyLineV1(line)
125 125
 	if err != nil {
126 126
 		return err
127
+	} else if ip == nil {
128
+		return nil
127 129
 	}
128 130
 
129 131
 	if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {

+ 1
- 1
irc/server.go View File

@@ -747,7 +747,7 @@ func (server *Server) loadFromDatastore(config *Config) (err error) {
747 747
 func (server *Server) setupListeners(config *Config) (err error) {
748 748
 	logListener := func(addr string, config utils.ListenerConfig) {
749 749
 		server.logger.Info("listeners",
750
-			fmt.Sprintf("now listening on %s, tls=%t, tlsproxy=%t, tor=%t, websocket=%t.", addr, (config.TLSConfig != nil), config.RequireProxy, config.Tor, config.WebSocket),
750
+			fmt.Sprintf("now listening on %s, tls=%t, proxy=%t, tor=%t, websocket=%t.", addr, (config.TLSConfig != nil), config.RequireProxy, config.Tor, config.WebSocket),
751 751
 		)
752 752
 	}
753 753
 

+ 134
- 27
irc/utils/proxy.go View File

@@ -5,7 +5,9 @@ package utils
5 5
 
6 6
 import (
7 7
 	"crypto/tls"
8
+	"encoding/binary"
8 9
 	"errors"
10
+	"io"
9 11
 	"net"
10 12
 	"strings"
11 13
 	"sync"
@@ -18,7 +20,7 @@ const (
18 20
 	// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
19 21
 	// "a 108-byte buffer is always enough to store all the line and a trailing zero
20 22
 	// for string processing."
21
-	maxProxyLineLen = 107
23
+	maxProxyLineLenV1 = 107
22 24
 )
23 25
 
24 26
 // XXX implement net.Error with a Temporary() method that returns true;
@@ -56,41 +58,88 @@ type ListenerConfig struct {
56 58
 	WebSocket bool
57 59
 }
58 60
 
59
-// read a PROXY line one byte at a time, to ensure we don't read anything beyond
60
-// that into a buffer, which would break the TLS handshake
61
-func readRawProxyLine(conn net.Conn, deadline time.Duration) (result string) {
61
+// read a PROXY header (either v1 or v2), ensuring we don't read anything beyond
62
+// the header into a buffer (this would break the TLS handshake)
63
+func readRawProxyLine(conn net.Conn, deadline time.Duration) (result []byte, err error) {
62 64
 	// normally this is covered by ping timeouts, but we're doing this outside
63 65
 	// of the normal client goroutine:
64 66
 	conn.SetDeadline(time.Now().Add(deadline))
65 67
 	defer conn.SetDeadline(time.Time{})
66 68
 
67
-	var buf [maxProxyLineLen]byte
68
-	oneByte := make([]byte, 1)
69
-	i := 0
70
-	for i < maxProxyLineLen {
71
-		n, err := conn.Read(oneByte)
69
+	// read the first 16 bytes of the proxy header
70
+	buf := make([]byte, 16, maxProxyLineLenV1)
71
+	_, err = io.ReadFull(conn, buf)
72
+	if err != nil {
73
+		return
74
+	}
75
+
76
+	switch buf[0] {
77
+	case 'P':
78
+		// PROXY v1: starts with "PROXY"
79
+		return readRawProxyLineV1(conn, buf)
80
+	case '\r':
81
+		// PROXY v2: starts with "\r\n\r\n"
82
+		return readRawProxyLineV2(conn, buf)
83
+	default:
84
+		return nil, ErrBadProxyLine
85
+	}
86
+}
87
+
88
+func readRawProxyLineV1(conn net.Conn, buf []byte) (result []byte, err error) {
89
+	for {
90
+		i := len(buf)
91
+		if i >= maxProxyLineLenV1 {
92
+			return nil, ErrBadProxyLine // did not find \r\n, fail
93
+		}
94
+		// prepare a single byte of free space, then read into it
95
+		buf = buf[0 : i+1]
96
+		_, err = io.ReadFull(conn, buf[i:])
72 97
 		if err != nil {
73
-			return
74
-		} else if n == 1 {
75
-			buf[i] = oneByte[0]
76
-			if buf[i] == '\n' {
77
-				candidate := string(buf[0 : i+1])
78
-				if strings.HasPrefix(candidate, "PROXY") {
79
-					return candidate
80
-				} else {
81
-					return
82
-				}
83
-			}
84
-			i += 1
98
+			return nil, err
99
+		}
100
+		if buf[i] == '\n' {
101
+			return buf, nil
85 102
 		}
86 103
 	}
104
+}
105
+
106
+func readRawProxyLineV2(conn net.Conn, buf []byte) (result []byte, err error) {
107
+	// "The 15th and 16th bytes is the address length in bytes in network endian order."
108
+	addrLen := int(binary.BigEndian.Uint16(buf[14:16]))
109
+	if addrLen == 0 {
110
+		return buf[0:16], nil
111
+	} else if addrLen <= cap(buf)-16 {
112
+		buf = buf[0 : 16+addrLen]
113
+	} else {
114
+		// proxy source is unix domain, we don't really handle this
115
+		buf2 := make([]byte, 16+addrLen)
116
+		copy(buf2[0:16], buf[0:16])
117
+		buf = buf2
118
+	}
119
+	_, err = io.ReadFull(conn, buf[16:16+addrLen])
120
+	if err != nil {
121
+		return
122
+	}
123
+	return buf[0 : 16+addrLen], nil
124
+}
87 125
 
88
-	// no \r\n, fail out
89
-	return
126
+// ParseProxyLine parses a PROXY protocol (v1 or v2) line and returns the remote IP.
127
+func ParseProxyLine(line []byte) (ip net.IP, err error) {
128
+	if len(line) == 0 {
129
+		return nil, ErrBadProxyLine
130
+	}
131
+	switch line[0] {
132
+	case 'P':
133
+		return ParseProxyLineV1(string(line))
134
+	case '\r':
135
+		return parseProxyLineV2(line)
136
+	default:
137
+		return nil, ErrBadProxyLine
138
+	}
90 139
 }
91 140
 
92
-// ParseProxyLine parses a PROXY protocol (v1) line and returns the remote IP.
93
-func ParseProxyLine(line string) (ip net.IP, err error) {
141
+// ParseProxyLineV1 parses a PROXY protocol (v1) line and returns the remote IP.
142
+func ParseProxyLineV1(line string) (ip net.IP, err error) {
94 143
 	params := strings.Fields(line)
95 144
 	if len(params) != 6 || params[0] != "PROXY" {
96 145
 		return nil, ErrBadProxyLine
@@ -102,6 +151,62 @@ func ParseProxyLine(line string) (ip net.IP, err error) {
102 151
 	return ip.To16(), nil
103 152
 }
104 153
 
154
+func parseProxyLineV2(line []byte) (ip net.IP, err error) {
155
+	if len(line) < 16 {
156
+		return nil, ErrBadProxyLine
157
+	}
158
+	// this doesn't allocate
159
+	if string(line[:12]) != "\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a" {
160
+		return nil, ErrBadProxyLine
161
+	}
162
+	// "The next byte (the 13th one) is the protocol version and command."
163
+	versionCmd := line[12]
164
+	// "The highest four bits contains the version [....] it must always be sent as \x2"
165
+	if (versionCmd >> 4) != 2 {
166
+		return nil, ErrBadProxyLine
167
+	}
168
+	// "The lowest four bits represents the command"
169
+	switch versionCmd & 0x0f {
170
+	case 0:
171
+		return nil, nil // LOCAL command
172
+	case 1:
173
+		// PROXY command, continue below
174
+	default:
175
+		// "Receivers must drop connections presenting unexpected values here"
176
+		return nil, ErrBadProxyLine
177
+	}
178
+
179
+	var addrLen int
180
+	// "The 14th byte contains the transport protocol and address family."
181
+	protoAddr := line[13]
182
+	// "The highest 4 bits contain the address family"
183
+	switch protoAddr >> 4 {
184
+	case 1:
185
+		addrLen = 4 // AF_INET
186
+	case 2:
187
+		addrLen = 16 // AF_INET6
188
+	default:
189
+		return nil, nil // AF_UNSPEC or AF_UNIX, either way there's no IP address
190
+	}
191
+
192
+	// header, source and destination address, two 16-bit port numbers:
193
+	expectedLen := 16 + 2*addrLen + 4
194
+	if len(line) < expectedLen {
195
+		return nil, ErrBadProxyLine
196
+	}
197
+
198
+	// "Starting from the 17th byte, addresses are presented in network byte order.
199
+	//  The address order is always the same :
200
+	//    - source layer 3 address in network byte order [...]"
201
+	if addrLen == 4 {
202
+		ip = net.IP(line[16 : 16+addrLen]).To16()
203
+	} else {
204
+		ip = make(net.IP, addrLen)
205
+		copy(ip, line[16:16+addrLen])
206
+	}
207
+	return ip, nil
208
+}
209
+
105 210
 /// WrappedConn is a net.Conn with some additional data stapled to it;
106 211
 // the proxied IP, if one was read via the PROXY protocol, and the listener
107 212
 // configuration.
@@ -161,8 +266,10 @@ func (rl *ReloadableListener) Accept() (conn net.Conn, err error) {
161 266
 		// but that's OK because this listener *requires* a PROXY line,
162 267
 		// therefore it must be used with proxies that always send the line
163 268
 		// and we won't get slowloris'ed waiting for the client response
164
-		proxyLine := readRawProxyLine(conn, config.ProxyDeadline)
165
-		proxiedIP, err = ParseProxyLine(proxyLine)
269
+		proxyLine, err := readRawProxyLine(conn, config.ProxyDeadline)
270
+		if err == nil {
271
+			proxiedIP, err = ParseProxyLine(proxyLine)
272
+		}
166 273
 		if err != nil {
167 274
 			conn.Close()
168 275
 			return nil, err

+ 5
- 4
traditional.yaml View File

@@ -26,10 +26,11 @@ server:
26 26
             tls:
27 27
                 cert: fullchain.pem
28 28
                 key: privkey.pem
29
-                # 'proxy' should typically be false. It's only for Kubernetes-style load
30
-                # balancing that does not terminate TLS, but sends an initial PROXY line
31
-                # in plaintext.
32
-                proxy: false
29
+            # 'proxy' should typically be false. It's for cloud load balancers that
30
+            # always send PROXY headers ahead of the connection (e.g., a v1 header
31
+            # ahead of unterminated TLS, or a v2 binary header) that MUST be present
32
+            # and cannot be processed on an optional basis.
33
+            proxy: false
33 34
 
34 35
         # Example of a Unix domain socket for proxying:
35 36
         # "/tmp/oragono_sock":

Loading…
Cancel
Save