shiori/internal/webserver/utils_ip.go

217 lines
5.9 KiB
Go
Raw Normal View History

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
}