5

I'm using golang http package. How could the server limit client IP address?

func (s *Worker) Run(c chan error) {
    apiMux := http.NewServeMux()
    apiMux.HandleFunc("/test", s.test)
    apiMux.HandleFunc("/block/create", s.CreateBlock)
    apiMux.HandleFunc("/block/delete", s.DeleteBlock)

    apiServer := &http.Server{
        Addr:    "0.0.0.0:" + strconv.Itoa(s.ListenPort),
        Handler: apiMux,
    }

    go func() {
        log.Println("Worker listening on " + apiServer.Addr)
        c <- apiServer.ListenAndServe()
    }()
}
icza
  • 389,944
  • 63
  • 907
  • 827
Will Lee
  • 71
  • 1
  • 2
  • Possible duplicate of [How to limit the connection count of an HTTP Server implemented in go?](http://stackoverflow.com/questions/22625367/how-to-limit-the-connection-count-of-an-http-server-implemented-in-go) – Tinwor Jun 18 '16 at 12:49
  • @Tinwor not really a duplicate of that – Not_a_Golfer Jun 18 '16 at 13:08
  • Similar question: http://stackoverflow.com/questions/28070923/golang-net-http-and-gorilla-run-code-before-handler – Charlie Tumahai Jun 18 '16 at 14:29

1 Answers1

11

You need to do two things: one is to wrap your mux with a middleware handler that pre-processes your requests and validates the IP. The other is get the real IP of the user, which is important if you are behind a firewall or load balancer (resulting in the address being always that of the LB), or if your user is behind a proxy.

As for wrapping your mux, it's pretty simple:

apiServer := &http.Server{
    Addr:    "0.0.0.0:8080",
    Handler: http.HandlerFunc( func(w http.ResponseWriter, req *http.Request) {
        // get the real IP of the user, see below
        addr := getRealAddr(req)

       // the actual vaildation - replace with whatever you want
       if (addr != "1.2.3.4") {
            http.Error(w, "Blocked", 401)
            return
        }
        // pass the request to the mux
        apiMux.ServeHTTP(w,req)
    }),
}

And I'm attaching the getRealAddr function which is from an actual project in which I did something like this:

func getRealAddr(r *http.Request)  string {

    remoteIP := ""
    // the default is the originating ip. but we try to find better options because this is almost
    // never the right IP
    if parts := strings.Split(r.RemoteAddr, ":"); len(parts) == 2 {
        remoteIP = parts[0]
    }
    // If we have a forwarded-for header, take the address from there
    if xff := strings.Trim(r.Header.Get("X-Forwarded-For"), ","); len(xff) > 0 {
        addrs := strings.Split(xff, ",")
        lastFwd := addrs[len(addrs)-1]
        if ip := net.ParseIP(lastFwd); ip != nil {
            remoteIP = ip.String()
        }
    // parse X-Real-Ip header
    } else if xri := r.Header.Get("X-Real-Ip"); len(xri) > 0 {
        if ip := net.ParseIP(xri); ip != nil {
            remoteIP = ip.String()
        }
    }

    return remoteIP

}

As for the filtering, it can be based on a set of ips, or CIDR ranges, it's up to you of course.

If you're interested, the above code is from an API building toolkit I wrote and used called Vertex, which has this built in: https://github.com/EverythingMe/vertex

Not_a_Golfer
  • 47,012
  • 14
  • 126
  • 92
  • Why do you use the last forward instead of the first? – lf215 Apr 23 '17 at 07:39
  • 1
    @lf215 the first one might be an internal LAN address or alike. The last one should be the address seen by your reverse proxy or LB, and is usually what you want for things like country filtering, i.e. the client's ISP-given address. – Not_a_Golfer Apr 26 '17 at 12:37
  • Quoting Wikipedia on that subject: `The last IP address is always the IP address that connects to the last proxy, which means it is the most reliable source of information`. https://en.wikipedia.org/wiki/X-Forwarded-For#Format – Not_a_Golfer Apr 26 '17 at 12:38
  • 3
    one thing to note is that if the machine that this code is running on has ipv6 enabled, the address may be in the `[::]:` form. For example, when I serve and request locally, r.RemoteAddr returns `[::1]:32445` (port will likely differ) – pulse0ne Jan 31 '18 at 16:27