mirror of https://github.com/mpolden/echoip
Add support for city lookup
This commit is contained in:
parent
8027c55fdf
commit
8f71576357
|
@ -1,4 +1,2 @@
|
||||||
bin/
|
/GeoLite2-Country.mmdb
|
||||||
pkg/
|
/GeoLite2-City.mmdb
|
||||||
src/
|
|
||||||
/GeoLite2-Country.mmdb
|
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -15,5 +15,6 @@ deps:
|
||||||
install:
|
install:
|
||||||
go install
|
go install
|
||||||
|
|
||||||
get-geoip-db:
|
get-geoip-dbs:
|
||||||
curl -s http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz | gunzip > GeoLite2-Country.mmdb
|
curl -s http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz | gunzip > GeoLite2-Country.mmdb
|
||||||
|
curl -s http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz | gunzip > GeoLite2-City.mmdb
|
||||||
|
|
16
api/api.go
16
api/api.go
|
@ -32,6 +32,7 @@ type API struct {
|
||||||
type Response struct {
|
type Response struct {
|
||||||
IP net.IP `json:"ip"`
|
IP net.IP `json:"ip"`
|
||||||
Country string `json:"country,omitempty"`
|
Country string `json:"country,omitempty"`
|
||||||
|
City string `json:"city,omitempty"`
|
||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +74,10 @@ func (a *API) newResponse(r *http.Request) (Response, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
city, err := a.oracle.LookupCity(ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
hostnames, err := a.oracle.LookupAddr(ip.String())
|
hostnames, err := a.oracle.LookupAddr(ip.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
@ -80,6 +85,7 @@ func (a *API) newResponse(r *http.Request) (Response, error) {
|
||||||
return Response{
|
return Response{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
Country: country,
|
Country: country,
|
||||||
|
City: city,
|
||||||
Hostname: strings.Join(hostnames, " "),
|
Hostname: strings.Join(hostnames, " "),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -102,6 +108,15 @@ func (a *API) CLICountryHandler(w http.ResponseWriter, r *http.Request) *appErro
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *API) CLICityHandler(w http.ResponseWriter, r *http.Request) *appError {
|
||||||
|
response, err := a.newResponse(r)
|
||||||
|
if err != nil {
|
||||||
|
return internalServerError(err)
|
||||||
|
}
|
||||||
|
io.WriteString(w, response.City+"\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *API) JSONHandler(w http.ResponseWriter, r *http.Request) *appError {
|
func (a *API) JSONHandler(w http.ResponseWriter, r *http.Request) *appError {
|
||||||
response, err := a.newResponse(r)
|
response, err := a.newResponse(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -223,6 +238,7 @@ func (a *API) Handlers() http.Handler {
|
||||||
r.Handle("/", appHandler(a.CLIHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
r.Handle("/", appHandler(a.CLIHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
||||||
r.Handle("/ip", appHandler(a.CLIHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
r.Handle("/ip", appHandler(a.CLIHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
||||||
r.Handle("/country", appHandler(a.CLICountryHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
r.Handle("/country", appHandler(a.CLICountryHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
||||||
|
r.Handle("/city", appHandler(a.CLICityHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
||||||
|
|
||||||
// Browser
|
// Browser
|
||||||
r.Handle("/", appHandler(a.DefaultHandler)).Methods("GET")
|
r.Handle("/", appHandler(a.DefaultHandler)).Methods("GET")
|
||||||
|
|
|
@ -13,9 +13,11 @@ type mockOracle struct{}
|
||||||
|
|
||||||
func (r *mockOracle) LookupAddr(string) ([]string, error) { return []string{"localhost"}, nil }
|
func (r *mockOracle) LookupAddr(string) ([]string, error) { return []string{"localhost"}, nil }
|
||||||
func (r *mockOracle) LookupCountry(net.IP) (string, error) { return "Elbonia", nil }
|
func (r *mockOracle) LookupCountry(net.IP) (string, error) { return "Elbonia", nil }
|
||||||
|
func (r *mockOracle) LookupCity(net.IP) (string, error) { return "Bornyasherk", nil }
|
||||||
func (r *mockOracle) LookupPort(net.IP, uint64) error { return nil }
|
func (r *mockOracle) LookupPort(net.IP, uint64) error { return nil }
|
||||||
func (r *mockOracle) IsLookupAddrEnabled() bool { return true }
|
func (r *mockOracle) IsLookupAddrEnabled() bool { return true }
|
||||||
func (r *mockOracle) IsLookupCountryEnabled() bool { return true }
|
func (r *mockOracle) IsLookupCountryEnabled() bool { return true }
|
||||||
|
func (r *mockOracle) IsLookupCityEnabled() bool { return true }
|
||||||
func (r *mockOracle) IsLookupPortEnabled() bool { return true }
|
func (r *mockOracle) IsLookupPortEnabled() bool { return true }
|
||||||
|
|
||||||
func newTestAPI() *API {
|
func newTestAPI() *API {
|
||||||
|
@ -60,6 +62,7 @@ func TestClIHandlers(t *testing.T) {
|
||||||
{s.URL, "127.0.0.1\n", 200},
|
{s.URL, "127.0.0.1\n", 200},
|
||||||
{s.URL + "/ip", "127.0.0.1\n", 200},
|
{s.URL + "/ip", "127.0.0.1\n", 200},
|
||||||
{s.URL + "/country", "Elbonia\n", 200},
|
{s.URL + "/country", "Elbonia\n", 200},
|
||||||
|
{s.URL + "/city", "Bornyasherk\n", 200},
|
||||||
{s.URL + "/foo", "404 page not found", 404},
|
{s.URL + "/foo", "404 page not found", 404},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +89,7 @@ func TestJSONHandlers(t *testing.T) {
|
||||||
out string
|
out string
|
||||||
status int
|
status int
|
||||||
}{
|
}{
|
||||||
{s.URL, `{"ip":"127.0.0.1","country":"Elbonia","hostname":"localhost"}`, 200},
|
{s.URL, `{"ip":"127.0.0.1","country":"Elbonia","city":"Bornyasherk","hostname":"localhost"}`, 200},
|
||||||
{s.URL + "/port/31337", `{"ip":"127.0.0.1","port":31337,"reachable":true}`, 200},
|
{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 + "/foo", `{"error":"404 page not found"}`, 404},
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,18 +11,22 @@ import (
|
||||||
type Oracle interface {
|
type Oracle interface {
|
||||||
LookupAddr(string) ([]string, error)
|
LookupAddr(string) ([]string, error)
|
||||||
LookupCountry(net.IP) (string, error)
|
LookupCountry(net.IP) (string, error)
|
||||||
|
LookupCity(net.IP) (string, error)
|
||||||
LookupPort(net.IP, uint64) error
|
LookupPort(net.IP, uint64) error
|
||||||
IsLookupAddrEnabled() bool
|
IsLookupAddrEnabled() bool
|
||||||
IsLookupCountryEnabled() bool
|
IsLookupCountryEnabled() bool
|
||||||
|
IsLookupCityEnabled() bool
|
||||||
IsLookupPortEnabled() bool
|
IsLookupPortEnabled() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultOracle struct {
|
type DefaultOracle struct {
|
||||||
lookupAddr func(string) ([]string, error)
|
lookupAddr func(string) ([]string, error)
|
||||||
lookupCountry func(net.IP) (string, error)
|
lookupCountry func(net.IP) (string, error)
|
||||||
|
lookupCity func(net.IP) (string, error)
|
||||||
lookupPort func(net.IP, uint64) error
|
lookupPort func(net.IP, uint64) error
|
||||||
lookupAddrEnabled bool
|
lookupAddrEnabled bool
|
||||||
lookupCountryEnabled bool
|
lookupCountryEnabled bool
|
||||||
|
lookupCityEnabled bool
|
||||||
lookupPortEnabled bool
|
lookupPortEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +34,7 @@ func NewOracle() *DefaultOracle {
|
||||||
return &DefaultOracle{
|
return &DefaultOracle{
|
||||||
lookupAddr: func(string) ([]string, error) { return nil, nil },
|
lookupAddr: func(string) ([]string, error) { return nil, nil },
|
||||||
lookupCountry: func(net.IP) (string, error) { return "", nil },
|
lookupCountry: func(net.IP) (string, error) { return "", nil },
|
||||||
|
lookupCity: func(net.IP) (string, error) { return "", nil },
|
||||||
lookupPort: func(net.IP, uint64) error { return nil },
|
lookupPort: func(net.IP, uint64) error { return nil },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +47,10 @@ func (r *DefaultOracle) LookupCountry(ip net.IP) (string, error) {
|
||||||
return r.lookupCountry(ip)
|
return r.lookupCountry(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DefaultOracle) LookupCity(ip net.IP) (string, error) {
|
||||||
|
return r.lookupCity(ip)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *DefaultOracle) LookupPort(ip net.IP, port uint64) error {
|
func (r *DefaultOracle) LookupPort(ip net.IP, port uint64) error {
|
||||||
return r.lookupPort(ip, port)
|
return r.lookupPort(ip, port)
|
||||||
}
|
}
|
||||||
|
@ -63,6 +72,18 @@ func (r *DefaultOracle) EnableLookupCountry(filepath string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DefaultOracle) EnableLookupCity(filepath string) error {
|
||||||
|
db, err := geoip2.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.lookupCity = func(ip net.IP) (string, error) {
|
||||||
|
return lookupCity(db, ip)
|
||||||
|
}
|
||||||
|
r.lookupCityEnabled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *DefaultOracle) EnableLookupPort() {
|
func (r *DefaultOracle) EnableLookupPort() {
|
||||||
r.lookupPort = lookupPort
|
r.lookupPort = lookupPort
|
||||||
r.lookupPortEnabled = true
|
r.lookupPortEnabled = true
|
||||||
|
@ -70,6 +91,7 @@ func (r *DefaultOracle) EnableLookupPort() {
|
||||||
|
|
||||||
func (r *DefaultOracle) IsLookupAddrEnabled() bool { return r.lookupAddrEnabled }
|
func (r *DefaultOracle) IsLookupAddrEnabled() bool { return r.lookupAddrEnabled }
|
||||||
func (r *DefaultOracle) IsLookupCountryEnabled() bool { return r.lookupCountryEnabled }
|
func (r *DefaultOracle) IsLookupCountryEnabled() bool { return r.lookupCountryEnabled }
|
||||||
|
func (r *DefaultOracle) IsLookupCityEnabled() bool { return r.lookupCityEnabled }
|
||||||
func (r *DefaultOracle) IsLookupPortEnabled() bool { return r.lookupPortEnabled }
|
func (r *DefaultOracle) IsLookupPortEnabled() bool { return r.lookupPortEnabled }
|
||||||
|
|
||||||
func lookupPort(ip net.IP, port uint64) error {
|
func lookupPort(ip net.IP, port uint64) error {
|
||||||
|
@ -95,3 +117,14 @@ func lookupCountry(db *geoip2.Reader, ip net.IP) (string, error) {
|
||||||
}
|
}
|
||||||
return "Unknown", fmt.Errorf("could not determine country for IP: %s", ip)
|
return "Unknown", fmt.Errorf("could not determine country for IP: %s", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lookupCity(db *geoip2.Reader, ip net.IP) (string, error) {
|
||||||
|
record, err := db.City(ip)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if city, exists := record.City.Names["en"]; exists {
|
||||||
|
return city, nil
|
||||||
|
}
|
||||||
|
return "Unknown", fmt.Errorf("could not determine city for IP: %s", ip)
|
||||||
|
}
|
||||||
|
|
10
index.html
10
index.html
|
@ -66,6 +66,13 @@ $ fetch -qo- http://ifconfig.co
|
||||||
$ http ifconfig.co/country
|
$ http ifconfig.co/country
|
||||||
{{ .Country }}
|
{{ .Country }}
|
||||||
</pre>
|
</pre>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .IsLookupCityEnabled }}
|
||||||
|
<p>City lookup:</p>
|
||||||
|
<pre>
|
||||||
|
$ http ifconfig.co/city
|
||||||
|
{{ .City }}
|
||||||
|
</pre>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-1-2">
|
<div class="pure-u-1 pure-u-md-1-2">
|
||||||
|
@ -78,7 +85,8 @@ Content-Type: application/json
|
||||||
Date: Fri, 15 Apr 2016 17:26:53 GMT
|
Date: Fri, 15 Apr 2016 17:26:53 GMT
|
||||||
|
|
||||||
{ {{ if .IsLookupCountryEnabled }}
|
{ {{ if .IsLookupCountryEnabled }}
|
||||||
"country": "{{ .Country }}",{{ end }}{{ if .IsLookupAddrEnabled }}
|
"country": "{{ .Country }}",{{ end }}{{ if .IsLookupCityEnabled }}
|
||||||
|
"city": "{{ .City }}",{{ end }}{{ if .IsLookupAddrEnabled }}
|
||||||
"hostname": "{{ .Hostname }}",{{ end }}
|
"hostname": "{{ .Hostname }}",{{ end }}
|
||||||
"ip": "{{ .IP }}"
|
"ip": "{{ .IP }}"
|
||||||
}
|
}
|
||||||
|
|
15
main.go
15
main.go
|
@ -11,7 +11,8 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var opts struct {
|
var opts struct {
|
||||||
DBPath string `short:"f" long:"file" description:"Path to GeoIP database" value-name:"FILE" default:""`
|
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:""`
|
||||||
Listen string `short:"l" long:"listen" description:"Listening address" value-name:"ADDR" default:":8080"`
|
Listen string `short:"l" long:"listen" description:"Listening address" value-name:"ADDR" default:":8080"`
|
||||||
CORS bool `short:"x" long:"cors" description:"Allow requests from other domains"`
|
CORS bool `short:"x" long:"cors" description:"Allow requests from other domains"`
|
||||||
ReverseLookup bool `short:"r" long:"reverse-lookup" description:"Perform reverse hostname lookups"`
|
ReverseLookup bool `short:"r" long:"reverse-lookup" description:"Perform reverse hostname lookups"`
|
||||||
|
@ -32,9 +33,15 @@ func main() {
|
||||||
log.Println("Enabling port lookup")
|
log.Println("Enabling port lookup")
|
||||||
oracle.EnableLookupPort()
|
oracle.EnableLookupPort()
|
||||||
}
|
}
|
||||||
if opts.DBPath != "" {
|
if opts.CountryDBPath != "" {
|
||||||
log.Printf("Enabling country lookup (using database: %s)", opts.DBPath)
|
log.Printf("Enabling country lookup (using database: %s)", opts.CountryDBPath)
|
||||||
if err := oracle.EnableLookupCountry(opts.DBPath); err != nil {
|
if err := oracle.EnableLookupCountry(opts.CountryDBPath); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opts.CityDBPath != "" {
|
||||||
|
log.Printf("Enabling city lookup (using database: %s)", opts.CityDBPath)
|
||||||
|
if err := oracle.EnableLookupCity(opts.CityDBPath); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue