feat: bogon

This commit is contained in:
skidoodle 2024-07-11 09:58:58 +02:00
parent ea53882eea
commit 0fb3779ee0

171
main.go
View file

@ -18,13 +18,13 @@ import (
) )
// GeoIP database readers // GeoIP database readers
var cityDB *maxminddb.Reader
var asnDB *maxminddb.Reader
var ( var (
cityDB *maxminddb.Reader
asnDB *maxminddb.Reader
dbMtx sync.RWMutex
currCityFilename = time.Now().Format("2006-01") + "-city.mmdb" currCityFilename = time.Now().Format("2006-01") + "-city.mmdb"
currASNFilename = time.Now().Format("2006-01") + "-asn.mmdb" currASNFilename = time.Now().Format("2006-01") + "-asn.mmdb"
dbMtx = new(sync.RWMutex)
) )
const ( const (
@ -33,54 +33,59 @@ const (
) )
func main() { func main() {
initDatabases() initDBs()
go startUpdater() go runUpdater()
startServer() runServer()
} }
func initDatabases() { // Initialize the databases
func initDBs() {
var err error var err error
cityDB, err = openDB(currCityFilename)
cityDB, err = maxminddb.Open(currCityFilename)
if err != nil { if err != nil {
if os.IsNotExist(err) { log.Printf("Error opening city database: %v", err)
currCityFilename = "" updateDB(cityDBURL, &cityDB, &currCityFilename)
doUpdate()
if cityDB == nil {
log.Fatalf("Failed to initialize city database: %v", err)
} }
} else { asnDB, err = openDB(currASNFilename)
log.Fatalf("Error opening city database: %v", err)
}
}
asnDB, err = maxminddb.Open(currASNFilename)
if err != nil { if err != nil {
if os.IsNotExist(err) { log.Printf("Error opening ASN database: %v", err)
currASNFilename = "" updateDB(asnDBURL, &asnDB, &currASNFilename)
doUpdate()
if asnDB == nil {
log.Fatalf("Failed to initialize ASN database: %v", err)
}
} else {
log.Fatalf("Error opening ASN database: %v", err)
}
} }
} }
func startUpdater() { // Open a MaxMind DB file
for range time.Tick(time.Hour * 24 * 7) { 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() doUpdate()
} }
} }
func startServer() { // Start the HTTP server
func runServer() {
log.Println("Server listening on :3000") log.Println("Server listening on :3000")
http.HandleFunc("/", handler) http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":3000", nil)) log.Fatal(http.ListenAndServe(":3000", nil))
} }
// Fetch and update the GeoIP databases. // Fetch and update the GeoIP databases
func doUpdate() { func doUpdate() {
log.Println("Fetching updates...") log.Println("Fetching updates...")
currMonth := time.Now().Format("2006-01") 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)) { func updateDatabase(urlTemplate, dstFilename string, updateFunc func(*maxminddb.Reader)) {
resp, err := http.Get(fmt.Sprintf(urlTemplate, time.Now().Format("2006-01"))) resp, err := http.Get(fmt.Sprintf(urlTemplate, time.Now().Format("2006-01")))
if err != nil { if err != nil {
@ -153,6 +159,7 @@ func updateDatabase(urlTemplate, dstFilename string, updateFunc func(*maxminddb.
var invalidIPBytes = []byte("Please provide a valid IP address.") var invalidIPBytes = []byte("Please provide a valid IP address.")
// Struct to hold IP data
type dataStruct struct { type dataStruct struct {
IP *string `json:"ip"` IP *string `json:"ip"`
Hostname *string `json:"hostname"` Hostname *string `json:"hostname"`
@ -167,6 +174,84 @@ type dataStruct struct {
Loc *string `json:"loc"` 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) { func handler(w http.ResponseWriter, r *http.Request) {
requestedThings := strings.Split(r.URL.Path, "/") requestedThings := strings.Split(r.URL.Path, "/")
@ -190,6 +275,17 @@ func handler(w http.ResponseWriter, r *http.Request) {
return 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) data := lookupIPData(ip)
if data == nil { if data == nil {
w.Header().Set("Content-Type", "text/plain; charset=utf-8") 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 { func getField(data *dataStruct, field string) *string {
switch field { switch field {
case "ip": 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 { func getRealIP(r *http.Request) string {
if realIP := r.Header.Get("CF-Connecting-IP"); realIP != "" { if realIP := r.Header.Get("CF-Connecting-IP"); realIP != "" {
return 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 { func lookupIPData(ip net.IP) *dataStruct {
dbMtx.RLock() dbMtx.RLock()
defer dbMtx.RUnlock() defer dbMtx.RUnlock()
@ -332,6 +431,7 @@ func lookupIPData(ip net.IP) *dataStruct {
} }
} }
// Convert string to pointer
func toPtr(s string) *string { func toPtr(s string) *string {
if s == "" { if s == "" {
return nil return nil
@ -339,9 +439,10 @@ func toPtr(s string) *string {
return &s return &s
} }
// Validate JSONP callback name
var callbackJSONP = regexp.MustCompile(`^[a-zA-Z_\$][a-zA-Z0-9_\$]*$`) 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 { func extractIP(ip string) string {
ip = strings.ReplaceAll(ip, "[", "") ip = strings.ReplaceAll(ip, "[", "")
ip = strings.ReplaceAll(ip, "]", "") ip = strings.ReplaceAll(ip, "]", "")