Day 103 — IP Reputation & ASN Classification
| Learning objective | Classify IP addresses as datacenter, residential, or known malicious |
| Key packages | net, strings |
| Security context | 60–70% of bot traffic originates from datacenter IPs; classification gives strong first-pass signal |
Key Concepts
ASN classification: Each ASN (Autonomous System Number) maps to an organization. AWS = ASN 16509, GCP = ASN 15169, etc. IPs in datacenter ASNs are more likely bots.
In production: Use MaxMind GeoIP2 or ipinfo.io. For this exercise: hardcode representative CIDR ranges for major cloud providers.
Exercise — IP Classifier
package bot
import (
"net"
)
type IPCategory string
const (
IPCategoryDatacenter IPCategory = "datacenter"
IPCategoryHosting IPCategory = "hosting"
IPCategoryResidential IPCategory = "residential"
IPCategoryUnknown IPCategory = "unknown"
)
type cidrEntry struct {
network *net.IPNet
provider string
category IPCategory
}
// Representative CIDR ranges for major cloud providers
// In production: use a regularly updated database
var cloudCIDRs = []struct {
cidr string
provider string
}{
// AWS
{"3.0.0.0/8", "AWS"},
{"13.32.0.0/11", "AWS CloudFront"},
{"52.0.0.0/8", "AWS"},
{"54.0.0.0/8", "AWS"},
// GCP
{"8.34.208.0/20", "GCP"},
{"34.0.0.0/9", "GCP"},
{"35.184.0.0/13", "GCP"},
// Azure
{"13.64.0.0/11", "Azure"},
{"20.0.0.0/8", "Azure"},
{"40.64.0.0/10", "Azure"},
// DigitalOcean
{"104.16.0.0/12", "DigitalOcean"},
{"159.89.0.0/16", "DigitalOcean"},
// Linode/Akamai
{"45.33.0.0/17", "Linode"},
{"139.162.0.0/16", "Linode"},
// Hetzner
{"5.9.0.0/16", "Hetzner"},
{"78.46.0.0/15", "Hetzner"},
}
var compiledCIDRs []cidrEntry
func init() {
for _, c := range cloudCIDRs {
_, network, err := net.ParseCIDR(c.cidr)
if err != nil {
continue
}
compiledCIDRs = append(compiledCIDRs, cidrEntry{
network: network,
provider: c.provider,
category: IPCategoryDatacenter,
})
}
}
func classifyIP(ipStr string) (IPCategory, string) {
ip := net.ParseIP(ipStr)
if ip == nil {
return IPCategoryUnknown, ""
}
for _, entry := range compiledCIDRs {
if entry.network.Contains(ip) {
return entry.category, entry.provider
}
}
// IPv6-only from unusual ASN — modest signal
if ip.To4() == nil {
return IPCategoryUnknown, "ipv6"
}
return IPCategoryResidential, ""
}
// IPReputationDetector implements Detector
type IPReputationDetector struct{}
func (d *IPReputationDetector) Detect(r *http.Request, session *SessionScore) {
ip := clientIP(r)
category, provider := classifyIP(ip)
switch category {
case IPCategoryDatacenter:
session.AddSignal(Signal{
"datacenter_ip", 15, CategoryNetwork,
fmt.Sprintf("IP belongs to %s (datacenter)", provider),
})
case IPCategoryHosting:
session.AddSignal(Signal{
"hosting_ip", 20, CategoryNetwork,
fmt.Sprintf("IP belongs to %s (hosting)", provider),
})
}
}