/
utils_ip.go
216 lines (208 loc) · 5.91 KB
/
utils_ip.go
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
package webserver
import (
"fmt"
"net"
"net/http"
"strings"
)
var (
userRealIpHeaderCandidates = [...]string{"X-Real-Ip", "X-Forwarded-For"}
// From: https://github.com/letsencrypt/boulder/blob/main/bdns/dns.go#L30-L146
// Private CIDRs to ignore
privateNetworks = []net.IPNet{
// RFC1918
// 10.0.0.0/8
{
IP: []byte{10, 0, 0, 0},
Mask: []byte{255, 0, 0, 0},
},
// 172.16.0.0/12
{
IP: []byte{172, 16, 0, 0},
Mask: []byte{255, 240, 0, 0},
},
// 192.168.0.0/16
{
IP: []byte{192, 168, 0, 0},
Mask: []byte{255, 255, 0, 0},
},
// RFC5735
// 127.0.0.0/8
{
IP: []byte{127, 0, 0, 0},
Mask: []byte{255, 0, 0, 0},
},
// RFC1122 Section 3.2.1.3
// 0.0.0.0/8
{
IP: []byte{0, 0, 0, 0},
Mask: []byte{255, 0, 0, 0},
},
// RFC3927
// 169.254.0.0/16
{
IP: []byte{169, 254, 0, 0},
Mask: []byte{255, 255, 0, 0},
},
// RFC 5736
// 192.0.0.0/24
{
IP: []byte{192, 0, 0, 0},
Mask: []byte{255, 255, 255, 0},
},
// RFC 5737
// 192.0.2.0/24
{
IP: []byte{192, 0, 2, 0},
Mask: []byte{255, 255, 255, 0},
},
// 198.51.100.0/24
{
IP: []byte{198, 51, 100, 0},
Mask: []byte{255, 255, 255, 0},
},
// 203.0.113.0/24
{
IP: []byte{203, 0, 113, 0},
Mask: []byte{255, 255, 255, 0},
},
// RFC 3068
// 192.88.99.0/24
{
IP: []byte{192, 88, 99, 0},
Mask: []byte{255, 255, 255, 0},
},
// RFC 2544, Errata 423
// 198.18.0.0/15
{
IP: []byte{198, 18, 0, 0},
Mask: []byte{255, 254, 0, 0},
},
// RFC 3171
// 224.0.0.0/4
{
IP: []byte{224, 0, 0, 0},
Mask: []byte{240, 0, 0, 0},
},
// RFC 1112
// 240.0.0.0/4
{
IP: []byte{240, 0, 0, 0},
Mask: []byte{240, 0, 0, 0},
},
// RFC 919 Section 7
// 255.255.255.255/32
{
IP: []byte{255, 255, 255, 255},
Mask: []byte{255, 255, 255, 255},
},
// RFC 6598
// 100.64.0.0/10
{
IP: []byte{100, 64, 0, 0},
Mask: []byte{255, 192, 0, 0},
},
}
// Sourced from https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
// where Global, Source, or Destination is False
privateV6Networks = []net.IPNet{
parseCIDR("::/128", "RFC 4291: Unspecified Address"),
parseCIDR("::1/128", "RFC 4291: Loopback Address"),
parseCIDR("::ffff:0:0/96", "RFC 4291: IPv4-mapped Address"),
parseCIDR("100::/64", "RFC 6666: Discard Address Block"),
parseCIDR("2001::/23", "RFC 2928: IETF Protocol Assignments"),
parseCIDR("2001:2::/48", "RFC 5180: Benchmarking"),
parseCIDR("2001:db8::/32", "RFC 3849: Documentation"),
parseCIDR("2001::/32", "RFC 4380: TEREDO"),
parseCIDR("fc00::/7", "RFC 4193: Unique-Local"),
parseCIDR("fe80::/10", "RFC 4291: Section 2.5.6 Link-Scoped Unicast"),
parseCIDR("ff00::/8", "RFC 4291: Section 2.7"),
// We disable validations to IPs under the 6to4 anycase prefix because
// there's too much risk of a malicious actor advertising the prefix and
// answering validations for a 6to4 host they do not control.
// https://community.letsencrypt.org/t/problems-validating-ipv6-against-host-running-6to4/18312/9
parseCIDR("2002::/16", "RFC 7526: 6to4 anycast prefix deprecated"),
}
)
// parseCIDR parses the predefined CIDR to `net.IPNet` that consisting of IP and IPMask.
func parseCIDR(network string, comment string) net.IPNet {
_, subNet, err := net.ParseCIDR(network)
if err != nil {
panic(fmt.Sprintf("error parsing %s (%s): %s", network, comment, err))
}
return *subNet
}
// isPrivateV4 checks whether an `ip` is private based on whether the IP is in the private CIDR range.
func isPrivateV4(ip net.IP) bool {
for _, subNet := range privateNetworks {
if subNet.Contains(ip) {
return true
}
}
return false
}
// isPrivateV6 checks whether an `ip` is private based on whether the IP is in the private CIDR range.
func isPrivateV6(ip net.IP) bool {
for _, subNet := range privateV6Networks {
if subNet.Contains(ip) {
return true
}
}
return false
}
// IsPrivateIP check IPv4 or IPv6 address according to the length of byte array
func IsPrivateIP(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
return isPrivateV4(ip4)
}
return ip.To16() != nil && isPrivateV6(ip)
}
// IsIPValidAndPublic is a helper function check if an IP address is valid and public.
func IsIPValidAndPublic(ipAddr string) bool {
if ipAddr == "" {
return false
}
ipAddr = strings.TrimSpace(ipAddr)
ip := net.ParseIP(ipAddr)
// remote address within public address range
if ip != nil && !IsPrivateIP(ip) {
return true
}
return false
}
// GetUserRealIP Get User Real IP from headers of request `r`
// 1. First, determine whether the remote addr of request is a private address.
// If it is a public network address, return it directly;
// 2. Otherwise, get and check the real IP from X-REAL-IP and X-Forwarded-For headers in turn.
// if the header value contains multiple IP addresses separated by commas, that is,
// the request may pass through multiple reverse proxies, we just keep the first one,
// which imply it is the user connecting IP.
// then we check the value is a valid public IP address using the `IsIPValidAndPublic` function.
// If it is, the function returns the value as the client's real IP address.
// 3. Finally, If the above headers do not exist or are invalid, the remote addr is returned as is.
func GetUserRealIP(r *http.Request) string {
fallbackAddr := r.RemoteAddr
connectAddr, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return fallbackAddr
}
if IsIPValidAndPublic(connectAddr) {
return connectAddr
}
// in case that remote address is private(container or internal)
for _, hd := range userRealIpHeaderCandidates {
val := r.Header.Get(hd)
if val == "" {
continue
}
// remove leading or tailing comma, tab, space
ipAddr := strings.Trim(val, ",\t ")
if idxFirstIP := strings.Index(ipAddr, ","); idxFirstIP >= 0 {
ipAddr = ipAddr[:idxFirstIP]
}
if IsIPValidAndPublic(ipAddr) {
return ipAddr
}
}
return fallbackAddr
}