-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.go
More file actions
288 lines (255 loc) · 7.11 KB
/
proxy.go
File metadata and controls
288 lines (255 loc) · 7.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
package magictls
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"net"
"strconv"
"strings"
"sync"
)
var (
allowedProxyIps []*net.IPNet
allowedProxyIpsLk sync.RWMutex
)
func init() {
SetAllowedProxies("127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fd00::/8")
}
type proxyError struct {
version int
msg string
}
func (p *proxyError) Error() string {
return fmt.Sprintf("Error in PROXYv%d protocol: %s", p.version, p.msg)
}
// SetAllowedProxies allows modifying the list of IP addresses allowed to use
// proxy protocol. Any host matching a CIDR listed in here will be trusted to
// provide the client's real IP.
//
// By default all local IPs are allowed as these cannot appear on Internet.
//
// Example:
//
// SetAllowedProxies("127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fd00::/8")
func SetAllowedProxies(cidrs ...string) error {
allowedProxyIpsLk.Lock()
allowedProxyIps = nil
allowedProxyIpsLk.Unlock()
return AddAllowedProxies(cidrs...)
}
// AddAllowedProxies adds CIDR ranges to the list of allowed proxies.
// These IP ranges will be trusted to send PROXY protocol headers.
func AddAllowedProxies(cidrs ...string) error {
allowedProxyIpsLk.Lock()
defer allowedProxyIpsLk.Unlock()
for _, s := range cidrs {
_, ipn, err := net.ParseCIDR(s)
if err != nil {
return err
}
allowedProxyIps = append(allowedProxyIps, ipn)
}
return nil
}
// AddAllowedProxiesSpf will perform TXT lookup on the given hosts and add
// those IPs as allowed proxies. This is only performed once and may need to
// be refreshed from times to times.
func AddAllowedProxiesSpf(spfhosts ...string) error {
// example: magictls.AddAllowedProxiesSpf("_cloud-eoips.googleusercontent.com")
var cidrs []string
for _, host := range spfhosts {
txtrecords, err := net.LookupTXT(host)
if err != nil {
return err
}
for _, rec := range txtrecords {
// typical response: v=spf1 ip4:<cidr> ~all
recData := strings.Fields(rec)
for _, rec := range recData {
if val, ok := strings.CutPrefix(rec, "ip4:"); ok {
if strings.IndexByte(val, '/') == -1 {
// no / found, this is a host and not a cidr
val += "/32"
}
cidrs = append(cidrs, val)
} else if val, ok = strings.CutPrefix(rec, "ip6:"); ok {
if strings.IndexByte(val, '/') == -1 {
// no / found, this is a host and not a cidr
val += "/128"
}
cidrs = append(cidrs, val)
}
}
}
}
return AddAllowedProxies(cidrs...)
}
// DetectProxy is a magictls [Filter] that detects PROXY protocol headers
// (both v1 and v2) and updates the connection's local/remote addresses
// based on the information provided in the PROXY header.
//
// Only connections from allowed proxy IPs (see [SetAllowedProxies]) will
// have their PROXY headers parsed. Connections from other sources will
// pass through unchanged.
func DetectProxy(cw *Conn, srv *Listener) error {
proxyAllow := false
allowedProxyIpsLk.RLock()
allowed := allowedProxyIps
allowedProxyIpsLk.RUnlock()
switch ipaddr := cw.r.(type) {
case *net.TCPAddr:
for _, n := range allowed {
if n.Contains(ipaddr.IP) {
proxyAllow = true
break
}
}
case *net.IPAddr:
for _, n := range allowed {
if n.Contains(ipaddr.IP) {
proxyAllow = true
break
}
}
}
if !proxyAllow {
return nil
}
buf, err := cw.PeekUntil(16)
if err != nil {
return err
}
// detect proxy protocol v2 signature: \r\n\r\n\x00\r\nQUIT\n
if bytes.Equal(buf[:12], []byte{0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a}) {
// proxy protocol v2
var verCmd, fam uint8
verCmd = buf[12]
fam = buf[13]
ln := binary.BigEndian.Uint16(buf[14:16])
var d []byte
if ln > 0 {
tmp, err := cw.PeekUntil(16 + int(ln))
if err != nil {
log.Printf("magictls: failed to read proxy v2 data")
return err
}
d = tmp[16:]
}
if err := parseProxyV2Data(cw, verCmd, fam, d); err != nil {
return err
}
cw.SkipPeek(16 + int(ln))
return nil
} else if bytes.Equal(buf[:6], []byte("PROXY ")) {
// proxy protocol v1
var pos int
for {
buf, err = cw.PeekMore(128) // max proxy line length is 107 bytes in theory
if err != nil {
log.Printf("magictls: failed to read full line of proxy protocol")
return err
}
pos = bytes.IndexByte(buf, '\n')
if pos > 0 {
break
}
if len(buf) > 128 {
log.Printf("magictls: got proxy protocol intro but line is too long, ignoring")
return nil
}
}
err := parseProxyLine(cw, buf[:pos])
if err != nil {
return err
}
cw.SkipPeek(pos + 1)
}
return nil
}
func parseProxyLine(c *Conn, buf []byte) error {
if buf[len(buf)-1] == '\r' {
buf = buf[:len(buf)-1]
}
s := bytes.Split(buf, []byte{' '})
if !bytes.Equal(s[0], []byte("PROXY")) {
return &proxyError{version: 1, msg: "invalid proxy line provided"}
}
// see: https://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
switch string(s[1]) {
case "UNKNOWN":
return nil // do nothing
case "TCP4", "TCP6":
if len(s) < 6 {
return &proxyError{version: 1, msg: "not enough parameters for TCP PROXY"}
}
rPort, _ := strconv.Atoi(string(s[4]))
lPort, _ := strconv.Atoi(string(s[5]))
c.SetRemoteAddr(&net.TCPAddr{IP: net.ParseIP(string(s[2])), Port: rPort})
c.SetLocalAddr(&net.TCPAddr{IP: net.ParseIP(string(s[3])), Port: lPort})
return nil
default:
return &proxyError{version: 1, msg: "invalid proxy transport provided"}
}
}
func parseProxyV2Data(c *Conn, verCmd, fam uint8, d []byte) error {
if verCmd>>4&0xf != 0x2 {
return &proxyError{version: 2, msg: "unsupported header version"}
}
switch verCmd & 0xf {
case 0x0: // LOCAL (health check, etc)
return nil
case 0x1: // PROXY
break
default:
return &proxyError{version: 2, msg: "unsupported proxy type"}
}
switch fam >> 4 & 0xf {
case 0x0: // UNSPEC
return nil
case 0x1, 0x2: // AF_INET, AF_INET6
break
case 0x3: // AF_UNIX
return nil
default:
return &proxyError{version: 2, msg: "unsupported address family"}
}
switch fam & 0xf {
case 0x0: // UNSPEC
return nil
case 0x1, 0x2: // STREAM, DGRAM
break
default:
return &proxyError{version: 2, msg: "unsupported protocol"}
}
// sanitarization done, let's parse data
b := bytes.NewBuffer(d)
var rPort, lPort uint16
switch fam >> 4 & 0xf {
case 0x1: // AF_INET
if len(d) < 12 {
return &proxyError{version: 2, msg: "not enough data for ipv4"}
}
rip := make([]byte, 4)
lip := make([]byte, 4)
binary.Read(b, binary.BigEndian, rip)
binary.Read(b, binary.BigEndian, lip)
binary.Read(b, binary.BigEndian, &rPort)
binary.Read(b, binary.BigEndian, &lPort)
c.SetRemoteAddr(&net.TCPAddr{IP: rip, Port: int(rPort)})
c.SetLocalAddr(&net.TCPAddr{IP: lip, Port: int(lPort)})
case 0x2: // AF_INET6
if len(d) < 36 {
return &proxyError{version: 2, msg: "not enough data for ipv6"}
}
rip := make([]byte, 16)
lip := make([]byte, 16)
binary.Read(b, binary.BigEndian, rip)
binary.Read(b, binary.BigEndian, lip)
binary.Read(b, binary.BigEndian, &rPort)
binary.Read(b, binary.BigEndian, &lPort)
c.SetRemoteAddr(&net.TCPAddr{IP: rip, Port: int(rPort)})
c.SetLocalAddr(&net.TCPAddr{IP: lip, Port: int(lPort)})
}
return nil
}