diff --git a/main.go b/main.go index 645e472..f943476 100644 --- a/main.go +++ b/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) - } - } else { - log.Fatalf("Error opening city database: %v", err) - } + log.Printf("Error opening city database: %v", err) + updateDB(cityDBURL, &cityDB, &currCityFilename) } - - 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, "]", "")