mirror of
https://github.com/skidoodle/ipinfo.git
synced 2025-02-15 08:29:17 +01:00
feat: bogon
This commit is contained in:
parent
ea53882eea
commit
0fb3779ee0
1 changed files with 136 additions and 35 deletions
171
main.go
171
main.go
|
@ -18,13 +18,13 @@ import (
|
|||
)
|
||||
|
||||
// GeoIP database readers
|
||||
var cityDB *maxminddb.Reader
|
||||
var asnDB *maxminddb.Reader
|
||||
|
||||
var (
|
||||
cityDB *maxminddb.Reader
|
||||
asnDB *maxminddb.Reader
|
||||
dbMtx sync.RWMutex
|
||||
|
||||
currCityFilename = time.Now().Format("2006-01") + "-city.mmdb"
|
||||
currASNFilename = time.Now().Format("2006-01") + "-asn.mmdb"
|
||||
dbMtx = new(sync.RWMutex)
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -33,54 +33,59 @@ const (
|
|||
)
|
||||
|
||||
func main() {
|
||||
initDatabases()
|
||||
go startUpdater()
|
||||
startServer()
|
||||
initDBs()
|
||||
go runUpdater()
|
||||
runServer()
|
||||
}
|
||||
|
||||
func initDatabases() {
|
||||
// Initialize the databases
|
||||
func initDBs() {
|
||||
var err error
|
||||
|
||||
cityDB, err = maxminddb.Open(currCityFilename)
|
||||
cityDB, err = openDB(currCityFilename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
currCityFilename = ""
|
||||
doUpdate()
|
||||
if cityDB == nil {
|
||||
log.Fatalf("Failed to initialize city database: %v", err)
|
||||
log.Printf("Error opening city database: %v", err)
|
||||
updateDB(cityDBURL, &cityDB, &currCityFilename)
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("Error opening city database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
asnDB, err = maxminddb.Open(currASNFilename)
|
||||
asnDB, err = openDB(currASNFilename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
currASNFilename = ""
|
||||
doUpdate()
|
||||
if asnDB == nil {
|
||||
log.Fatalf("Failed to initialize ASN database: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("Error opening ASN database: %v", err)
|
||||
}
|
||||
log.Printf("Error opening ASN database: %v", err)
|
||||
updateDB(asnDBURL, &asnDB, &currASNFilename)
|
||||
}
|
||||
}
|
||||
|
||||
func startUpdater() {
|
||||
for range time.Tick(time.Hour * 24 * 7) {
|
||||
// Open a MaxMind DB file
|
||||
func openDB(filename string) (*maxminddb.Reader, error) {
|
||||
db, err := maxminddb.Open(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Download and set the database
|
||||
func updateDB(urlTemplate string, db **maxminddb.Reader, currFilename *string) {
|
||||
*currFilename = ""
|
||||
doUpdate()
|
||||
if *db == nil {
|
||||
log.Fatalf("Failed to initialize database from %s", urlTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
// Periodically update the databases
|
||||
func runUpdater() {
|
||||
for range time.Tick(24 * time.Hour * 7) {
|
||||
doUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
func startServer() {
|
||||
// Start the HTTP server
|
||||
func runServer() {
|
||||
log.Println("Server listening on :3000")
|
||||
http.HandleFunc("/", handler)
|
||||
log.Fatal(http.ListenAndServe(":3000", nil))
|
||||
}
|
||||
|
||||
// Fetch and update the GeoIP databases.
|
||||
// Fetch and update the GeoIP databases
|
||||
func doUpdate() {
|
||||
log.Println("Fetching updates...")
|
||||
currMonth := time.Now().Format("2006-01")
|
||||
|
@ -110,6 +115,7 @@ func doUpdate() {
|
|||
})
|
||||
}
|
||||
|
||||
// Update the specified database
|
||||
func updateDatabase(urlTemplate, dstFilename string, updateFunc func(*maxminddb.Reader)) {
|
||||
resp, err := http.Get(fmt.Sprintf(urlTemplate, time.Now().Format("2006-01")))
|
||||
if err != nil {
|
||||
|
@ -153,6 +159,7 @@ func updateDatabase(urlTemplate, dstFilename string, updateFunc func(*maxminddb.
|
|||
|
||||
var invalidIPBytes = []byte("Please provide a valid IP address.")
|
||||
|
||||
// Struct to hold IP data
|
||||
type dataStruct struct {
|
||||
IP *string `json:"ip"`
|
||||
Hostname *string `json:"hostname"`
|
||||
|
@ -167,6 +174,84 @@ type dataStruct struct {
|
|||
Loc *string `json:"loc"`
|
||||
}
|
||||
|
||||
type bogonDataStruct struct {
|
||||
IP string `json:"ip"`
|
||||
Bogon bool `json:"bogon"`
|
||||
}
|
||||
|
||||
// List of bogon IP networks
|
||||
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(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("::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("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
|
||||
// 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("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:ffff:ffff::/64"), Mask: net.CIDRMask(64, 128)}, // Teredo bogon (255.255.255.255/32)
|
||||
}
|
||||
|
||||
// Check if the IP is a bogon IP
|
||||
func isBogon(ip net.IP) bool {
|
||||
for _, net := range bogonNets {
|
||||
if net.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HTTP handler
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
requestedThings := strings.Split(r.URL.Path, "/")
|
||||
|
||||
|
@ -190,6 +275,17 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check if the IP is a bogon
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
data := lookupIPData(ip)
|
||||
if data == nil {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
@ -227,6 +323,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// Get specific field from dataStruct
|
||||
func getField(data *dataStruct, field string) *string {
|
||||
switch field {
|
||||
case "ip":
|
||||
|
@ -256,6 +353,7 @@ func getField(data *dataStruct, field string) *string {
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -266,6 +364,7 @@ func getRealIP(r *http.Request) string {
|
|||
}
|
||||
}
|
||||
|
||||
// Lookup IP data in the databases
|
||||
func lookupIPData(ip net.IP) *dataStruct {
|
||||
dbMtx.RLock()
|
||||
defer dbMtx.RUnlock()
|
||||
|
@ -332,6 +431,7 @@ func lookupIPData(ip net.IP) *dataStruct {
|
|||
}
|
||||
}
|
||||
|
||||
// Convert string to pointer
|
||||
func toPtr(s string) *string {
|
||||
if s == "" {
|
||||
return nil
|
||||
|
@ -339,9 +439,10 @@ 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.
|
||||
// Extract the IP address from a string, removing unwanted characters
|
||||
func extractIP(ip string) string {
|
||||
ip = strings.ReplaceAll(ip, "[", "")
|
||||
ip = strings.ReplaceAll(ip, "]", "")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue