/
ip.go
159 lines (122 loc) · 3.45 KB
/
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
package pirsch
import (
"net"
"net/http"
"strings"
)
var (
// CFConnectingIP is an HeaderParser.
// https://support.cloudflare.com/hc/en-us/articles/206776727-What-is-True-Client-IP-
CFConnectingIP = HeaderParser{"CF-Connecting-IP", parseXForwardedForHeader}
// TrueClientIP is an HeaderParser.
TrueClientIP = HeaderParser{"True-Client-IP", parseXForwardedForHeader}
// XForwardedFor is an HeaderParser.
XForwardedFor = HeaderParser{"X-Forwarded-For", parseXForwardedForHeader}
// Forwarded is an HeaderParser.
Forwarded = HeaderParser{"Forwarded", parseForwardedHeader}
// XRealIP is an HeaderParser.
XRealIP = HeaderParser{"X-Real-IP", parseXRealIPHeader}
// DefaultHeaderParser is a list of headers and corresponding parsers to look up the real client IP.
// They will be check in order, the first non-empty one will be picked,
// or else the remote address is selected.
DefaultHeaderParser = []HeaderParser{
CFConnectingIP,
TrueClientIP,
XForwardedFor,
Forwarded,
XRealIP,
}
)
// ParseHeaderFunc parses and validates an IP address from a header.
// It must return an empty string if the header or contained IP address is invalid.
type ParseHeaderFunc func(string) string
// HeaderParser parses a header to extract the real client IP address.
type HeaderParser struct {
Header string
Parser ParseHeaderFunc
}
// getIP returns the IP from given request.
// It will try to extract the real client IP from headers if possible.
func getIP(r *http.Request, parser []HeaderParser, allowed []net.IPNet) string {
ip := cleanIP(r.RemoteAddr)
if allowed != nil && !validProxySource(ip, allowed) {
return ip
}
for _, header := range parser {
value := r.Header.Get(header.Header)
if value != "" {
parsedIP := header.Parser(value)
if parsedIP != "" {
return parsedIP
}
}
}
return ip
}
// cleanIP returns the ip without port, if any.
func cleanIP(ip string) string {
if strings.Contains(ip, ":") {
host, _, err := net.SplitHostPort(ip)
if err != nil {
return ip
}
return host
}
return ip
}
// validProxySource returns whether the IP is in the allowed proxy subnet list.
func validProxySource(address string, allowed []net.IPNet) bool {
ip := net.ParseIP(address)
if ip == nil {
return false
}
for _, from := range allowed {
if from.Contains(ip) {
return true
}
}
return false
}
// parseForwardedHeader parses the Forwarded header to extract the real client IP.
func parseForwardedHeader(value string) string {
parts := strings.Split(value, ",")
if len(parts) > 0 {
parts = strings.Split(parts[len(parts)-1], ";")
for _, part := range parts {
k, ip, found := strings.Cut(part, "=")
if found && strings.TrimSpace(k) == "for" {
ip = cleanIP(ip)
if isValidIP(ip) {
return ip
}
}
}
}
return ""
}
// parseXForwardedForHeader parses the X-Forwarded-For header to extract the real client IP.
func parseXForwardedForHeader(value string) string {
parts := strings.Split(value, ",")
if len(parts) > 0 {
ip := cleanIP(strings.TrimSpace(parts[len(parts)-1]))
if isValidIP(ip) {
return ip
}
}
return ""
}
// parseXRealIPHeader parses the X-Real-IP header to extract the real client IP.
func parseXRealIPHeader(value string) string {
value = cleanIP(strings.TrimSpace(value))
if isValidIP(value) {
return value
}
return ""
}
func isValidIP(value string) bool {
ip := net.ParseIP(value)
return ip != nil &&
!ip.IsPrivate() &&
!ip.IsLoopback() &&
!ip.IsUnspecified()
}