github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/webserver/utils_ip.go (about) 1 package webserver 2 3 import ( 4 "fmt" 5 "net" 6 "net/http" 7 "strings" 8 ) 9 10 var ( 11 userRealIpHeaderCandidates = [...]string{"X-Real-Ip", "X-Forwarded-For"} 12 // From: https://github.com/letsencrypt/boulder/blob/main/bdns/dns.go#L30-L146 13 // Private CIDRs to ignore 14 privateNetworks = []net.IPNet{ 15 // RFC1918 16 // 10.0.0.0/8 17 { 18 IP: []byte{10, 0, 0, 0}, 19 Mask: []byte{255, 0, 0, 0}, 20 }, 21 // 172.16.0.0/12 22 { 23 IP: []byte{172, 16, 0, 0}, 24 Mask: []byte{255, 240, 0, 0}, 25 }, 26 // 192.168.0.0/16 27 { 28 IP: []byte{192, 168, 0, 0}, 29 Mask: []byte{255, 255, 0, 0}, 30 }, 31 // RFC5735 32 // 127.0.0.0/8 33 { 34 IP: []byte{127, 0, 0, 0}, 35 Mask: []byte{255, 0, 0, 0}, 36 }, 37 // RFC1122 Section 3.2.1.3 38 // 0.0.0.0/8 39 { 40 IP: []byte{0, 0, 0, 0}, 41 Mask: []byte{255, 0, 0, 0}, 42 }, 43 // RFC3927 44 // 169.254.0.0/16 45 { 46 IP: []byte{169, 254, 0, 0}, 47 Mask: []byte{255, 255, 0, 0}, 48 }, 49 // RFC 5736 50 // 192.0.0.0/24 51 { 52 IP: []byte{192, 0, 0, 0}, 53 Mask: []byte{255, 255, 255, 0}, 54 }, 55 // RFC 5737 56 // 192.0.2.0/24 57 { 58 IP: []byte{192, 0, 2, 0}, 59 Mask: []byte{255, 255, 255, 0}, 60 }, 61 // 198.51.100.0/24 62 { 63 IP: []byte{198, 51, 100, 0}, 64 Mask: []byte{255, 255, 255, 0}, 65 }, 66 // 203.0.113.0/24 67 { 68 IP: []byte{203, 0, 113, 0}, 69 Mask: []byte{255, 255, 255, 0}, 70 }, 71 // RFC 3068 72 // 192.88.99.0/24 73 { 74 IP: []byte{192, 88, 99, 0}, 75 Mask: []byte{255, 255, 255, 0}, 76 }, 77 // RFC 2544, Errata 423 78 // 198.18.0.0/15 79 { 80 IP: []byte{198, 18, 0, 0}, 81 Mask: []byte{255, 254, 0, 0}, 82 }, 83 // RFC 3171 84 // 224.0.0.0/4 85 { 86 IP: []byte{224, 0, 0, 0}, 87 Mask: []byte{240, 0, 0, 0}, 88 }, 89 // RFC 1112 90 // 240.0.0.0/4 91 { 92 IP: []byte{240, 0, 0, 0}, 93 Mask: []byte{240, 0, 0, 0}, 94 }, 95 // RFC 919 Section 7 96 // 255.255.255.255/32 97 { 98 IP: []byte{255, 255, 255, 255}, 99 Mask: []byte{255, 255, 255, 255}, 100 }, 101 // RFC 6598 102 // 100.64.0.0/10 103 { 104 IP: []byte{100, 64, 0, 0}, 105 Mask: []byte{255, 192, 0, 0}, 106 }, 107 } 108 // Sourced from https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml 109 // where Global, Source, or Destination is False 110 privateV6Networks = []net.IPNet{ 111 parseCIDR("::/128", "RFC 4291: Unspecified Address"), 112 parseCIDR("::1/128", "RFC 4291: Loopback Address"), 113 parseCIDR("::ffff:0:0/96", "RFC 4291: IPv4-mapped Address"), 114 parseCIDR("100::/64", "RFC 6666: Discard Address Block"), 115 parseCIDR("2001::/23", "RFC 2928: IETF Protocol Assignments"), 116 parseCIDR("2001:2::/48", "RFC 5180: Benchmarking"), 117 parseCIDR("2001:db8::/32", "RFC 3849: Documentation"), 118 parseCIDR("2001::/32", "RFC 4380: TEREDO"), 119 parseCIDR("fc00::/7", "RFC 4193: Unique-Local"), 120 parseCIDR("fe80::/10", "RFC 4291: Section 2.5.6 Link-Scoped Unicast"), 121 parseCIDR("ff00::/8", "RFC 4291: Section 2.7"), 122 // We disable validations to IPs under the 6to4 anycase prefix because 123 // there's too much risk of a malicious actor advertising the prefix and 124 // answering validations for a 6to4 host they do not control. 125 // https://community.letsencrypt.org/t/problems-validating-ipv6-against-host-running-6to4/18312/9 126 parseCIDR("2002::/16", "RFC 7526: 6to4 anycast prefix deprecated"), 127 } 128 ) 129 130 // parseCIDR parses the predefined CIDR to `net.IPNet` that consisting of IP and IPMask. 131 func parseCIDR(network string, comment string) net.IPNet { 132 _, subNet, err := net.ParseCIDR(network) 133 if err != nil { 134 panic(fmt.Sprintf("error parsing %s (%s): %s", network, comment, err)) 135 } 136 return *subNet 137 } 138 139 // isPrivateV4 checks whether an `ip` is private based on whether the IP is in the private CIDR range. 140 func isPrivateV4(ip net.IP) bool { 141 for _, subNet := range privateNetworks { 142 if subNet.Contains(ip) { 143 return true 144 } 145 } 146 return false 147 } 148 149 // isPrivateV6 checks whether an `ip` is private based on whether the IP is in the private CIDR range. 150 func isPrivateV6(ip net.IP) bool { 151 for _, subNet := range privateV6Networks { 152 if subNet.Contains(ip) { 153 return true 154 } 155 } 156 return false 157 } 158 159 // IsPrivateIP check IPv4 or IPv6 address according to the length of byte array 160 func IsPrivateIP(ip net.IP) bool { 161 if ip4 := ip.To4(); ip4 != nil { 162 return isPrivateV4(ip4) 163 } 164 return ip.To16() != nil && isPrivateV6(ip) 165 } 166 167 // IsIPValidAndPublic is a helper function check if an IP address is valid and public. 168 func IsIPValidAndPublic(ipAddr string) bool { 169 if ipAddr == "" { 170 return false 171 } 172 ipAddr = strings.TrimSpace(ipAddr) 173 ip := net.ParseIP(ipAddr) 174 // remote address within public address range 175 if ip != nil && !IsPrivateIP(ip) { 176 return true 177 } 178 return false 179 } 180 181 // GetUserRealIP Get User Real IP from headers of request `r` 182 // 1. First, determine whether the remote addr of request is a private address. 183 // If it is a public network address, return it directly; 184 // 2. Otherwise, get and check the real IP from X-REAL-IP and X-Forwarded-For headers in turn. 185 // if the header value contains multiple IP addresses separated by commas, that is, 186 // the request may pass through multiple reverse proxies, we just keep the first one, 187 // which imply it is the user connecting IP. 188 // then we check the value is a valid public IP address using the `IsIPValidAndPublic` function. 189 // If it is, the function returns the value as the client's real IP address. 190 // 3. Finally, If the above headers do not exist or are invalid, the remote addr is returned as is. 191 func GetUserRealIP(r *http.Request) string { 192 fallbackAddr := r.RemoteAddr 193 connectAddr, _, err := net.SplitHostPort(r.RemoteAddr) 194 if err != nil { 195 return fallbackAddr 196 } 197 if IsIPValidAndPublic(connectAddr) { 198 return connectAddr 199 } 200 // in case that remote address is private(container or internal) 201 for _, hd := range userRealIpHeaderCandidates { 202 val := r.Header.Get(hd) 203 if val == "" { 204 continue 205 } 206 // remove leading or tailing comma, tab, space 207 ipAddr := strings.Trim(val, ",\t ") 208 if idxFirstIP := strings.Index(ipAddr, ","); idxFirstIP >= 0 { 209 ipAddr = ipAddr[:idxFirstIP] 210 } 211 if IsIPValidAndPublic(ipAddr) { 212 return ipAddr 213 } 214 } 215 return fallbackAddr 216 }