mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-23 21:00:44 +00:00
Rename denyIPs field to denyNets, supporting blacklist configuration with CIDR ranges. Now supports both individual IP addresses and CIDR subnet masks as deny rules, with IPv4 automatically converted to /32 and IPv6 to /128, using the Contains method for more flexible subnet matching.
133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
package netutil
|
|
|
|
import (
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
// IPAccessChecker checks if an IP address is allowed based on whitelist/blacklist rules.
|
|
type IPAccessChecker struct {
|
|
allowNets []*net.IPNet // Allowed CIDR ranges (whitelist)
|
|
denyNets []*net.IPNet // Denied CIDR ranges (blacklist)
|
|
hasAllow bool // Whether whitelist is configured
|
|
hasDeny bool // Whether blacklist is configured
|
|
}
|
|
|
|
// NewIPAccessChecker creates a new IP access checker from CIDR and IP lists.
|
|
// allowCIDRs: list of CIDR ranges to allow (e.g., "192.168.1.0/24", "10.0.0.0/8")
|
|
// denyIPs: list of CIDR ranges or IP addresses to deny (e.g., "192.168.0.0/16", "1.2.3.4")
|
|
func NewIPAccessChecker(allowCIDRs, denyIPs []string) *IPAccessChecker {
|
|
checker := &IPAccessChecker{}
|
|
|
|
// Parse allowed CIDRs
|
|
for _, cidr := range allowCIDRs {
|
|
cidr = strings.TrimSpace(cidr)
|
|
if cidr == "" {
|
|
continue
|
|
}
|
|
|
|
// If no "/" in the string, treat it as a single IP (/32 for IPv4, /128 for IPv6)
|
|
if !strings.Contains(cidr, "/") {
|
|
ip := net.ParseIP(cidr)
|
|
if ip != nil {
|
|
if ip.To4() != nil {
|
|
cidr = cidr + "/32"
|
|
} else {
|
|
cidr = cidr + "/128"
|
|
}
|
|
}
|
|
}
|
|
|
|
_, ipNet, err := net.ParseCIDR(cidr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
checker.allowNets = append(checker.allowNets, ipNet)
|
|
}
|
|
checker.hasAllow = len(checker.allowNets) > 0
|
|
|
|
// Parse denied IPs/CIDRs
|
|
for _, ipStr := range denyIPs {
|
|
ipStr = strings.TrimSpace(ipStr)
|
|
if ipStr == "" {
|
|
continue
|
|
}
|
|
|
|
// If no "/" in the string, treat it as a single IP (/32 for IPv4, /128 for IPv6)
|
|
if !strings.Contains(ipStr, "/") {
|
|
ip := net.ParseIP(ipStr)
|
|
if ip != nil {
|
|
if ip.To4() != nil {
|
|
ipStr = ipStr + "/32"
|
|
} else {
|
|
ipStr = ipStr + "/128"
|
|
}
|
|
}
|
|
}
|
|
|
|
_, ipNet, err := net.ParseCIDR(ipStr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
checker.denyNets = append(checker.denyNets, ipNet)
|
|
}
|
|
checker.hasDeny = len(checker.denyNets) > 0
|
|
|
|
return checker
|
|
}
|
|
|
|
// IsAllowed checks if the given IP address is allowed.
|
|
// Rules:
|
|
// 1. If IP is in deny list, reject
|
|
// 2. If whitelist is configured and IP is not in whitelist, reject
|
|
// 3. Otherwise, allow
|
|
func (c *IPAccessChecker) IsAllowed(ipStr string) bool {
|
|
if c == nil || (!c.hasAllow && !c.hasDeny) {
|
|
return true // No rules configured, allow all
|
|
}
|
|
|
|
ip := net.ParseIP(ipStr)
|
|
if ip == nil {
|
|
return false // Invalid IP, reject
|
|
}
|
|
|
|
// Check deny list first (blacklist takes priority)
|
|
if c.hasDeny {
|
|
for _, denyNet := range c.denyNets {
|
|
if denyNet.Contains(ip) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check allow list (whitelist)
|
|
if c.hasAllow {
|
|
for _, allowNet := range c.allowNets {
|
|
if allowNet.Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false // Whitelist configured but IP not in it
|
|
}
|
|
|
|
return true // No whitelist, and not in blacklist
|
|
}
|
|
|
|
// HasRules returns true if any access control rules are configured.
|
|
func (c *IPAccessChecker) HasRules() bool {
|
|
return c != nil && (c.hasAllow || c.hasDeny)
|
|
}
|
|
|
|
// ExtractIP extracts the IP address from a remote address string (e.g., "192.168.1.1:12345").
|
|
func ExtractIP(remoteAddr string) string {
|
|
host, _, err := net.SplitHostPort(remoteAddr)
|
|
if err != nil {
|
|
// Maybe it's just an IP without port
|
|
if ip := net.ParseIP(remoteAddr); ip != nil {
|
|
return remoteAddr
|
|
}
|
|
return ""
|
|
}
|
|
return host
|
|
}
|