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  }