diff --git a/cmd/ipd/main.go b/cmd/ipd/main.go index 71e8bf5..fca2335 100644 --- a/cmd/ipd/main.go +++ b/cmd/ipd/main.go @@ -6,6 +6,8 @@ import ( "os" "github.com/mpolden/ipd/http" + "github.com/mpolden/ipd/iputil" + "github.com/mpolden/ipd/iputil/db" "github.com/sirupsen/logrus" ) @@ -32,32 +34,28 @@ func main() { } 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 { log.Println("Enabling reverse lookup") - oracle.EnableLookupAddr() + lookupAddr = iputil.LookupAddr } if opts.PortLookup { log.Println("Enabling port lookup") - oracle.EnableLookupPort() - } - 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) - } + lookupPort = iputil.LookupPort } if 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.IPHeader = opts.IPHeader diff --git a/http/http.go b/http/http.go index 41f8a65..ed003af 100644 --- a/http/http.go +++ b/http/http.go @@ -5,6 +5,8 @@ import ( "fmt" "html/template" + "github.com/mpolden/ipd/iputil" + "github.com/mpolden/ipd/iputil/db" "github.com/mpolden/ipd/useragent" "github.com/sirupsen/logrus" @@ -23,11 +25,16 @@ const ( textMediaType = "text/plain" ) +type LookupAddr func(net.IP) ([]string, error) +type LookupPort func(net.IP, uint64) error + type Server struct { - Template string - IPHeader string - oracle Oracle - log *logrus.Logger + Template string + IPHeader string + lookupAddr LookupAddr + lookupPort LookupPort + db db.Database + log *logrus.Logger } type Response struct { @@ -45,18 +52,8 @@ type PortResponse struct { Reachable bool `json:"reachable"` } -func New(oracle Oracle, logger *logrus.Logger) *Server { - return &Server{oracle: oracle, 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 New(db db.Database, lookupAddr LookupAddr, lookupPort LookupPort, logger *logrus.Logger) *Server { + return &Server{lookupAddr: lookupAddr, lookupPort: lookupPort, db: db, log: logger} } 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 { return Response{}, err } - ipDecimal := ipToDecimal(ip) - country, err := s.oracle.LookupCountry(ip) + ipDecimal := iputil.ToDecimal(ip) + country, err := s.db.Country(ip) if err != nil { s.log.Debug(err) } - countryISO, err := s.oracle.LookupCountryISO(ip) + city, err := s.db.City(ip) if err != nil { s.log.Debug(err) } - city, err := s.oracle.LookupCity(ip) - if err != nil { - s.log.Debug(err) - } - hostnames, err := s.oracle.LookupAddr(ip) + hostnames, err := s.lookupAddr(ip) if err != nil { s.log.Debug(err) } return Response{ IP: ip, IPDecimal: ipDecimal, - Country: country, - CountryISO: countryISO, + Country: country.Name, + CountryISO: country.ISO, City: city, Hostname: strings.Join(hostnames, " "), }, nil @@ -120,7 +113,7 @@ func (s *Server) newPortResponse(r *http.Request) (PortResponse, error) { if err != nil { return PortResponse{Port: port}, err } - err = s.oracle.LookupPort(ip, port) + err = s.lookupPort(ip, port) return PortResponse{ IP: ip, Port: port, @@ -201,11 +194,23 @@ func (s *Server) DefaultHandler(w http.ResponseWriter, r *http.Request) *appErro if err != nil { return internalServerError(err) } + json, err := json.MarshalIndent(response, "", " ") + if err != nil { + return internalServerError(err) + } var data = struct { - Host string Response - Oracle - }{r.Host, response, s.oracle} + Host string + 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 { return internalServerError(err) } diff --git a/http/http_test.go b/http/http_test.go index f4ce1a3..2b2912a 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -3,27 +3,25 @@ package http import ( "io/ioutil" "log" - "math/big" "net" "net/http" "net/http/httptest" "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 (r *mockOracle) LookupCountry(net.IP) (string, error) { return "Elbonia", nil } -func (r *mockOracle) LookupCountryISO(net.IP) (string, error) { return "EB", 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) IsLookupAddrEnabled() 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 lookupAddr(net.IP) ([]string, error) { return []string{"localhost"}, nil } +func lookupPort(net.IP, uint64) error { return nil } +func (d *database) Country(net.IP) (db.Country, error) { + return db.Country{Name: "Elbonia", ISO: "EB"}, nil +} +func (d *database) City(net.IP) (string, error) { return "Bornyasherk", nil } 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) { @@ -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) - } - } -} diff --git a/http/oracle.go b/http/oracle.go deleted file mode 100644 index 1654758..0000000 --- a/http/oracle.go +++ /dev/null @@ -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) -} diff --git a/index.html b/index.html index 735e8bc..c0bc613 100644 --- a/index.html +++ b/index.html @@ -5,9 +5,9 @@ What is my IP address? — {{ .Host }} - - - + + +