diff --git a/README.md b/README.md index 71a1714f..8d4629b2 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Usage of echoip: Path to GeoIP country database -l string Listening address (default ":8080") + -L Disable custom IP lookup -p Enable port lookup -r Perform reverse hostname lookups -s Show sponsor logo diff --git a/cmd/echoip/main.go b/cmd/echoip/main.go index f3d4c945..20a9f9e3 100644 --- a/cmd/echoip/main.go +++ b/cmd/echoip/main.go @@ -35,6 +35,7 @@ func main() { listen := flag.String("l", ":8080", "Listening address") reverseLookup := flag.Bool("r", false, "Perform reverse hostname lookups") portLookup := flag.Bool("p", false, "Enable port lookup") + noCustomIP := flag.Bool("L", false, "Disable custom IP lookup") template := flag.String("t", "html", "Path to template dir") cacheSize := flag.Int("C", 0, "Size of response cache. Set to 0 to disable") profile := flag.Bool("P", false, "Enables profiling handlers") @@ -71,6 +72,10 @@ func main() { log.Println("Enabling sponsor logo") server.Sponsor = *sponsor } + if *noCustomIP { + log.Println("Disabling custom IP lookup") + server.NoCustomIP = true + } if len(headers) > 0 { log.Printf("Trusting remote IP from header(s): %s", headers.String()) } diff --git a/html/index.html b/html/index.html index dc4ecece..5720068b 100644 --- a/html/index.html +++ b/html/index.html @@ -31,6 +31,11 @@ {{ end }}

❯ curl {{ .Host }}

+ {{ if .NoCustomIP }} +
+ +
+ {{ else }}
@@ -40,6 +45,7 @@

❯ curl {{ .Host }}

{{ end }}
+ {{ end }}

{{- if .City }}{{ .City }}{{ if .Country }}, {{ end }}{{ end -}} {{- .Country -}} @@ -222,7 +228,9 @@

Plain text

JSON

diff --git a/http/http.go b/http/http.go index a6bcb5f0..f0ec914a 100644 --- a/http/http.go +++ b/http/http.go @@ -35,6 +35,7 @@ type Server struct { gr geo.Reader profile bool Sponsor bool + NoCustomIP bool } type Response struct { @@ -129,7 +130,7 @@ func userAgentFromRequest(r *http.Request) *useragent.UserAgent { } func (s *Server) newResponse(r *http.Request) (Response, error) { - ip, err := ipFromRequest(s.IPHeaders, r, true) + ip, err := ipFromRequest(s.IPHeaders, r, !s.NoCustomIP) if err != nil { return Response{}, err } @@ -193,7 +194,7 @@ func (s *Server) newPortResponse(r *http.Request) (PortResponse, error) { } func (s *Server) CLIHandler(w http.ResponseWriter, r *http.Request) *appError { - ip, err := ipFromRequest(s.IPHeaders, r, true) + ip, err := ipFromRequest(s.IPHeaders, r, !s.NoCustomIP) if err != nil { return badRequest(err).WithMessage(err.Error()).AsJSON() } @@ -364,6 +365,7 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro Port bool Sponsor bool ExplicitLookup bool + NoCustomIP bool }{ response, r.Host, @@ -374,7 +376,8 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro string(json), s.LookupPort != nil, s.Sponsor, - r.URL.Query().Has("ip"), + !s.NoCustomIP && r.URL.Query().Has("ip"), + s.NoCustomIP, } if err := t.Execute(w, &data); err != nil { return internalServerError(err) diff --git a/http/http_test.go b/http/http_test.go index 458653d9..a0d970e4 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -284,6 +284,99 @@ func testIpFromRequest(t *testing.T, tests []ipTestCase) { } } +func TestIPFromRequestCustomIPDisabled(t *testing.T) { + var tests = []struct { + remoteAddr string + headerKey string + headerValue string + trustedHeaders []string + out string + }{ + // When customIP is false, ?ip= parameter should be ignored and remote addr used + {"127.0.0.1:9999?ip=1.2.3.4", "", "", nil, "127.0.0.1"}, + {"127.0.0.1:9999?ip=1.2.3.4", "X-Forwarded-For", "1.3.3.7,4.2.4.2", []string{"X-Forwarded-For"}, "1.3.3.7"}, + {"[::1]:9999?ip=::ffff:102:304", "", "", nil, "::1"}, + {"[::1]:9999?ip=::ffff:102:304", "X-Forwarded-For", "::ffff:103:307,::ffff:402:402", []string{"X-Forwarded-For"}, "::ffff:103:307"}, + // Without ?ip= parameter, behaviour is unchanged + {"127.0.0.1:9999", "", "", nil, "127.0.0.1"}, + {"127.0.0.1:9999", "X-Real-IP", "1.3.3.7", []string{"X-Real-IP"}, "1.3.3.7"}, + } + for _, tt := range tests { + u, err := url.Parse("http://" + tt.remoteAddr) + if err != nil { + t.Fatal(err) + } + r := &http.Request{ + RemoteAddr: u.Host, + Header: http.Header{}, + URL: u, + } + r.Header.Add(tt.headerKey, tt.headerValue) + ip, err := ipFromRequest(tt.trustedHeaders, r, false) + if err != nil { + t.Fatal(err) + } + out := net.ParseIP(tt.out) + if !ip.Equal(out) { + t.Errorf("Expected %s, got %s", out, ip) + } + } +} + +func TestCLIHandlersNoCustomIP(t *testing.T) { + log.SetOutput(ioutil.Discard) + server := testServer() + server.NoCustomIP = true + s := httptest.NewServer(server.Handler()) + + var tests = []struct { + url string + out string + status int + }{ + // ?ip= parameter should be ignored; server returns its own IP (127.0.0.1) + {s.URL + "/ip?ip=1.2.3.4", "127.0.0.1\n", 200}, + {s.URL + "/ip", "127.0.0.1\n", 200}, + {s.URL + "/country?ip=1.2.3.4", "Elbonia\n", 200}, + } + + for _, tt := range tests { + out, status, err := httpGet(tt.url, "", "") + if err != nil { + t.Fatal(err) + } + if status != tt.status { + t.Errorf("Expected %d for %s, got %d", tt.status, tt.url, status) + } + if out != tt.out { + t.Errorf("Expected %q for %s, got %q", tt.out, tt.url, out) + } + } +} + +func TestJSONHandlersNoCustomIP(t *testing.T) { + log.SetOutput(ioutil.Discard) + server := testServer() + server.NoCustomIP = true + s := httptest.NewServer(server.Handler()) + + // With NoCustomIP, ?ip=1.3.3.7 should be ignored; the response should + // contain the actual client IP (127.0.0.1), not the requested one. + out, status, err := httpGet(s.URL+"/json?ip=1.3.3.7", jsonMediaType, "curl/7.2.6.0") + if err != nil { + t.Fatal(err) + } + if status != 200 { + t.Errorf("Expected 200, got %d", status) + } + if !strings.Contains(out, `"ip": "127.0.0.1"`) { + t.Errorf("Expected response to contain 127.0.0.1, got %q", out) + } + if strings.Contains(out, "1.3.3.7") { + t.Errorf("Expected response to NOT contain 1.3.3.7, got %q", out) + } +} + func TestCLIMatcher(t *testing.T) { browserUserAgent := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.28 " +