mirror of
https://github.com/skidoodle/ipinfo.git
synced 2025-02-15 08:29:17 +01:00
Update dependencies, enhance database handling, and improve request parsing
This commit is contained in:
parent
bc9f481717
commit
a36241f263
7 changed files with 157 additions and 162 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
*.mmdb
|
||||
.env**
|
||||
.geoipupdate.lock
|
||||
|
|
48
db.go
48
db.go
|
@ -1,32 +1,70 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
|
||||
var cityDB *maxminddb.Reader
|
||||
var asnDB *maxminddb.Reader
|
||||
var dbMtx = new(sync.RWMutex)
|
||||
var (
|
||||
cityDB *maxminddb.Reader
|
||||
asnDB *maxminddb.Reader
|
||||
dbMtx = new(sync.RWMutex)
|
||||
)
|
||||
|
||||
const (
|
||||
cityDBPath = "./GeoLite2-City.mmdb"
|
||||
asnDBPath = "./GeoLite2-ASN.mmdb"
|
||||
)
|
||||
|
||||
func downloadDB() error {
|
||||
cmd := exec.Command("geoipupdate", "-d", "./")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to download database: %v. Ensure geoipupdate is installed and configured", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initDatabases() {
|
||||
var err error
|
||||
|
||||
dbMtx.Lock()
|
||||
defer dbMtx.Unlock()
|
||||
|
||||
cityDB, err = maxminddb.Open(cityDBPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening city database: %v", err)
|
||||
if os.IsNotExist(err) {
|
||||
log.Println("City database not found, attempting to download...")
|
||||
if errDownload := downloadDB(); errDownload != nil {
|
||||
log.Fatalf("Error downloading city database: %v", errDownload)
|
||||
}
|
||||
cityDB, err = maxminddb.Open(cityDBPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening city database after download: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("Error opening city database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
asnDB, err = maxminddb.Open(asnDBPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening ASN database: %v", err)
|
||||
if os.IsNotExist(err) {
|
||||
log.Println("ASN database not found, attempting to download...")
|
||||
if errDownload := downloadDB(); errDownload != nil {
|
||||
log.Fatalf("Error downloading ASN database: %v", errDownload)
|
||||
}
|
||||
asnDB, err = maxminddb.Open(asnDBPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening ASN database after download: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("Error opening ASN database: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
4
go.mod
4
go.mod
|
@ -2,6 +2,6 @@ module github.com/skidoodle/ipinfo
|
|||
|
||||
go 1.22.4
|
||||
|
||||
require github.com/oschwald/maxminddb-golang v1.6.0
|
||||
require github.com/oschwald/maxminddb-golang v1.13.1
|
||||
|
||||
require golang.org/x/sys v0.1.0 // indirect
|
||||
require golang.org/x/sys v0.29.0 // indirect
|
||||
|
|
23
go.sum
23
go.sum
|
@ -1,15 +1,12 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w=
|
||||
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/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
126
iputils.go
126
iputils.go
|
@ -5,7 +5,6 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -13,62 +12,62 @@ import (
|
|||
|
||||
var bogonNets = []*net.IPNet{
|
||||
// IPv4
|
||||
{IP: net.IPv4(0, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // "This" network
|
||||
{IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // Private-use networks
|
||||
{IP: net.IPv4(100, 64, 0, 0), Mask: net.CIDRMask(10, 32)}, // Carrier-grade NAT
|
||||
{IP: net.IPv4(127, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // Loopback
|
||||
{IP: net.IPv4(127, 0, 53, 53), Mask: net.CIDRMask(32, 32)}, // Name collision occurrence
|
||||
{IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)}, // Link-local
|
||||
{IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)}, // Private-use networks
|
||||
{IP: net.IPv4(192, 0, 0, 0), Mask: net.CIDRMask(24, 32)}, // IETF protocol assignments
|
||||
{IP: net.IPv4(192, 0, 2, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-1
|
||||
{IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)}, // Private-use networks
|
||||
{IP: net.IPv4(198, 18, 0, 0), Mask: net.CIDRMask(15, 32)}, // Network interconnect device benchmark testing
|
||||
{IP: net.IPv4(198, 51, 100, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-2
|
||||
{IP: net.IPv4(203, 0, 113, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-3
|
||||
{IP: net.IPv4(224, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // Multicast
|
||||
{IP: net.IPv4(240, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // Reserved for future use
|
||||
{IP: net.IPv4(0, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // "This" network
|
||||
{IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // Private-use networks
|
||||
{IP: net.IPv4(100, 64, 0, 0), Mask: net.CIDRMask(10, 32)}, // Carrier-grade NAT
|
||||
{IP: net.IPv4(127, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // Loopback
|
||||
{IP: net.IPv4(127, 0, 53, 53), Mask: net.CIDRMask(32, 32)}, // Name collision occurrence
|
||||
{IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)}, // Link-local
|
||||
{IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)}, // Private-use networks
|
||||
{IP: net.IPv4(192, 0, 0, 0), Mask: net.CIDRMask(24, 32)}, // IETF protocol assignments
|
||||
{IP: net.IPv4(192, 0, 2, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-1
|
||||
{IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)}, // Private-use networks
|
||||
{IP: net.IPv4(198, 18, 0, 0), Mask: net.CIDRMask(15, 32)}, // Network interconnect device benchmark testing
|
||||
{IP: net.IPv4(198, 51, 100, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-2
|
||||
{IP: net.IPv4(203, 0, 113, 0), Mask: net.CIDRMask(24, 32)}, // TEST-NET-3
|
||||
{IP: net.IPv4(224, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // Multicast
|
||||
{IP: net.IPv4(240, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // Reserved for future use
|
||||
{IP: net.IPv4(255, 255, 255, 255), Mask: net.CIDRMask(32, 32)}, // Limited broadcast
|
||||
// IPv6
|
||||
{IP: net.ParseIP("::/128"), Mask: net.CIDRMask(128, 128)}, // Node-scope unicast unspecified address
|
||||
{IP: net.ParseIP("::1/128"), Mask: net.CIDRMask(128, 128)}, // Node-scope unicast loopback address
|
||||
{IP: net.ParseIP("::/128"), Mask: net.CIDRMask(128, 128)}, // Node-scope unicast unspecified address
|
||||
{IP: net.ParseIP("::1/128"), Mask: net.CIDRMask(128, 128)}, // Node-scope unicast loopback address
|
||||
{IP: net.ParseIP("::ffff:0:0/96"), Mask: net.CIDRMask(96, 128)}, // IPv4-mapped addresses
|
||||
{IP: net.ParseIP("::/96"), Mask: net.CIDRMask(96, 128)}, // IPv4-compatible addresses
|
||||
{IP: net.ParseIP("100::/64"), Mask: net.CIDRMask(64, 128)}, // Remotely triggered black hole addresses
|
||||
{IP: net.ParseIP("2001:10::/28"), Mask: net.CIDRMask(28, 128)}, // Overlay routable cryptographic hash identifiers (ORCHID)
|
||||
{IP: net.ParseIP("::/96"), Mask: net.CIDRMask(96, 128)}, // IPv4-compatible addresses
|
||||
{IP: net.ParseIP("100::/64"), Mask: net.CIDRMask(64, 128)}, // Remotely triggered black hole addresses
|
||||
{IP: net.ParseIP("2001:10::/28"), Mask: net.CIDRMask(28, 128)}, // Overlay routable cryptographic hash identifiers (ORCHID)
|
||||
{IP: net.ParseIP("2001:db8::/32"), Mask: net.CIDRMask(32, 128)}, // Documentation prefix
|
||||
{IP: net.ParseIP("fc00::/7"), Mask: net.CIDRMask(7, 128)}, // Unique local addresses (ULA)
|
||||
{IP: net.ParseIP("fe80::/10"), Mask: net.CIDRMask(10, 128)}, // Link-local unicast
|
||||
{IP: net.ParseIP("fec0::/10"), Mask: net.CIDRMask(10, 128)}, // Site-local unicast (deprecated)
|
||||
{IP: net.ParseIP("ff00::/8"), Mask: net.CIDRMask(8, 128)}, // Multicast
|
||||
{IP: net.ParseIP("fc00::/7"), Mask: net.CIDRMask(7, 128)}, // Unique local addresses (ULA)
|
||||
{IP: net.ParseIP("fe80::/10"), Mask: net.CIDRMask(10, 128)}, // Link-local unicast
|
||||
{IP: net.ParseIP("fec0::/10"), Mask: net.CIDRMask(10, 128)}, // Site-local unicast (deprecated)
|
||||
{IP: net.ParseIP("ff00::/8"), Mask: net.CIDRMask(8, 128)}, // Multicast
|
||||
// Additional Bogon Ranges
|
||||
{IP: net.ParseIP("2002::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (0.0.0.0/8)
|
||||
{IP: net.ParseIP("2002:a00::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (10.0.0.0/8)
|
||||
{IP: net.ParseIP("2002:7f00::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (127.0.0.0/8)
|
||||
{IP: net.ParseIP("2002:a9fe::/32"), Mask: net.CIDRMask(32, 128)}, // 6to4 bogon (169.254.0.0/16)
|
||||
{IP: net.ParseIP("2002:ac10::/28"), Mask: net.CIDRMask(28, 128)}, // 6to4 bogon (172.16.0.0/12)
|
||||
{IP: net.ParseIP("2002:c000::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (192.0.0.0/24)
|
||||
{IP: net.ParseIP("2002:c000:200::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (192.0.2.0/24)
|
||||
{IP: net.ParseIP("2002:c0a8::/32"), Mask: net.CIDRMask(32, 128)}, // 6to4 bogon (192.168.0.0/16)
|
||||
{IP: net.ParseIP("2002:c612::/31"), Mask: net.CIDRMask(31, 128)}, // 6to4 bogon (198.18.0.0/15)
|
||||
{IP: net.ParseIP("2002:c633:6400::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (198.51.100.0/24)
|
||||
{IP: net.ParseIP("2002:cb00:7100::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (203.0.113.0/24)
|
||||
{IP: net.ParseIP("2002:e000::/20"), Mask: net.CIDRMask(20, 128)}, // 6to4 bogon (224.0.0.0/4)
|
||||
{IP: net.ParseIP("2002:f000::/20"), Mask: net.CIDRMask(20, 128)}, // 6to4 bogon (240.0.0.0/4)
|
||||
{IP: net.ParseIP("2002:ffff:ffff::/48"), Mask: net.CIDRMask(48, 128)}, // 6to4 bogon (255.255.255.255/32)
|
||||
{IP: net.ParseIP("2001::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (0.0.0.0/8)
|
||||
{IP: net.ParseIP("2001:0:a00::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (10.0.0.0/8)
|
||||
{IP: net.ParseIP("2001:0:7f00::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (127.0.0.0/8)
|
||||
{IP: net.ParseIP("2001:0:a9fe::/48"), Mask: net.CIDRMask(48, 128)}, // Teredo bogon (169.254.0.0/16)
|
||||
{IP: net.ParseIP("2001:0:ac10::/44"), Mask: net.CIDRMask(44, 128)}, // Teredo bogon (172.16.0.0/12)
|
||||
{IP: net.ParseIP("2001:0:c000::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (192.0.0.0/24)
|
||||
{IP: net.ParseIP("2001:0:c000:200::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (192.0.2.0/24)
|
||||
{IP: net.ParseIP("2001:0:c0a8::/48"), Mask: net.CIDRMask(48, 128)}, // Teredo bogon (192.168.0.0/16)
|
||||
{IP: net.ParseIP("2001:0:c612::/47"), Mask: net.CIDRMask(47, 128)}, // Teredo bogon (198.18.0.0/15)
|
||||
{IP: net.ParseIP("2002::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (0.0.0.0/8)
|
||||
{IP: net.ParseIP("2002:a00::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (10.0.0.0/8)
|
||||
{IP: net.ParseIP("2002:7f00::/24"), Mask: net.CIDRMask(24, 128)}, // 6to4 bogon (127.0.0.0/8)
|
||||
{IP: net.ParseIP("2002:a9fe::/32"), Mask: net.CIDRMask(32, 128)}, // 6to4 bogon (169.254.0.0/16)
|
||||
{IP: net.ParseIP("2002:ac10::/28"), Mask: net.CIDRMask(28, 128)}, // 6to4 bogon (172.16.0.0/12)
|
||||
{IP: net.ParseIP("2002:c000::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (192.0.0.0/24)
|
||||
{IP: net.ParseIP("2002:c000:200::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (192.0.2.0/24)
|
||||
{IP: net.ParseIP("2002:c0a8::/32"), Mask: net.CIDRMask(32, 128)}, // 6to4 bogon (192.168.0.0/16)
|
||||
{IP: net.ParseIP("2002:c612::/31"), Mask: net.CIDRMask(31, 128)}, // 6to4 bogon (198.18.0.0/15)
|
||||
{IP: net.ParseIP("2002:c633:6400::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (198.51.100.0/24)
|
||||
{IP: net.ParseIP("2002:cb00:7100::/40"), Mask: net.CIDRMask(40, 128)}, // 6to4 bogon (203.0.113.0/24)
|
||||
{IP: net.ParseIP("2002:e000::/20"), Mask: net.CIDRMask(20, 128)}, // 6to4 bogon (224.0.0.0/4)
|
||||
{IP: net.ParseIP("2002:f000::/20"), Mask: net.CIDRMask(20, 128)}, // 6to4 bogon (240.0.0.0/4)
|
||||
{IP: net.ParseIP("2002:ffff:ffff::/48"), Mask: net.CIDRMask(48, 128)}, // 6to4 bogon (255.255.255.255/32)
|
||||
{IP: net.ParseIP("2001::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (0.0.0.0/8)
|
||||
{IP: net.ParseIP("2001:0:a00::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (10.0.0.0/8)
|
||||
{IP: net.ParseIP("2001:0:7f00::/40"), Mask: net.CIDRMask(40, 128)}, // Teredo bogon (127.0.0.0/8)
|
||||
{IP: net.ParseIP("2001:0:a9fe::/48"), Mask: net.CIDRMask(48, 128)}, // Teredo bogon (169.254.0.0/16)
|
||||
{IP: net.ParseIP("2001:0:ac10::/44"), Mask: net.CIDRMask(44, 128)}, // Teredo bogon (172.16.0.0/12)
|
||||
{IP: net.ParseIP("2001:0:c000::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (192.0.0.0/24)
|
||||
{IP: net.ParseIP("2001:0:c000:200::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (192.0.2.0/24)
|
||||
{IP: net.ParseIP("2001:0:c0a8::/48"), Mask: net.CIDRMask(48, 128)}, // Teredo bogon (192.168.0.0/16)
|
||||
{IP: net.ParseIP("2001:0:c612::/47"), Mask: net.CIDRMask(47, 128)}, // Teredo bogon (198.18.0.0/15)
|
||||
{IP: net.ParseIP("2001:0:c633:6400::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (198.51.100.0/24)
|
||||
{IP: net.ParseIP("2001:0:cb00:7100::/56"), Mask: net.CIDRMask(56, 128)}, // Teredo bogon (203.0.113.0/24)
|
||||
{IP: net.ParseIP("2001:0:e000::/36"), Mask: net.CIDRMask(36, 128)}, // Teredo bogon (224.0.0.0/4)
|
||||
{IP: net.ParseIP("2001:0:f000::/36"), Mask: net.CIDRMask(36, 128)}, // Teredo bogon (240.0.0.0/4)
|
||||
{IP: net.ParseIP("2001:0:e000::/36"), Mask: net.CIDRMask(36, 128)}, // Teredo bogon (224.0.0.0/4)
|
||||
{IP: net.ParseIP("2001:0:f000::/36"), Mask: net.CIDRMask(36, 128)}, // Teredo bogon (240.0.0.0/4)
|
||||
{IP: net.ParseIP("2001:0:ffff:ffff::/64"), Mask: net.CIDRMask(64, 128)}, // Teredo bogon (255.255.255.255/32)
|
||||
}
|
||||
|
||||
|
@ -84,13 +83,17 @@ func isBogon(ip net.IP) bool {
|
|||
|
||||
// Get the real IP address from the request headers
|
||||
func getRealIP(r *http.Request) string {
|
||||
if realIP := r.Header.Get("CF-Connecting-IP"); realIP != "" {
|
||||
return realIP
|
||||
} else if realIP := r.Header.Get("X-Forwarded-For"); realIP != "" {
|
||||
return strings.Split(realIP, ",")[0]
|
||||
} else {
|
||||
return extractIP(r.RemoteAddr)
|
||||
for _, header := range []string{"CF-Connecting-IP", "X-Real-IP", "X-Forwarded-For"} {
|
||||
if ip := r.Header.Get(header); ip != "" {
|
||||
return strings.TrimSpace(strings.Split(ip, ",")[0])
|
||||
}
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
var ipCache = sync.Map{}
|
||||
|
@ -188,14 +191,3 @@ func toPtr(s string) *string {
|
|||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
// Validate JSONP callback name
|
||||
var callbackJSONP = regexp.MustCompile(`^[a-zA-Z_\$][a-zA-Z0-9_\$]*$`)
|
||||
|
||||
// Extract the IP address from a string, removing unwanted characters
|
||||
func extractIP(ip string) string {
|
||||
ip = strings.ReplaceAll(ip, "[", "")
|
||||
ip = strings.ReplaceAll(ip, "]", "")
|
||||
ss := strings.Split(ip, ":")
|
||||
return strings.Join(ss[:len(ss)-1], ":")
|
||||
}
|
||||
|
|
28
readme.md
28
readme.md
|
@ -8,7 +8,6 @@
|
|||
- **ASN Information**: Includes autonomous system number and organization.
|
||||
- **Hostname Lookup**: Retrieves the hostname associated with the IP address.
|
||||
- **Automatic Database Updates**: Keeps GeoIP databases up-to-date daily.
|
||||
- **JSONP Support**: Allows JSONP responses for cross-domain requests.
|
||||
|
||||
## Example Endpoints
|
||||
|
||||
|
@ -39,33 +38,6 @@ $ curl https://ip.albert.lol/9.9.9.9/city
|
|||
}
|
||||
```
|
||||
|
||||
### Use JSONP callback function
|
||||
|
||||
```sh
|
||||
$ curl https://test.albert.lol/9.9.9.9?callback=Quad9
|
||||
/**/ typeof Quad9 === 'function' && Quad9({
|
||||
"ip": "9.9.9.9",
|
||||
"hostname": "dns9.quad9.net",
|
||||
"asn": "19281",
|
||||
"org": "QUAD9-AS-1",
|
||||
"city": "Berkeley",
|
||||
"region": "California",
|
||||
"country": "United States",
|
||||
"continent": "North America",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"loc": "37.8767,-122.2676"
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<script>
|
||||
let Quad9 = function(data) {
|
||||
alert("Quad9's ASN is " + data.asn);
|
||||
}
|
||||
</script>
|
||||
<script src="https://test.albert.lol/9.9.9.9?callback=Quad9"></script>
|
||||
```
|
||||
|
||||
## Running Locally
|
||||
|
||||
### With Docker
|
||||
|
|
89
server.go
89
server.go
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -11,6 +10,7 @@ import (
|
|||
)
|
||||
|
||||
var invalidIPBytes = []byte("Please provide a valid IP address.")
|
||||
var invalidFieldBytes = []byte("Please provide a valid field.")
|
||||
|
||||
type dataStruct struct {
|
||||
IP *string `json:"ip"`
|
||||
|
@ -46,7 +46,7 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
|||
}
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
requestedThings := strings.Split(r.URL.Path, "/")
|
||||
requestedThings := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
||||
|
||||
// Enable gzip compression if requested
|
||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
|
@ -56,26 +56,46 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
|||
w = &gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
}
|
||||
|
||||
// Extract IP and field
|
||||
var IPAddress, field string
|
||||
if len(requestedThings) > 1 && net.ParseIP(requestedThings[1]) != nil {
|
||||
IPAddress = requestedThings[1]
|
||||
if len(requestedThings) > 2 {
|
||||
field = requestedThings[2]
|
||||
|
||||
// Parse the request URL
|
||||
switch len(requestedThings) {
|
||||
case 0:
|
||||
IPAddress = getRealIP(r) // Default to visitor's IP
|
||||
case 1:
|
||||
if requestedThings[0] == "" {
|
||||
IPAddress = getRealIP(r) // Handle root page case
|
||||
} else if _, ok := fieldMap[requestedThings[0]]; ok {
|
||||
IPAddress = getRealIP(r)
|
||||
field = requestedThings[0]
|
||||
} else if net.ParseIP(requestedThings[0]) != nil {
|
||||
IPAddress = requestedThings[0] // Valid IP provided
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": string(invalidIPBytes)})
|
||||
return
|
||||
}
|
||||
} else if len(requestedThings) > 1 {
|
||||
IPAddress = requestedThings[1] // This might be an invalid IP
|
||||
case 2:
|
||||
IPAddress = requestedThings[0]
|
||||
if _, ok := fieldMap[requestedThings[1]]; ok {
|
||||
field = requestedThings[1]
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": string(invalidFieldBytes)})
|
||||
return
|
||||
}
|
||||
default:
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": string(invalidIPBytes)})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the IP is the client's IP
|
||||
if IPAddress == "" {
|
||||
IPAddress = getRealIP(r)
|
||||
}
|
||||
|
||||
// Validate the IP address
|
||||
// Validate the resolved IP
|
||||
ip := net.ParseIP(IPAddress)
|
||||
if ip == nil {
|
||||
// Send 400 Bad Request with invalid IP message
|
||||
http.Error(w, string(invalidIPBytes), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
@ -83,18 +103,13 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
|||
// Check if the IP is a bogon IP
|
||||
if isBogon(ip) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
bogonData := bogonDataStruct{
|
||||
IP: ip.String(),
|
||||
Bogon: true,
|
||||
}
|
||||
json.NewEncoder(w).Encode(bogonData)
|
||||
json.NewEncoder(w).Encode(bogonDataStruct{IP: ip.String(), Bogon: true})
|
||||
return
|
||||
}
|
||||
|
||||
// Look up IP data
|
||||
data := lookupIPData(ip)
|
||||
if data == nil {
|
||||
// Send 400 Bad Request with invalid IP message
|
||||
http.Error(w, string(invalidIPBytes), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
@ -102,34 +117,14 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
|||
// Handle specific field requests
|
||||
if field != "" {
|
||||
value := getField(data, field)
|
||||
if value != nil {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]*string{field: value})
|
||||
return
|
||||
} else {
|
||||
// Handle invalid field request
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]*string{field: nil})
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]*string{field: value})
|
||||
return
|
||||
}
|
||||
|
||||
// If no specific field is requested, return the whole data
|
||||
// Default case: return full IP data
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
callback := r.URL.Query().Get("callback")
|
||||
enableJSONP := callback != "" && len(callback) < 2000 && callbackJSONP.MatchString(callback)
|
||||
if enableJSONP {
|
||||
jsonData, _ := json.MarshalIndent(data, "", " ")
|
||||
response := fmt.Sprintf("/**/ typeof %s === 'function' && %s(%s);", callback, callback, jsonData)
|
||||
w.Write([]byte(response))
|
||||
} else {
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
if r.URL.Query().Get("compact") == "true" {
|
||||
enc.SetIndent("", "")
|
||||
}
|
||||
enc.Encode(data)
|
||||
}
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
var fieldMap = map[string]func(*dataStruct) *string{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue