Correct way of getting Client's IP Addresses from http.Request

HttpGoIp

Http Problem Overview


What's the correct way to get all client's IP Addresses from http.Request? In PHP there are a lot of variables that I should check. Is it the same on Go?

One that I found is:

req.RemoteAddr

And is the request case sensitive? for example x-forwarded-for is the same as X-Forwarded-For and X-FORWARDED-FOR? (from req.Header.Get("X-FORWARDED-FOR"))

Http Solutions


Solution 1 - Http

Looking at http.Request you can find the following member variables:

// HTTP defines that header names are case-insensitive.
// The request parser implements this by canonicalizing the
// name, making the first character and any characters
// following a hyphen uppercase and the rest lowercase.
//
// For client requests certain headers are automatically
// added and may override values in Header.
//
// See the documentation for the Request.Write method.
Header Header

// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string

You can use RemoteAddr to get the remote client's IP address and port (the format is "IP:port"), which is the address of the original requestor or the last proxy (for example a load balancer which lives in front of your server).

This is all you have for sure.

Then you can investigate the headers, which are case-insensitive (per documentation above), meaning all of your examples will work and yield the same result:

req.Header.Get("X-Forwarded-For") // capitalisation
req.Header.Get("x-forwarded-for") // doesn't
req.Header.Get("X-FORWARDED-FOR") // matter

This is because internally http.Header.Get will normalise the key for you. (If you want to access header map directly, and not through Get, you would need to use http.CanonicalHeaderKey first.)

Finally, "X-Forwarded-For" is probably the field you want to take a look at in order to grab more information about client's IP. This greatly depends on the HTTP software used on the remote side though, as client can put anything in there if it wishes to. Also, note the expected format of this field is the comma+space separated list of IP addresses. You will need to parse it a little bit to get a single IP of your choice (probably the first one in the list), for example:

// Assuming format is as expected
ips := strings.Split("10.0.0.1, 10.0.0.2, 10.0.0.3", ", ")
for _, ip := range ips {
	fmt.Println(ip)
}

will produce:

10.0.0.1
10.0.0.2
10.0.0.3

Solution 2 - Http

Here a completely working example

package main

import (  
    // Standard library packages
    "fmt"
	"strconv"
	"log"
    "net"
	"net/http"
		
	// Third party packages
    "github.com/julienschmidt/httprouter"
	"github.com/skratchdot/open-golang/open"
)



// https://blog.golang.org/context/userip/userip.go
func getIP(w http.ResponseWriter, req *http.Request, _ httprouter.Params){
    fmt.Fprintf(w, "<h1>static file server</h1><p><a href='./static'>folder</p></a>")
	
	ip, port, err := net.SplitHostPort(req.RemoteAddr)
	if err != nil {
		//return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
		
		fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
	}

	userIP := net.ParseIP(ip)
	if userIP == nil {
		//return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
		fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
		return
	}
	
	// This will only be defined when site is accessed via non-anonymous proxy
	// and takes precedence over RemoteAddr
    // Header.Get is case-insensitive
	forward := req.Header.Get("X-Forwarded-For")
	
	fmt.Fprintf(w, "<p>IP: %s</p>", ip)
	fmt.Fprintf(w, "<p>Port: %s</p>", port)
	fmt.Fprintf(w, "<p>Forwarded for: %s</p>", forward)
}


func main() {  
	myport := strconv.Itoa(10002);
	
	
    // Instantiate a new router
    r := httprouter.New()
	
    r.GET("/ip", getIP)
	
    // Add a handler on /test
    r.GET("/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
        // Simply write some test data for now
        fmt.Fprint(w, "Welcome!\n")
    })	
	
	
	l, err := net.Listen("tcp", "localhost:" + myport)
	if err != nil {
	    log.Fatal(err)
	}
	// The browser can connect now because the listening socket is open.
	
	
	//err = open.Start("http://localhost:"+ myport + "/test")
	err = open.Start("http://localhost:"+ myport + "/ip")
	if err != nil {
	     log.Println(err)
	}
	
	// Start the blocking server loop.
	log.Fatal(http.Serve(l, r)) 
}

Solution 3 - Http

This is how I come up with the IP

func ReadUserIP(r *http.Request) string {
	IPAddress := r.Header.Get("X-Real-Ip")
	if IPAddress == "" {
		IPAddress = r.Header.Get("X-Forwarded-For")
	}
	if IPAddress == "" {
		IPAddress = r.RemoteAddr
	}
	return IPAddress
}
  • X-Real-Ip - fetches first true IP (if the requests sits behind multiple NAT sources/load balancer)

  • X-Forwarded-For - if for some reason X-Real-Ip is blank and does not return response, get from X-Forwarded-For

  • Remote Address - last resort (usually won't be reliable as this might be the last ip or if it is a naked http request to server ie no load balancer)

Solution 4 - Http

> In PHP there are a lot of variables that I should check. Is it the same on Go?

This has nothing to do with Go (or PHP for that matter). It just depends on what the client, proxy, load-balancer, or server is sending. Get the one you need depending on your environment.

http.Request.RemoteAddr contains the remote IP address. It may or may not be your actual client.

> And is the request case sensitive? for example x-forwarded-for is the same as X-Forwarded-For and X-FORWARDED-FOR? (from req.Header.Get("X-FORWARDED-FOR"))

No, why not try it yourself? http://play.golang.org/p/YMf_UBvDsH

Solution 5 - Http

According to Mozilla MDN: "The X-Forwarded-For (XFF) header is a de-facto standard header for identifying the originating IP address of a client."
They publish clear information in their X-Forwarded-For article.

Solution 6 - Http

I think I have a better way than the current method posted.

package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strings"
)

func main() {
	http.HandleFunc("/", getUserIP)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

// Get the IP address of the server's connected user.
func getUserIP(httpWriter http.ResponseWriter, httpServer *http.Request) {
	var userIP string
	if len(httpServer.Header.Get("CF-Connecting-IP")) > 1 {
		userIP = httpServer.Header.Get("CF-Connecting-IP")
		fmt.Println(net.ParseIP(userIP))
	} else if len(httpServer.Header.Get("X-Forwarded-For")) > 1 {
		userIP = httpServer.Header.Get("X-Forwarded-For")
		fmt.Println(net.ParseIP(userIP))
	} else if len(httpServer.Header.Get("X-Real-IP")) > 1 {
		userIP = httpServer.Header.Get("X-Real-IP")
		fmt.Println(net.ParseIP(userIP))
	} else {
		userIP = httpServer.RemoteAddr
		if strings.Contains(userIP, ":") {
			fmt.Println(net.ParseIP(strings.Split(userIP, ":")[0]))
		} else {
			fmt.Println(net.ParseIP(userIP))
		}
	}
}

Solution 7 - Http

The client can set the X-Forwarded-For header to any arbitrary value it wants. Usage X-Forwarded-For without check trusted proxies may lead to ip spoofing.

Header example: X-Forwarded-For: <client>, <proxy1>, <proxy2>

For example someone may call:

curl -H "X-Forwarded-For: 136.226.254.1" -H "X-Real-Ip: 136.226.254.2" "http://super.com"

If your L7 balancer don't check and cleanup this headers you will get ip spoofing in your code (136.226.254.1). If you have some logic based on client IP addresses, it won't work correctly. Throttling based on ip for example.

For example nginx module http://nginx.org/ru/docs/http/ngx_http_realip_module.html used logic based on getting last untrusted ip address in chain X-Forwarded-For addresses. I didn't find right middleware for go with same logic and write it: https://github.com/thrownew/go-middlewares/tree/main/clientip

Solution 8 - Http

When using Cloudfront the client IP address is in the x-original-forwarded-for. Below is a Javascript example.

function getIp(request) {
  const { headers, connection, socket } = request
  const connectionSocket = connection && connection.socket

  return (
    (headers && headers['x-original-forwarded-for']) ||
    (connection && connection.remoteAddress) ||
    (socket && socket.remoteAddress) ||
    (connectionSocket && connectionSocket.remoteAddress) ||
    null
  )
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKokizzuView Question on Stackoverflow
Solution 1 - HttptomaszView Answer on Stackoverflow
Solution 2 - HttpStefan SteigerView Answer on Stackoverflow
Solution 3 - Httpmel3kingsView Answer on Stackoverflow
Solution 4 - HttpJimBView Answer on Stackoverflow
Solution 5 - HttpJuan LanusView Answer on Stackoverflow
Solution 6 - HttpPrajwal KoiralaView Answer on Stackoverflow
Solution 7 - HttpthrownewView Answer on Stackoverflow
Solution 8 - HttpMarc RaazView Answer on Stackoverflow