mirror of
https://github.com/mpolden/echoip.git
synced 2025-01-12 19:27:21 +01:00
Extract iputil package
This commit is contained in:
parent
35061bfe83
commit
8112536125
@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/mpolden/ipd/http"
|
"github.com/mpolden/ipd/http"
|
||||||
|
"github.com/mpolden/ipd/iputil"
|
||||||
|
"github.com/mpolden/ipd/iputil/db"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,32 +34,28 @@ func main() {
|
|||||||
}
|
}
|
||||||
log.Level = level
|
log.Level = level
|
||||||
|
|
||||||
oracle := http.NewOracle()
|
ipDb := db.Empty()
|
||||||
|
if opts.CountryDBPath != "" || opts.CityDBPath != "" {
|
||||||
|
ipDb, err = db.Open(opts.CountryDBPath, opts.CityDBPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var lookupAddr http.LookupAddr
|
||||||
|
var lookupPort http.LookupPort
|
||||||
if opts.ReverseLookup {
|
if opts.ReverseLookup {
|
||||||
log.Println("Enabling reverse lookup")
|
log.Println("Enabling reverse lookup")
|
||||||
oracle.EnableLookupAddr()
|
lookupAddr = iputil.LookupAddr
|
||||||
}
|
}
|
||||||
if opts.PortLookup {
|
if opts.PortLookup {
|
||||||
log.Println("Enabling port lookup")
|
log.Println("Enabling port lookup")
|
||||||
oracle.EnableLookupPort()
|
lookupPort = iputil.LookupPort
|
||||||
}
|
|
||||||
if opts.CountryDBPath != "" {
|
|
||||||
log.Printf("Enabling country lookup (using database: %s)", opts.CountryDBPath)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if opts.IPHeader != "" {
|
if opts.IPHeader != "" {
|
||||||
log.Printf("Trusting header %s to contain correct remote IP", opts.IPHeader)
|
log.Printf("Trusting header %s to contain correct remote IP", opts.IPHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
server := http.New(oracle, log)
|
server := http.New(ipDb, lookupAddr, lookupPort, log)
|
||||||
server.Template = opts.Template
|
server.Template = opts.Template
|
||||||
server.IPHeader = opts.IPHeader
|
server.IPHeader = opts.IPHeader
|
||||||
|
|
||||||
|
65
http/http.go
65
http/http.go
@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/mpolden/ipd/iputil"
|
||||||
|
"github.com/mpolden/ipd/iputil/db"
|
||||||
"github.com/mpolden/ipd/useragent"
|
"github.com/mpolden/ipd/useragent"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
@ -23,11 +25,16 @@ const (
|
|||||||
textMediaType = "text/plain"
|
textMediaType = "text/plain"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LookupAddr func(net.IP) ([]string, error)
|
||||||
|
type LookupPort func(net.IP, uint64) error
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Template string
|
Template string
|
||||||
IPHeader string
|
IPHeader string
|
||||||
oracle Oracle
|
lookupAddr LookupAddr
|
||||||
log *logrus.Logger
|
lookupPort LookupPort
|
||||||
|
db db.Database
|
||||||
|
log *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
@ -45,18 +52,8 @@ type PortResponse struct {
|
|||||||
Reachable bool `json:"reachable"`
|
Reachable bool `json:"reachable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(oracle Oracle, logger *logrus.Logger) *Server {
|
func New(db db.Database, lookupAddr LookupAddr, lookupPort LookupPort, logger *logrus.Logger) *Server {
|
||||||
return &Server{oracle: oracle, log: logger}
|
return &Server{lookupAddr: lookupAddr, lookupPort: lookupPort, db: db, log: logger}
|
||||||
}
|
|
||||||
|
|
||||||
func ipToDecimal(ip net.IP) *big.Int {
|
|
||||||
i := big.NewInt(0)
|
|
||||||
if to4 := ip.To4(); to4 != nil {
|
|
||||||
i.SetBytes(to4)
|
|
||||||
} else {
|
|
||||||
i.SetBytes(ip)
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ipFromRequest(header string, r *http.Request) (net.IP, error) {
|
func ipFromRequest(header string, r *http.Request) (net.IP, error) {
|
||||||
@ -80,28 +77,24 @@ func (s *Server) newResponse(r *http.Request) (Response, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return Response{}, err
|
return Response{}, err
|
||||||
}
|
}
|
||||||
ipDecimal := ipToDecimal(ip)
|
ipDecimal := iputil.ToDecimal(ip)
|
||||||
country, err := s.oracle.LookupCountry(ip)
|
country, err := s.db.Country(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Debug(err)
|
s.log.Debug(err)
|
||||||
}
|
}
|
||||||
countryISO, err := s.oracle.LookupCountryISO(ip)
|
city, err := s.db.City(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Debug(err)
|
s.log.Debug(err)
|
||||||
}
|
}
|
||||||
city, err := s.oracle.LookupCity(ip)
|
hostnames, err := s.lookupAddr(ip)
|
||||||
if err != nil {
|
|
||||||
s.log.Debug(err)
|
|
||||||
}
|
|
||||||
hostnames, err := s.oracle.LookupAddr(ip)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Debug(err)
|
s.log.Debug(err)
|
||||||
}
|
}
|
||||||
return Response{
|
return Response{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
IPDecimal: ipDecimal,
|
IPDecimal: ipDecimal,
|
||||||
Country: country,
|
Country: country.Name,
|
||||||
CountryISO: countryISO,
|
CountryISO: country.ISO,
|
||||||
City: city,
|
City: city,
|
||||||
Hostname: strings.Join(hostnames, " "),
|
Hostname: strings.Join(hostnames, " "),
|
||||||
}, nil
|
}, nil
|
||||||
@ -120,7 +113,7 @@ func (s *Server) newPortResponse(r *http.Request) (PortResponse, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return PortResponse{Port: port}, err
|
return PortResponse{Port: port}, err
|
||||||
}
|
}
|
||||||
err = s.oracle.LookupPort(ip, port)
|
err = s.lookupPort(ip, port)
|
||||||
return PortResponse{
|
return PortResponse{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
Port: port,
|
Port: port,
|
||||||
@ -201,11 +194,23 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return internalServerError(err)
|
return internalServerError(err)
|
||||||
}
|
}
|
||||||
|
json, err := json.MarshalIndent(response, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return internalServerError(err)
|
||||||
|
}
|
||||||
var data = struct {
|
var data = struct {
|
||||||
Host string
|
|
||||||
Response
|
Response
|
||||||
Oracle
|
Host string
|
||||||
}{r.Host, response, s.oracle}
|
JSON string
|
||||||
|
Port bool
|
||||||
|
Map bool
|
||||||
|
}{
|
||||||
|
response,
|
||||||
|
r.Host,
|
||||||
|
string(json),
|
||||||
|
s.lookupPort != nil,
|
||||||
|
response.Country != "" && response.City != "",
|
||||||
|
}
|
||||||
if err := t.Execute(w, &data); err != nil {
|
if err := t.Execute(w, &data); err != nil {
|
||||||
return internalServerError(err)
|
return internalServerError(err)
|
||||||
}
|
}
|
||||||
|
@ -3,27 +3,25 @@ package http
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mpolden/ipd/iputil/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockOracle struct{}
|
type database struct{}
|
||||||
|
|
||||||
func (r *mockOracle) LookupAddr(net.IP) ([]string, error) { return []string{"localhost"}, nil }
|
func lookupAddr(net.IP) ([]string, error) { return []string{"localhost"}, nil }
|
||||||
func (r *mockOracle) LookupCountry(net.IP) (string, error) { return "Elbonia", nil }
|
func lookupPort(net.IP, uint64) error { return nil }
|
||||||
func (r *mockOracle) LookupCountryISO(net.IP) (string, error) { return "EB", nil }
|
func (d *database) Country(net.IP) (db.Country, error) {
|
||||||
func (r *mockOracle) LookupCity(net.IP) (string, error) { return "Bornyasherk", nil }
|
return db.Country{Name: "Elbonia", ISO: "EB"}, nil
|
||||||
func (r *mockOracle) LookupPort(net.IP, uint64) error { return nil }
|
}
|
||||||
func (r *mockOracle) IsLookupAddrEnabled() bool { return true }
|
func (d *database) City(net.IP) (string, error) { return "Bornyasherk", nil }
|
||||||
func (r *mockOracle) IsLookupCountryEnabled() bool { return true }
|
|
||||||
func (r *mockOracle) IsLookupCityEnabled() bool { return true }
|
|
||||||
func (r *mockOracle) IsLookupPortEnabled() bool { return true }
|
|
||||||
|
|
||||||
func newTestAPI() *Server {
|
func newTestAPI() *Server {
|
||||||
return &Server{oracle: &mockOracle{}}
|
return &Server{db: &database{}, lookupAddr: lookupAddr, lookupPort: lookupPort}
|
||||||
}
|
}
|
||||||
|
|
||||||
func httpGet(url string, acceptMediaType string, userAgent string) (string, int, error) {
|
func httpGet(url string, acceptMediaType string, userAgent string) (string, int, error) {
|
||||||
@ -168,19 +166,3 @@ func TestCLIMatcher(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIPToDecimal(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
in string
|
|
||||||
out *big.Int
|
|
||||||
}{
|
|
||||||
{"127.0.0.1", big.NewInt(2130706433)},
|
|
||||||
{"::1", big.NewInt(1)},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
i := ipToDecimal(net.ParseIP(tt.in))
|
|
||||||
if i.Cmp(tt.out) != 0 {
|
|
||||||
t.Errorf("Expected %d, got %d for IP %s", tt.out, i, tt.in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
163
http/oracle.go
163
http/oracle.go
@ -1,163 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/oschwald/geoip2-golang"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Oracle interface {
|
|
||||||
LookupAddr(net.IP) ([]string, error)
|
|
||||||
LookupCountry(net.IP) (string, error)
|
|
||||||
LookupCountryISO(net.IP) (string, error)
|
|
||||||
LookupCity(net.IP) (string, error)
|
|
||||||
LookupPort(net.IP, uint64) error
|
|
||||||
IsLookupAddrEnabled() bool
|
|
||||||
IsLookupCountryEnabled() bool
|
|
||||||
IsLookupCityEnabled() bool
|
|
||||||
IsLookupPortEnabled() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultOracle struct {
|
|
||||||
lookupAddr func(net.IP) ([]string, error)
|
|
||||||
lookupCountry func(net.IP) (string, error)
|
|
||||||
lookupCountryISO func(net.IP) (string, error)
|
|
||||||
lookupCity func(net.IP) (string, error)
|
|
||||||
lookupPort func(net.IP, uint64) error
|
|
||||||
lookupAddrEnabled bool
|
|
||||||
lookupCountryEnabled bool
|
|
||||||
lookupCityEnabled bool
|
|
||||||
lookupPortEnabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOracle() *DefaultOracle {
|
|
||||||
return &DefaultOracle{
|
|
||||||
lookupAddr: func(net.IP) ([]string, error) { return nil, nil },
|
|
||||||
lookupCountry: func(net.IP) (string, error) { return "", nil },
|
|
||||||
lookupCountryISO: func(net.IP) (string, error) { return "", nil },
|
|
||||||
lookupCity: func(net.IP) (string, error) { return "", nil },
|
|
||||||
lookupPort: func(net.IP, uint64) error { return nil },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultOracle) LookupAddr(ip net.IP) ([]string, error) {
|
|
||||||
return r.lookupAddr(ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultOracle) LookupCountry(ip net.IP) (string, error) {
|
|
||||||
return r.lookupCountry(ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultOracle) LookupCountryISO(ip net.IP) (string, error) {
|
|
||||||
return r.lookupCountryISO(ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultOracle) LookupCity(ip net.IP) (string, error) {
|
|
||||||
return r.lookupCity(ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultOracle) LookupPort(ip net.IP, port uint64) error {
|
|
||||||
return r.lookupPort(ip, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultOracle) EnableLookupAddr() {
|
|
||||||
r.lookupAddr = lookupAddr
|
|
||||||
r.lookupAddrEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultOracle) EnableLookupCountry(filepath string) error {
|
|
||||||
db, err := geoip2.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.lookupCountry = func(ip net.IP) (string, error) {
|
|
||||||
return lookupCountry(db, ip)
|
|
||||||
}
|
|
||||||
r.lookupCountryISO = func(ip net.IP) (string, error) {
|
|
||||||
return lookupCountryISO(db, ip)
|
|
||||||
}
|
|
||||||
r.lookupCountryEnabled = true
|
|
||||||
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() {
|
|
||||||
r.lookupPort = lookupPort
|
|
||||||
r.lookupPortEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultOracle) IsLookupAddrEnabled() bool { return r.lookupAddrEnabled }
|
|
||||||
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 lookupAddr(ip net.IP) ([]string, error) {
|
|
||||||
names, err := net.LookupAddr(ip.String())
|
|
||||||
for i, _ := range names {
|
|
||||||
names[i] = strings.TrimRight(names[i], ".") // Always return unrooted name
|
|
||||||
}
|
|
||||||
return names, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupPort(ip net.IP, port uint64) error {
|
|
||||||
address := fmt.Sprintf("[%s]:%d", ip, port)
|
|
||||||
conn, err := net.DialTimeout("tcp", address, 2*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupCountry(db *geoip2.Reader, ip net.IP) (string, error) {
|
|
||||||
record, err := db.Country(ip)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if country, exists := record.Country.Names["en"]; exists {
|
|
||||||
return country, nil
|
|
||||||
}
|
|
||||||
if country, exists := record.RegisteredCountry.Names["en"]; exists {
|
|
||||||
return country, nil
|
|
||||||
}
|
|
||||||
return "Unknown", fmt.Errorf("could not determine country for IP: %s", ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupCountryISO(db *geoip2.Reader, ip net.IP) (string, error) {
|
|
||||||
record, err := db.City(ip)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if record.Country.IsoCode != "" {
|
|
||||||
return record.Country.IsoCode, nil
|
|
||||||
}
|
|
||||||
if record.RegisteredCountry.IsoCode != "" {
|
|
||||||
return record.RegisteredCountry.IsoCode, nil
|
|
||||||
}
|
|
||||||
return "Unknown", fmt.Errorf("could not determine country ISO Code 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)
|
|
||||||
}
|
|
26
index.html
26
index.html
@ -5,9 +5,9 @@
|
|||||||
<title>What is my IP address? — {{ .Host }}</title>
|
<title>What is my IP address? — {{ .Host }}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="description" content="What is my IP address?">
|
<meta name="description" content="What is my IP address?">
|
||||||
<link href="//fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/pure/0.6.2/pure-min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/0.6.2/pure-min.css">
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/pure/0.6.2/grids-responsive-min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/0.6.2/grids-responsive-min.css">
|
||||||
<style>
|
<style>
|
||||||
html, .pure-g [class *= "pure-u"] {
|
html, .pure-g [class *= "pure-u"] {
|
||||||
font-family: "Open Sans", sans-serif;
|
font-family: "Open Sans", sans-serif;
|
||||||
@ -58,7 +58,7 @@ $ fetch -qo- https://{{ .Host }}
|
|||||||
|
|
||||||
$ bat -print=b {{ .Host }}/ip
|
$ bat -print=b {{ .Host }}/ip
|
||||||
{{ .IP }}</pre>
|
{{ .IP }}</pre>
|
||||||
{{ if .IsLookupCountryEnabled }}
|
{{ if .Country }}
|
||||||
<h2>Country lookup</h2>
|
<h2>Country lookup</h2>
|
||||||
<pre>
|
<pre>
|
||||||
$ http {{ .Host }}/country
|
$ http {{ .Host }}/country
|
||||||
@ -67,7 +67,7 @@ $ http {{ .Host }}/country
|
|||||||
$ http {{ .Host }}/country-iso
|
$ http {{ .Host }}/country-iso
|
||||||
{{ .CountryISO }}</pre>
|
{{ .CountryISO }}</pre>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ if .IsLookupCityEnabled }}
|
{{ if .City }}
|
||||||
<h2>City lookup</h2>
|
<h2>City lookup</h2>
|
||||||
<pre>
|
<pre>
|
||||||
$ http {{ .Host }}/city
|
$ http {{ .Host }}/city
|
||||||
@ -78,22 +78,14 @@ $ http {{ .Host }}/city
|
|||||||
<h2>JSON output</h2>
|
<h2>JSON output</h2>
|
||||||
<pre>
|
<pre>
|
||||||
$ http {{ .Host }}/json
|
$ http {{ .Host }}/json
|
||||||
{ {{ if .IsLookupCityEnabled }}
|
{{ .JSON }}</pre>
|
||||||
"city": "{{ .City }}",
|
|
||||||
"country": "{{ .Country }}",
|
|
||||||
"country_iso": "{{ .CountryISO }}",{{ end }}{{ if .IsLookupAddrEnabled }}
|
|
||||||
"hostname": "{{ .Hostname }}",{{ end }}
|
|
||||||
"ip": "{{ .IP }}",
|
|
||||||
"ip_decimal": {{ .IPDecimal }}
|
|
||||||
}</pre>
|
|
||||||
|
|
||||||
<p>Setting the Accept header to application/json also works.</p>
|
<p>Setting the Accept header to application/json also works.</p>
|
||||||
<h2>Plain output</h2>
|
<h2>Plain output</h2>
|
||||||
<p>Always returns the IP address including a trailing newline, regardless of user agent.</p>
|
<p>Always returns the IP address including a trailing newline, regardless of user agent.</p>
|
||||||
<pre>
|
<pre>
|
||||||
$ http {{ .Host }}/ip
|
$ http {{ .Host }}/ip
|
||||||
{{ .IP }}</pre>
|
{{ .IP }}</pre>
|
||||||
{{ if .IsLookupPortEnabled }}
|
{{ if .Port }}
|
||||||
<h2>Port testing</h2>
|
<h2>Port testing</h2>
|
||||||
<pre>
|
<pre>
|
||||||
$ http {{ .Host }}/port/8080
|
$ http {{ .Host }}/port/8080
|
||||||
@ -104,13 +96,13 @@ $ http {{ .Host }}/port/8080
|
|||||||
}</pre>
|
}</pre>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ if .IsLookupCountryEnabled }}{{ if ne .Country "Unknown"}}{{ if .IsLookupCityEnabled }}{{ if ne .City "Unknown"}}
|
{{ if .Map }}
|
||||||
<div class="pure-u-1 pure-u-md-1-2">
|
<div class="pure-u-1 pure-u-md-1-2">
|
||||||
<h2>Map</h2>
|
<h2>Map</h2>
|
||||||
<p><img src="https://maps.googleapis.com/maps/api/staticmap?size=500x166&scale=2&markers=color%3Aorange%7Clabel%3AS%7c{{ .City }},{{ .Country }}" width="500" height="166"/>
|
<p><img src="https://maps.googleapis.com/maps/api/staticmap?size=500x166&scale=2&markers=color%3Aorange%7Clabel%3AS%7c{{ .City }},{{ .Country }}" width="500" height="166"/>
|
||||||
</p>
|
</p>
|
||||||
{{ end }}{{ end }}{{ end }}{{ end }}
|
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
<div class="pure-u-1 pure-u-md-1-2">
|
<div class="pure-u-1 pure-u-md-1-2">
|
||||||
<h2>FAQ</h2>
|
<h2>FAQ</h2>
|
||||||
<h3>How do I force IPv4 or IPv6 lookup?</h3>
|
<h3>How do I force IPv4 or IPv6 lookup?</h3>
|
||||||
|
89
iputil/db/db.go
Normal file
89
iputil/db/db.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
geoip2 "github.com/oschwald/geoip2-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database interface {
|
||||||
|
Country(net.IP) (Country, error)
|
||||||
|
City(net.IP) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Country struct {
|
||||||
|
Name string
|
||||||
|
ISO string
|
||||||
|
}
|
||||||
|
|
||||||
|
type geoip struct {
|
||||||
|
country *geoip2.Reader
|
||||||
|
city *geoip2.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
type empty struct{}
|
||||||
|
|
||||||
|
func (d *empty) Country(ip net.IP) (Country, error) { return Country{}, nil }
|
||||||
|
func (d *empty) City(ip net.IP) (string, error) { return "", nil }
|
||||||
|
|
||||||
|
func Empty() Database { return &empty{} }
|
||||||
|
|
||||||
|
func Open(countryDB, cityDB string) (Database, error) {
|
||||||
|
var (
|
||||||
|
country *geoip2.Reader
|
||||||
|
city *geoip2.Reader
|
||||||
|
)
|
||||||
|
if countryDB != "" {
|
||||||
|
r, err := geoip2.Open(countryDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
country = r
|
||||||
|
}
|
||||||
|
if cityDB != "" {
|
||||||
|
r, err := geoip2.Open(cityDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
city = r
|
||||||
|
}
|
||||||
|
return &geoip{country: country, city: city}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *geoip) Country(ip net.IP) (Country, error) {
|
||||||
|
country := Country{}
|
||||||
|
if g.country == nil {
|
||||||
|
return country, nil
|
||||||
|
}
|
||||||
|
record, err := g.country.Country(ip)
|
||||||
|
if err != nil {
|
||||||
|
return country, err
|
||||||
|
}
|
||||||
|
if c, exists := record.Country.Names["en"]; exists {
|
||||||
|
country.Name = c
|
||||||
|
}
|
||||||
|
if c, exists := record.RegisteredCountry.Names["en"]; exists && country.Name == "" {
|
||||||
|
country.Name = c
|
||||||
|
}
|
||||||
|
if record.Country.IsoCode != "" {
|
||||||
|
country.ISO = record.Country.IsoCode
|
||||||
|
}
|
||||||
|
if record.RegisteredCountry.IsoCode != "" && country.ISO == "" {
|
||||||
|
country.ISO = record.RegisteredCountry.IsoCode
|
||||||
|
}
|
||||||
|
return country, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *geoip) City(ip net.IP) (string, error) {
|
||||||
|
if g.city == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
record, err := g.city.City(ip)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if city, exists := record.City.Names["en"]; exists {
|
||||||
|
return city, nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
37
iputil/iputil.go
Normal file
37
iputil/iputil.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package iputil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LookupAddr(ip net.IP) ([]string, error) {
|
||||||
|
names, err := net.LookupAddr(ip.String())
|
||||||
|
for i, _ := range names {
|
||||||
|
names[i] = strings.TrimRight(names[i], ".") // Always return unrooted name
|
||||||
|
}
|
||||||
|
return names, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func LookupPort(ip net.IP, port uint64) error {
|
||||||
|
address := fmt.Sprintf("[%s]:%d", ip, port)
|
||||||
|
conn, err := net.DialTimeout("tcp", address, 2*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToDecimal(ip net.IP) *big.Int {
|
||||||
|
i := big.NewInt(0)
|
||||||
|
if to4 := ip.To4(); to4 != nil {
|
||||||
|
i.SetBytes(to4)
|
||||||
|
} else {
|
||||||
|
i.SetBytes(ip)
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
23
iputil/iputil_test.go
Normal file
23
iputil/iputil_test.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package iputil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToDecimal(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
in string
|
||||||
|
out *big.Int
|
||||||
|
}{
|
||||||
|
{"127.0.0.1", big.NewInt(2130706433)},
|
||||||
|
{"::1", big.NewInt(1)},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
i := ToDecimal(net.ParseIP(tt.in))
|
||||||
|
if i.Cmp(tt.out) != 0 {
|
||||||
|
t.Errorf("Expected %d, got %d for IP %s", tt.out, i, tt.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user