├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── remoteaddr.go └── remoteaddr_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .DS_Store 18 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Netinternet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remoteaddr 2 | Go http real ip header parser module 3 | 4 | A forwarders such as a reverse proxy or Cloudflare find the real IP address from the requests made to the http server behind it. Local IP addresses and CloudFlare ip addresses are defined by default within the module. It is possible to define more forwarder IP addresses. 5 | 6 | In Turkey, it is obligatory to keep the port information of IP addresses shared with cgnat by the law no 5651. For this reason, dst port information is also given along with the IP addresses. **If the IP address is behind a proxy, the dst port information is returned as -1.** 7 | 8 | ## Usage 9 | 10 | ``` 11 | go get -u github.com/netinternet/remoteaddr 12 | ``` 13 | 14 | ```go 15 | // remoteaddr.Parse().IP(*http.Request) return to string IPv4 or IPv6 address 16 | ``` 17 | 18 | ## Example 19 | 20 | Run a simple web server and get the real IP address to string format 21 | 22 | ```go 23 | package main 24 | 25 | import ( 26 | "fmt" 27 | "log" 28 | "net/http" 29 | 30 | "github.com/netinternet/remoteaddr" 31 | ) 32 | 33 | func root(w http.ResponseWriter, r *http.Request) { 34 | ip, port := remoteaddr.Parse().IP(r) 35 | fmt.Fprintf(w, "Your IP address is "+ip+" and dst port "+port) 36 | } 37 | 38 | func main() { 39 | http.HandleFunc("/", root) 40 | log.Fatal(http.ListenAndServe(":8081", nil)) 41 | } 42 | 43 | ``` 44 | 45 | ## Example 2 (Nginx or another web service forwarder address) 46 | 47 | **AddForwarders([]string{"8.8.8.0/24"})** = Add a new multiple forwarder prefixes 48 | 49 | ```go 50 | package main 51 | 52 | import ( 53 | "fmt" 54 | "log" 55 | "net/http" 56 | 57 | "github.com/netinternet/remoteaddr" 58 | ) 59 | 60 | func root(w http.ResponseWriter, r *http.Request) { 61 | ip, port := remoteaddr.Parse().AddForwarders([]string{"8.8.8.0/24"}).IP(r) 62 | fmt.Fprintf(w, "Your IP address is "+ip+" and dst port "+port) 63 | } 64 | 65 | func main() { 66 | http.HandleFunc("/", root) 67 | log.Fatal(http.ListenAndServe(":8081", nil)) 68 | } 69 | 70 | ``` 71 | 72 | ## Example 3 (Add an alternative header for real IP address) 73 | 74 | **AddHeaders([]string{"True-Client-IP"})** = Add a new multiple real ip headers 75 | 76 | ```go 77 | package main 78 | 79 | import ( 80 | "fmt" 81 | "log" 82 | "net/http" 83 | 84 | "github.com/netinternet/remoteaddr" 85 | ) 86 | 87 | func root(w http.ResponseWriter, r *http.Request) { 88 | ip, port := remoteaddr.Parse().AddHeaders([]string{"True-Client-IP"}).IP(r) 89 | fmt.Fprintf(w, "Your IP address is "+ip+" and dst port "+port) 90 | } 91 | 92 | func main() { 93 | http.HandleFunc("/", root) 94 | log.Fatal(http.ListenAndServe(":8081", nil)) 95 | } 96 | 97 | ``` 98 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/netinternet/remoteaddr 2 | 3 | go 1.16 4 | 5 | require github.com/stretchr/testify v1.7.0 // indirect 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 8 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /remoteaddr.go: -------------------------------------------------------------------------------- 1 | package remoteaddr 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | type Addr struct { 10 | Forwarders []string 11 | Headers []string 12 | } 13 | 14 | // Inital function 15 | func Parse() *Addr { 16 | // RFC1918 IPv4 Private Address Space 17 | local_prefixes := []string{ 18 | "10.0.0.0/8", 19 | "172.16.0.0/12", 20 | "192.168.0.0/16", 21 | } 22 | waf_prefixes := []string{ 23 | // CloudFlare IP Address Space; https://www.cloudflare.com/ips/ 24 | "103.21.244.0/22", 25 | "103.22.200.0/22", 26 | "103.31.4.0/22", 27 | "104.16.0.0/13", 28 | "104.24.0.0/14", 29 | "108.162.192.0/18", 30 | "131.0.72.0/22", 31 | "141.101.64.0/18", 32 | "162.158.0.0/15", 33 | "172.64.0.0/13", 34 | "173.245.48.0/20", 35 | "188.114.96.0/20", 36 | "190.93.240.0/20", 37 | "197.234.240.0/22", 38 | "198.41.128.0/17", 39 | "2400:cb00::/32", 40 | "2606:4700::/32", 41 | "2803:f800::/32", 42 | "2405:b500::/32", 43 | "2405:8100::/32", 44 | "2a06:98c0::/29", 45 | "2c0f:f248::/32", 46 | 47 | // HEIMWALL IPs 48 | "159.253.42.0/24", 49 | "94.102.14.5/24", 50 | "2a03:2100:a::/48", 51 | "2a03:2100:b::/48", 52 | } 53 | return &Addr{ 54 | Forwarders: append(local_prefixes, waf_prefixes...), 55 | Headers: []string{"CF-Connecting-IP", "X-Forwarded-For", "X-Real-Ip"}, 56 | } 57 | } 58 | 59 | // Add more Forwarder Prefixes 60 | func (a *Addr) AddForwarders(prefixes []string) *Addr { 61 | a.Forwarders = append(a.Forwarders, prefixes...) 62 | return a 63 | } 64 | 65 | // Add more Real IP Address Headers 66 | func (a *Addr) AddHeaders(headers []string) *Addr { 67 | a.Headers = append(a.Headers, headers...) 68 | return a 69 | } 70 | 71 | // Helper function 72 | func (a *Addr) isForwarders(ip net.IP) bool { 73 | for _, forwarder := range a.Forwarders { 74 | if _, cidr, _ := net.ParseCIDR(forwarder); cidr.Contains(ip) { 75 | return true 76 | } 77 | } 78 | return false 79 | } 80 | 81 | // Add http.request to find real IPv4 or IPv6 address and destination port 82 | func (a *Addr) IP(r *http.Request) (ipaddr string, port string) { 83 | ipaddr, port, _ = net.SplitHostPort(r.RemoteAddr) 84 | if a.isForwarders(net.ParseIP(ipaddr)) { 85 | port = "-1" 86 | for _, h := range a.Headers { 87 | for _, ip := range strings.Split(r.Header.Get(h), ",") { 88 | realIP := net.ParseIP(strings.Replace(ip, " ", "", -1)) 89 | if check := net.ParseIP(realIP.String()); check != nil { 90 | ipaddr = realIP.String() 91 | if !a.isForwarders(net.ParseIP(ipaddr)) { 92 | break 93 | } 94 | } 95 | } 96 | } 97 | } 98 | return ipaddr, port 99 | } 100 | -------------------------------------------------------------------------------- /remoteaddr_test.go: -------------------------------------------------------------------------------- 1 | package remoteaddr_test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/netinternet/remoteaddr" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestIP(t *testing.T) { 13 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | testObj := remoteaddr.Parse() 15 | testObj.AddForwarders([]string{"8.8.8.0/24"}) 16 | testObj.AddHeaders([]string{"True-Client-IP"}) 17 | ip, port := testObj.IP(r) 18 | 19 | // Test 20 | require.Contains(t, testObj.Forwarders, "8.8.8.0/24") 21 | require.Contains(t, testObj.Headers, "True-Client-IP") 22 | require.Equal(t, "127.0.0.1", ip) 23 | require.NotZero(t, port) 24 | })) 25 | defer ts.Close() 26 | 27 | _, err := http.Get(ts.URL) 28 | require.NoError(t, err) 29 | 30 | } 31 | --------------------------------------------------------------------------------