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 }}
+ {{ end }}
{{- if .City }}{{ .City }}{{ if .Country }}, {{ end }}{{ end -}}
{{- .Country -}}
@@ -222,7 +228,9 @@
Plain text
- Set the
Accept: application/json header to request response as JSON.
+ {{ if not .NoCustomIP }}
- Append
?ip=IP to the request URL to lookup information for a different IP. This is not supported for the /port endpoint.
+ {{ end }}
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 " +