diff --git a/.gitignore b/.gitignore
index 85427b9..5022fb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/data/
/custom.html
+/vendor/
diff --git a/.travis.yml b/.travis.yml
index 7ed2851..d0bc237 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,15 @@
-sudo: false
+language: minimal
-language: go
+services:
+ - docker
-go:
- - 1.x
- - tip
+script:
+ - make docker-build
+
+deploy:
+ - provider: script
+ script: make docker-push
+ - provider: heroku
+ app: ifconfig-co
+ api_key:
+ secure: IQG/ls5Zu0yua5Ynn5EL9JCPjo1/WcmS0z7BSaXWdgW+JIWFm7oF5z54bUZHl/q1tTuWzAJk59zSTYJijtQqh2Ssl3fLu3uFDwyJSrOuUu1akPlETam8NpdbH4lPkFp75JSDdDXV08c0APmeLL6gqRuTrUuufu69Wigjq4gLo+o=
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..711588b
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+# Build
+FROM golang:1.12-stretch AS build
+WORKDIR /go/src/github.com/mpolden/echoip
+COPY . .
+# Must build without cgo because libc is unavailable in runtime image
+ENV GO111MODULE=on CGO_ENABLED=0
+RUN make
+
+# Run
+FROM scratch
+EXPOSE 8080
+COPY --from=build \
+ /go/bin/echoip \
+ /go/src/github.com/mpolden/echoip/index.html \
+ /opt/echoip/
+WORKDIR /opt/echoip
+ENTRYPOINT ["/opt/echoip/echoip"]
diff --git a/Makefile b/Makefile
index 9036973..d1da7df 100644
--- a/Makefile
+++ b/Makefile
@@ -1,31 +1,48 @@
+DOCKER_IMAGE := mpolden/echoip
OS := $(shell uname)
ifeq ($(OS),Linux)
TAR_OPTS := --wildcards
endif
-all: deps test vet install
-
-fmt:
- go fmt ./...
-
-test:
- go test ./...
-
-vet:
- go vet ./...
+all: deps lint test install
deps:
- go get -d -v ./...
+ go get ./...
-install:
+test: deps
+ go test ./...
+
+vet: deps
+ go vet ./...
+
+check-fmt:
+ bash -c "diff --line-format='%L' <(echo -n) <(gofmt -d -s .)"
+
+lint: check-fmt vet
+
+install: deps
go install ./...
databases := GeoLite2-City GeoLite2-Country
$(databases):
mkdir -p data
- curl -fsSL -m 30 http://geolite.maxmind.com/download/geoip/database/$@.tar.gz | tar $(TAR_OPTS) --strip-components=1 -C $(PWD)/data -xzf - '*.mmdb'
+ curl -fsSL -m 30 https://geolite.maxmind.com/download/geoip/database/$@.tar.gz | tar $(TAR_OPTS) --strip-components=1 -C $(CURDIR)/data -xzf - '*.mmdb'
test ! -f data/GeoLite2-City.mmdb || mv data/GeoLite2-City.mmdb data/city.mmdb
test ! -f data/GeoLite2-Country.mmdb || mv data/GeoLite2-Country.mmdb data/country.mmdb
geoip-download: $(databases)
+
+docker-build:
+ docker build -t $(DOCKER_IMAGE) .
+
+docker-login:
+ @echo "$(DOCKER_PASSWORD)" | docker login -u "$(DOCKER_USERNAME)" --password-stdin
+
+docker-test:
+ $(eval CONTAINER=$(shell docker run --rm --detach --publish-all $(DOCKER_IMAGE)))
+ $(eval DOCKER_PORT=$(shell docker port $(CONTAINER) | cut -d ":" -f 2))
+ curl -fsS -m 5 localhost:$(DOCKER_PORT) > /dev/null; docker stop $(CONTAINER)
+
+docker-push: docker-test docker-login
+ docker push $(DOCKER_IMAGE)
diff --git a/README.md b/README.md
index 7652809..f41ec21 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# ipd
+# echoip
-[![Build Status](https://travis-ci.org/mpolden/ipd.svg)](https://travis-ci.org/mpolden/ipd)
+[![Build Status](https://travis-ci.org/mpolden/echoip.svg)](https://travis-ci.org/mpolden/echoip)
A simple service for looking up your IP address. This is the code that powers
https://ifconfig.co.
@@ -66,9 +66,6 @@ $ curl ifconfig.co/port/80
Pass the appropriate flag (usually `-4` and `-6`) to your client to switch
between IPv4 and IPv6 lookup.
-The subdomains https://v4.ifconfig.co and https://v6.ifconfig.co can be used to
-force IPv4 or IPv6 lookup.
-
## Features
* Easy to remember domain name
@@ -92,17 +89,24 @@ force IPv4 or IPv6 lookup.
Compiling requires the [Golang compiler](https://golang.org/) to be installed.
This package can be installed with `go get`:
-`go get github.com/mpolden/ipd/...`
+`go get github.com/mpolden/echoip/...`
For more information on building a Go project, see the [official Go
documentation](https://golang.org/doc/code.html).
+## Docker image
+
+A Docker image is available on [Docker
+Hub](https://hub.docker.com/r/mpolden/echoip), which can be downloaded with:
+
+`docker pull mpolden/echoip`
+
### Usage
```
-$ ipd -h
+$ echoip -h
Usage:
- ipd [OPTIONS]
+ echoip [OPTIONS]
Application Options:
-f, --country-db=FILE Path to GeoIP country database
diff --git a/cmd/echoip/main.go b/cmd/echoip/main.go
new file mode 100644
index 0000000..9f18681
--- /dev/null
+++ b/cmd/echoip/main.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "log"
+
+ flags "github.com/jessevdk/go-flags"
+
+ "os"
+
+ "github.com/mpolden/echoip/http"
+ "github.com/mpolden/echoip/iputil"
+ "github.com/mpolden/echoip/iputil/geo"
+)
+
+func main() {
+ var opts struct {
+ CountryDBPath string `short:"f" long:"country-db" description:"Path to GeoIP country database" value-name:"FILE" default:""`
+ CityDBPath string `short:"c" long:"city-db" description:"Path to GeoIP city database" value-name:"FILE" default:""`
+ ASNDBPath string `short:"a" long:"asn-db" description:"Path to GeoIP ASN database" value-name:"FILE" default:""`
+ Listen string `short:"l" long:"listen" description:"Listening address" value-name:"ADDR" default:":8080"`
+ ReverseLookup bool `short:"r" long:"reverse-lookup" description:"Perform reverse hostname lookups"`
+ PortLookup bool `short:"p" long:"port-lookup" description:"Enable port lookup"`
+ Template string `short:"t" long:"template" description:"Path to template" default:"index.html" value-name:"FILE"`
+ IPHeaders []string `short:"H" long:"trusted-header" description:"Header to trust for remote IP, if present (e.g. X-Real-IP)" value-name:"NAME"`
+ }
+ _, err := flags.ParseArgs(&opts, os.Args)
+ if err != nil {
+ os.Exit(1)
+ }
+
+ log := log.New(os.Stderr, "echoip: ", 0)
+ r, err := geo.Open(opts.CountryDBPath, opts.CityDBPath, opts.ASNDBPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ server := http.New(r)
+ server.IPHeaders = opts.IPHeaders
+ if _, err := os.Stat(opts.Template); err == nil {
+ server.Template = opts.Template
+ } else {
+ log.Printf("Not configuring default handler: Template not found: %s", opts.Template)
+ }
+ if opts.ReverseLookup {
+ log.Println("Enabling reverse lookup")
+ server.LookupAddr = iputil.LookupAddr
+ }
+ if opts.PortLookup {
+ log.Println("Enabling port lookup")
+ server.LookupPort = iputil.LookupPort
+ }
+ if len(opts.IPHeaders) > 0 {
+ log.Printf("Trusting header(s) %+v to contain correct remote IP", opts.IPHeaders)
+ }
+
+ listen := opts.Listen
+ if listen == ":8080" {
+ listen = "0.0.0.0:8080"
+ }
+ log.Printf("Listening on http://%s", listen)
+ if err := server.ListenAndServe(opts.Listen); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/cmd/ipd/main.go b/cmd/ipd/main.go
deleted file mode 100644
index 9710dfc..0000000
--- a/cmd/ipd/main.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package main
-
-import (
- "log"
-
- flags "github.com/jessevdk/go-flags"
-
- "os"
-
- "github.com/mpolden/ipd/http"
- "github.com/mpolden/ipd/iputil"
- "github.com/mpolden/ipd/iputil/database"
-)
-
-func main() {
- var opts struct {
- CountryDBPath string `short:"f" long:"country-db" description:"Path to GeoIP country database" value-name:"FILE" default:""`
- CityDBPath string `short:"c" long:"city-db" description:"Path to GeoIP city database" value-name:"FILE" default:""`
- ASNDBPath string `short:"a" long:"asn-db" description:"Path to GeoIP ASN database" value-name:"FILE" default:""`
- Listen string `short:"l" long:"listen" description:"Listening address" value-name:"ADDR" default:":8080"`
- ReverseLookup bool `short:"r" long:"reverse-lookup" description:"Perform reverse hostname lookups"`
- PortLookup bool `short:"p" long:"port-lookup" description:"Enable port lookup"`
- Template string `short:"t" long:"template" description:"Path to template" default:"index.html" value-name:"FILE"`
- IPHeader string `short:"H" long:"trusted-header" description:"Header to trust for remote IP, if present (e.g. X-Real-IP)" value-name:"NAME"`
- }
- _, err := flags.ParseArgs(&opts, os.Args)
- if err != nil {
- os.Exit(1)
- }
-
- log := log.New(os.Stderr, "ipd: ", 0)
- db, err := database.New(opts.CountryDBPath, opts.CityDBPath, opts.ASNDBPath)
- if err != nil {
- log.Fatal(err)
- }
-
- server := http.New(db)
- server.Template = opts.Template
- server.IPHeader = opts.IPHeader
- if opts.ReverseLookup {
- log.Println("Enabling reverse lookup")
- server.LookupAddr = iputil.LookupAddr
- }
- if opts.PortLookup {
- log.Println("Enabling port lookup")
- server.LookupPort = iputil.LookupPort
- }
- if opts.IPHeader != "" {
- log.Printf("Trusting header %s to contain correct remote IP", opts.IPHeader)
- }
-
- log.Printf("Listening on http://%s", opts.Listen)
- if err := server.ListenAndServe(opts.Listen); err != nil {
- log.Fatal(err)
- }
-}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..2d40e34
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,13 @@
+// +heroku install ./...
+// +heroku goVersion go1.12
+module github.com/mpolden/echoip
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/jessevdk/go-flags v1.4.0
+ github.com/oschwald/geoip2-golang v1.2.1
+ github.com/oschwald/maxminddb-golang v1.2.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/stretchr/testify v1.2.2 // indirect
+ golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..673f1b2
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,14 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
+github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
+github.com/oschwald/maxminddb-golang v1.2.1 h1:1wUyw1BYyCY7E0bbG8lD7P5aPDFIsRr611otw6LOJtM=
+github.com/oschwald/maxminddb-golang v1.2.1/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5 h1:MF92a0wJ3gzSUVBpjcwdrDr5+klMFRNEEu6Mev4n00I=
+golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/heroku.yml b/heroku.yml
new file mode 100644
index 0000000..0d550a5
--- /dev/null
+++ b/heroku.yml
@@ -0,0 +1,7 @@
+build:
+ languages:
+ - go
+ pre:
+ - make geoip-download
+run:
+ web: echoip -f data/country.mmdb -c data/city.mmdb -p -r -H CF-Connecting-IP -H X-Forwarded-For -l :$PORT
diff --git a/http/http.go b/http/http.go
index 05017d3..3ca9965 100644
--- a/http/http.go
+++ b/http/http.go
@@ -5,11 +5,13 @@ import (
"fmt"
"html/template"
"path/filepath"
+ "strings"
- "github.com/mpolden/ipd/iputil"
- "github.com/mpolden/ipd/iputil/database"
- "github.com/mpolden/ipd/useragent"
+ "github.com/mpolden/echoip/iputil"
+ "github.com/mpolden/echoip/iputil/geo"
+ "github.com/mpolden/echoip/useragent"
+ "math/big"
"net"
"net/http"
"strconv"
@@ -22,22 +24,22 @@ const (
type Server struct {
Template string
- IPHeader string
+ IPHeaders []string
LookupAddr func(net.IP) (string, error)
LookupPort func(net.IP, uint64) error
- db database.Client
+ gr geo.Reader
}
type Response struct {
- IP net.IP `json:"ip"`
- IPDecimal uint64 `json:"ip_decimal"`
- Country string `json:"country,omitempty"`
- CountryISO string `json:"country_iso,omitempty"`
- IsInEuropeanUnion bool `json:"is_in_european_union,omitempty"`
- City string `json:"city,omitempty"`
- Hostname string `json:"hostname,omitempty"`
- LocationLatitude float64 `json:"location_latitude,omitempty"`
- LocationLongitude float64 `json:"location_longitude,omitempty"`
+ IP net.IP `json:"ip"`
+ IPDecimal *big.Int `json:"ip_decimal"`
+ Country string `json:"country,omitempty"`
+ CountryEU *bool `json:"country_eu,omitempty"`
+ CountryISO string `json:"country_iso,omitempty"`
+ City string `json:"city,omitempty"`
+ Hostname string `json:"hostname,omitempty"`
+ Latitude float64 `json:"latitude,omitempty"`
+ Longitude float64 `json:"longitude,omitempty"`
AutonomousSystemNumber string `json:"asn_number,omitempty"`
AutonomousSystemOrganization string `json:"asn_organization,omitempty"`
}
@@ -48,12 +50,29 @@ type PortResponse struct {
Reachable bool `json:"reachable"`
}
-func New(db database.Client) *Server {
- return &Server{db: db}
+func New(db geo.Reader) *Server {
+ return &Server{gr: db}
}
-func ipFromRequest(header string, r *http.Request) (net.IP, error) {
- remoteIP := r.Header.Get(header)
+func ipFromForwardedForHeader(v string) string {
+ sep := strings.Index(v, ",")
+ if sep == -1 {
+ return v
+ }
+ return v[:sep]
+}
+
+func ipFromRequest(headers []string, r *http.Request) (net.IP, error) {
+ remoteIP := ""
+ for _, header := range headers {
+ remoteIP = r.Header.Get(header)
+ if http.CanonicalHeaderKey(header) == "X-Forwarded-For" {
+ remoteIP = ipFromForwardedForHeader(remoteIP)
+ }
+ if remoteIP != "" {
+ break
+ }
+ }
if remoteIP == "" {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
@@ -69,14 +88,14 @@ func ipFromRequest(header string, r *http.Request) (net.IP, error) {
}
func (s *Server) newResponse(r *http.Request) (Response, error) {
- ip, err := ipFromRequest(s.IPHeader, r)
+ ip, err := ipFromRequest(s.IPHeaders, r)
if err != nil {
return Response{}, err
}
ipDecimal := iputil.ToDecimal(ip)
- country, _ := s.db.Country(ip)
- city, _ := s.db.City(ip)
- asn, _ := s.db.ASN(ip)
+ country, _ := s.gr.Country(ip)
+ city, _ := s.gr.City(ip)
+ asn, _ := s.gr.ASN(ip)
var hostname string
if s.LookupAddr != nil {
hostname, _ = s.LookupAddr(ip)
@@ -86,15 +105,15 @@ func (s *Server) newResponse(r *http.Request) (Response, error) {
autonomousSystemNumber = "AS" + strconv.FormatUint(uint64(asn.AutonomousSystemNumber), 10);
}
return Response{
- IP: ip,
- IPDecimal: ipDecimal,
- Country: country.Name,
- CountryISO: country.ISO,
- IsInEuropeanUnion: country.IsInEuropeanUnion,
- City: city.Name,
- Hostname: hostname,
- LocationLatitude: city.Latitude,
- LocationLongitude: city.Longitude,
+ IP: ip,
+ IPDecimal: ipDecimal,
+ Country: country.Name,
+ CountryISO: country.ISO,
+ CountryEU: country.IsEU,
+ City: city.Name,
+ Hostname: hostname,
+ Latitude: city.Latitude,
+ Longitude: city.Longitude,
AutonomousSystemNumber: autonomousSystemNumber,
AutonomousSystemOrganization: asn.AutonomousSystemOrganization,
}, nil
@@ -103,10 +122,10 @@ func (s *Server) newResponse(r *http.Request) (Response, error) {
func (s *Server) newPortResponse(r *http.Request) (PortResponse, error) {
lastElement := filepath.Base(r.URL.Path)
port, err := strconv.ParseUint(lastElement, 10, 16)
- if err != nil || port < 1 || port > 65355 {
- return PortResponse{Port: port}, fmt.Errorf("invalid port: %d", port)
+ if err != nil || port < 1 || port > 65535 {
+ return PortResponse{Port: port}, fmt.Errorf("invalid port: %s", lastElement)
}
- ip, err := ipFromRequest(s.IPHeader, r)
+ ip, err := ipFromRequest(s.IPHeaders, r)
if err != nil {
return PortResponse{Port: port}, err
}
@@ -119,7 +138,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.IPHeader, r)
+ ip, err := ipFromRequest(s.IPHeaders, r)
if err != nil {
return internalServerError(err)
}
@@ -159,11 +178,7 @@ func (s *Server) CLICoordinatesHandler(w http.ResponseWriter, r *http.Request) *
if err != nil {
return internalServerError(err)
}
- var str string
- str += FloatToString(response.LocationLatitude)
- str += ","
- str += FloatToString(response.LocationLongitude)
- fmt.Fprintln(w, str)
+ fmt.Fprintf(w, "%s,%s\n", formatCoordinate(response.Latitude), formatCoordinate(response.Longitude))
return nil
}
@@ -181,10 +196,16 @@ func (s *Server) JSONHandler(w http.ResponseWriter, r *http.Request) *appError {
return nil
}
+func (s *Server) HealthHandler(w http.ResponseWriter, r *http.Request) *appError {
+ w.Header().Set("Content-Type", jsonMediaType)
+ w.Write([]byte(`{"status":"OK"}`))
+ return nil
+}
+
func (s *Server) PortHandler(w http.ResponseWriter, r *http.Request) *appError {
response, err := s.newPortResponse(r)
if err != nil {
- return badRequest(err).WithMessage(fmt.Sprintf("Invalid port: %d", response.Port)).AsJSON()
+ return badRequest(err).WithMessage(err.Error()).AsJSON()
}
b, err := json.Marshal(response)
if err != nil {
@@ -210,12 +231,20 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro
}
var data = struct {
Response
- Host string
- JSON string
- Port bool
+ Host string
+ BoxLatTop float64
+ BoxLatBottom float64
+ BoxLonLeft float64
+ BoxLonRight float64
+ JSON string
+ Port bool
}{
response,
r.Host,
+ response.Latitude + 0.05,
+ response.Latitude - 0.05,
+ response.Longitude - 0.05,
+ response.Longitude + 0.05,
string(json),
s.LookupPort != nil,
}
@@ -269,6 +298,9 @@ func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (s *Server) Handler() http.Handler {
r := NewRouter()
+ // Health
+ r.Route("GET", "/health", s.HealthHandler)
+
// JSON
r.Route("GET", "/", s.JSONHandler).Header("Accept", jsonMediaType)
r.Route("GET", "/json", s.JSONHandler)
@@ -277,7 +309,7 @@ func (s *Server) Handler() http.Handler {
r.Route("GET", "/", s.CLIHandler).MatcherFunc(cliMatcher)
r.Route("GET", "/", s.CLIHandler).Header("Accept", textMediaType)
r.Route("GET", "/ip", s.CLIHandler)
- if !s.db.IsEmpty() {
+ if !s.gr.IsEmpty() {
r.Route("GET", "/country", s.CLICountryHandler)
r.Route("GET", "/country-iso", s.CLICountryISOHandler)
r.Route("GET", "/city", s.CLICityHandler)
@@ -285,7 +317,9 @@ func (s *Server) Handler() http.Handler {
}
// Browser
- r.Route("GET", "/", s.DefaultHandler)
+ if s.Template != "" {
+ r.Route("GET", "/", s.DefaultHandler)
+ }
// Port testing
if s.LookupPort != nil {
@@ -299,6 +333,6 @@ func (s *Server) ListenAndServe(addr string) error {
return http.ListenAndServe(addr, s.Handler())
}
-func FloatToString(input_num float64) string {
- return strconv.FormatFloat(input_num, 'f', 6, 64)
+func formatCoordinate(c float64) string {
+ return strconv.FormatFloat(c, 'f', 6, 64)
}
diff --git a/http/http_test.go b/http/http_test.go
index ade01a7..e1494c1 100644
--- a/http/http_test.go
+++ b/http/http_test.go
@@ -8,7 +8,7 @@ import (
"net/http/httptest"
"testing"
- "github.com/mpolden/ipd/iputil/database"
+ "github.com/mpolden/echoip/iputil/geo"
)
func lookupAddr(net.IP) (string, error) { return "localhost", nil }
@@ -16,8 +16,8 @@ func lookupPort(net.IP, uint64) error { return nil }
type testDb struct{}
-func (t *testDb) Country(net.IP) (database.Country, error) {
- return database.Country{Name: "Elbonia", ISO: "EB"}, nil
+func (t *testDb) Country(net.IP) (geo.Country, error) {
+ return geo.Country{Name: "Elbonia", ISO: "EB", IsEU: new(bool)}, nil
}
func (t *testDb) City(net.IP) (database.City, error) {
@@ -31,7 +31,7 @@ func (t *testDb) ASN(net.IP) (database.ASN, error) {
func (t *testDb) IsEmpty() bool { return false }
func testServer() *Server {
- return &Server{db: &testDb{}, LookupAddr: lookupAddr, LookupPort: lookupPort}
+ return &Server{gr: &testDb{}, LookupAddr: lookupAddr, LookupPort: lookupPort}
}
func httpGet(url string, acceptMediaType string, userAgent string) (string, int, error) {
@@ -71,6 +71,7 @@ func TestCLIHandlers(t *testing.T) {
{s.URL + "/ip", "127.0.0.1\n", 200, "", ""},
{s.URL + "/country", "Elbonia\n", 200, "", ""},
{s.URL + "/country-iso", "EB\n", 200, "", ""},
+ {s.URL + "/coordinates", "63.416667,10.416667\n", 200, "", ""},
{s.URL + "/city", "Bornyasherk\n", 200, "", ""},
{s.URL + "/foo", "404 page not found", 404, "", ""},
}
@@ -94,7 +95,7 @@ func TestDisabledHandlers(t *testing.T) {
server := testServer()
server.LookupPort = nil
server.LookupAddr = nil
- server.db, _ = database.New("", "", "")
+ server.gr, _ = geo.Open("", "", "")
s := httptest.NewServer(server.Handler())
var tests = []struct {
@@ -132,12 +133,13 @@ func TestJSONHandlers(t *testing.T) {
out string
status int
}{
- {s.URL, `{"ip":"127.0.0.1","ip_decimal":2130706433,"country":"Elbonia","country_iso":"EB","city":"Bornyasherk","hostname":"localhost","asn_number":"AS59795","asn_organization":"Hosting4Real"}`, 200},
- {s.URL + "/port/foo", `{"error":"Invalid port: 0"}`, 400},
- {s.URL + "/port/0", `{"error":"Invalid port: 0"}`, 400},
- {s.URL + "/port/65356", `{"error":"Invalid port: 65356"}`, 400},
+ {s.URL, `{"ip":"127.0.0.1","ip_decimal":2130706433,"country":"Elbonia","country_eu":false,"country_iso":"EB","city":"Bornyasherk","hostname":"localhost","latitude":63.416667,"longitude":10.416667,"asn_number":"AS59795","asn_organization":"Hosting4Real"}`, 200},
+ {s.URL + "/port/foo", `{"error":"invalid port: foo"}`, 400},
+ {s.URL + "/port/0", `{"error":"invalid port: 0"}`, 400},
+ {s.URL + "/port/65537", `{"error":"invalid port: 65537"}`, 400},
{s.URL + "/port/31337", `{"ip":"127.0.0.1","port":31337,"reachable":true}`, 200},
{s.URL + "/foo", `{"error":"404 page not found"}`, 404},
+ {s.URL + "/health", `{"status":"OK"}`, 200},
}
for _, tt := range tests {
@@ -156,16 +158,20 @@ func TestJSONHandlers(t *testing.T) {
func TestIPFromRequest(t *testing.T) {
var tests = []struct {
- remoteAddr string
- headerKey string
- headerValue string
- trustedHeader string
- out string
+ remoteAddr string
+ headerKey string
+ headerValue string
+ trustedHeaders []string
+ out string
}{
- {"127.0.0.1:9999", "", "", "", "127.0.0.1"}, // No header given
- {"127.0.0.1:9999", "X-Real-IP", "1.3.3.7", "", "127.0.0.1"}, // Trusted header is empty
- {"127.0.0.1:9999", "X-Real-IP", "1.3.3.7", "X-Foo-Bar", "127.0.0.1"}, // Trusted header does not match
- {"127.0.0.1:9999", "X-Real-IP", "1.3.3.7", "X-Real-IP", "1.3.3.7"}, // Trusted header matches
+ {"127.0.0.1:9999", "", "", nil, "127.0.0.1"}, // No header given
+ {"127.0.0.1:9999", "X-Real-IP", "1.3.3.7", nil, "127.0.0.1"}, // Trusted header is empty
+ {"127.0.0.1:9999", "X-Real-IP", "1.3.3.7", []string{"X-Foo-Bar"}, "127.0.0.1"}, // Trusted header does not match
+ {"127.0.0.1:9999", "X-Real-IP", "1.3.3.7", []string{"X-Real-IP", "X-Forwarded-For"}, "1.3.3.7"}, // Trusted header matches
+ {"127.0.0.1:9999", "X-Forwarded-For", "1.3.3.7", []string{"X-Real-IP", "X-Forwarded-For"}, "1.3.3.7"}, // Second trusted header matches
+ {"127.0.0.1:9999", "X-Forwarded-For", "1.3.3.7,4.2.4.2", []string{"X-Forwarded-For"}, "1.3.3.7"}, // X-Forwarded-For with multiple entries (commas separator)
+ {"127.0.0.1:9999", "X-Forwarded-For", "1.3.3.7, 4.2.4.2", []string{"X-Forwarded-For"}, "1.3.3.7"}, // X-Forwarded-For with multiple entries (space+comma separator)
+ {"127.0.0.1:9999", "X-Forwarded-For", "", []string{"X-Forwarded-For"}, "127.0.0.1"}, // Empty header
}
for _, tt := range tests {
r := &http.Request{
@@ -173,7 +179,7 @@ func TestIPFromRequest(t *testing.T) {
Header: http.Header{},
}
r.Header.Add(tt.headerKey, tt.headerValue)
- ip, err := ipFromRequest(tt.trustedHeader, r)
+ ip, err := ipFromRequest(tt.trustedHeaders, r)
if err != nil {
t.Fatal(err)
}
diff --git a/index.html b/index.html
index d5b1d70..39f5c2c 100644
--- a/index.html
+++ b/index.html
@@ -10,6 +10,7 @@
+