-
Notifications
You must be signed in to change notification settings - Fork 0
/
client-ip-func.go
172 lines (148 loc) · 5.21 KB
/
client-ip-func.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
/*
* Copyright (c) 2024.
* Created by Andy Pangaribuan <https://github.com/apangaribuan>.
*
* This product is protected by copyright and distributed under
* licenses restricting copying, distribution and decompilation.
* All Rights Reserved.
*/
package server
import (
"net"
"net/http"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/pkg/errors"
)
func init() {
maxCidrBlocks := []string{
"127.0.0.1/8", // localhost
"10.0.0.0/8", // 24-bit block
"172.16.0.0/12", // 20-bit block
"192.168.0.0/16", // 16-bit block
"169.254.0.0/16", // link local address
"::1/128", // localhost IPv6
"fc00::/7", // unique local address IPv6
"fe80::/10", // link local address IPv6
}
cidrs := make([]*net.IPNet, len(maxCidrBlocks))
for i, maxCidrBlock := range maxCidrBlocks {
_, cidr, _ := net.ParseCIDR(maxCidrBlock)
cidrs[i] = cidr
}
cip = &stuClientIP{
// Should use canonical format of the header key s
// https://golang.org/pkg/net/http/#CanonicalHeaderKey
// Header may return multiple IP addresses in the format: "client IP, proxy 1 IP, proxy 2 IP", so we take the the first one.
cidrs: cidrs,
xOriginalForwardedForHeader: http.CanonicalHeaderKey("X-Original-Forwarded-For"),
xForwardedForHeader: http.CanonicalHeaderKey("X-Forwarded-For"),
xForwardedHeader: http.CanonicalHeaderKey("X-Forwarded"),
forwardedForHeader: http.CanonicalHeaderKey("Forwarded-For"),
forwardedHeader: http.CanonicalHeaderKey("Forwarded"),
// Standard headers used by Amazon EC2, Heroku, and others
xClientIPHeader: http.CanonicalHeaderKey("X-Client-IP"),
// Nginx proxy/FastCGI
xRealIPHeader: http.CanonicalHeaderKey("X-Real-IP"),
// Cloudflare.
// @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
// CF-Connecting-IP - applied to every request to the origin.
cfConnectingIPHeader: http.CanonicalHeaderKey("CF-Connecting-IP"),
// Fastly CDN and Firebase hosting header when forwarded to a cloud function
fastlyClientIPHeader: http.CanonicalHeaderKey("Fastly-Client-Ip"),
// Akamai and Cloudflare
trueClientIPHeader: http.CanonicalHeaderKey("True-Client-Ip"),
}
}
func (slf *stuClientIP) getClientIP(fiberCtx *fiber.Ctx) string {
ctx := fiberCtx.Context()
xClientIP := ctx.Request.Header.Peek(slf.xClientIPHeader)
if xClientIP != nil {
return string(xClientIP)
}
xOriginalForwardedFor := ctx.Request.Header.Peek(slf.xOriginalForwardedForHeader)
if xOriginalForwardedFor != nil {
requestIP, err := slf.retrieveForwardedIP(string(xOriginalForwardedFor))
if err == nil {
return requestIP
}
}
xForwardedFor := ctx.Request.Header.Peek(slf.xForwardedForHeader)
if xForwardedFor != nil {
requestIP, err := slf.retrieveForwardedIP(string(xForwardedFor))
if err == nil {
return requestIP
}
}
if ip, err := slf.fromSpecialHeaders(fiberCtx); err == nil {
return ip
}
if ip, err := slf.fromForwardedHeaders(fiberCtx); err == nil {
return ip
}
var remoteIP string
remoteAddr := ctx.RemoteAddr().String()
if strings.ContainsRune(remoteAddr, ':') {
remoteIP, _, _ = net.SplitHostPort(remoteAddr)
} else {
remoteIP = remoteAddr
}
return remoteIP
}
func (slf *stuClientIP) fromSpecialHeaders(fiberCtx *fiber.Ctx) (string, error) {
ctx := fiberCtx.Context()
ipHeaders := [...]string{slf.cfConnectingIPHeader, slf.fastlyClientIPHeader, slf.trueClientIPHeader, slf.xRealIPHeader}
for _, iplHeader := range ipHeaders {
if clientIP := ctx.Request.Header.Peek(iplHeader); clientIP != nil {
return string(clientIP), nil
}
}
return "", errors.New("can't get ip from special headers")
}
func (slf *stuClientIP) fromForwardedHeaders(fiberCtx *fiber.Ctx) (string, error) {
ctx := fiberCtx.Context()
forwardedHeaders := [...]string{slf.xForwardedHeader, slf.forwardedForHeader, slf.forwardedHeader}
for _, forwardedHeader := range forwardedHeaders {
if forwarded := ctx.Request.Header.Peek(forwardedHeader); forwarded != nil {
if clientIP, err := slf.retrieveForwardedIP(string(forwarded)); err == nil {
return clientIP, nil
}
}
}
return "", errors.New("can't get ip from forwarded headers")
}
// isLocalAddress works by checking if the address is under private CIDR blocks.
// List of private CIDR blocks can be seen on :
//
// https://en.wikipedia.org/wiki/Private_network
//
// https://en.wikipedia.org/wiki/Link-local_address
func (slf *stuClientIP) isPrivateAddress(address string) (bool, error) {
ipAddress := net.ParseIP(address)
if ipAddress == nil {
return false, errors.New("address is not valid")
}
for i := range slf.cidrs {
if slf.cidrs[i].Contains(ipAddress) {
return true, nil
}
}
return false, nil
}
func (slf *stuClientIP) retrieveForwardedIP(forwardedHeader string) (string, error) {
for _, address := range strings.Split(forwardedHeader, ",") {
if len(address) > 0 {
address = strings.TrimSpace(address)
isPrivate, err := slf.isPrivateAddress(address)
switch {
case !isPrivate && err == nil:
return address, nil
case isPrivate && err == nil:
return "", errors.New("forwarded ip is private")
default:
return "", err
}
}
}
return "", errors.New("empty or invalid forwarded header")
}