Enhance parseCIDR in utils/ipallowlist.go to support single IP addresses (without CIDR prefix) and ensure IP is in canonical form. This matches the functionality previously in access.go. - Add ParseCIDR as public function supporting CIDR and single IP - Update access.go to use utils.ParseCIDR instead of local implementation - Remove duplicate parseCIDR function from access.go - Update tests to use utils.ParseCIDR Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
135 lines
3.0 KiB
Go
135 lines
3.0 KiB
Go
// Package utils 提供通用工具函数
|
||
package utils
|
||
|
||
import (
|
||
"fmt"
|
||
"net"
|
||
"strings"
|
||
)
|
||
|
||
// ParseIPAllowList 解析 IP/CIDR 白名单列表。
|
||
//
|
||
// 支持格式:
|
||
// - CIDR 格式:192.168.1.0/24、::1/128
|
||
// - 单个 IP:192.168.1.1(自动转换为 /32 或 /128)
|
||
// - 特殊值 "localhost":映射为 127.0.0.1/32 和 ::1/128
|
||
//
|
||
// 参数:
|
||
// - allow: IP/CIDR 字符串列表
|
||
//
|
||
// 返回值:
|
||
// - []net.IPNet: 解析后的网络列表
|
||
// - error: 解析失败时返回错误
|
||
func ParseIPAllowList(allow []string) ([]net.IPNet, error) {
|
||
if len(allow) == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
result := make([]net.IPNet, 0, len(allow)+2) // +2 for localhost expansion
|
||
|
||
for _, cidr := range allow {
|
||
// 处理 localhost 特殊情况
|
||
if cidr == "localhost" {
|
||
// localhost 解析为 127.0.0.1 和 ::1
|
||
if v4Net, err := parseCIDR("127.0.0.1/32"); err == nil {
|
||
result = append(result, *v4Net)
|
||
}
|
||
if v6Net, err := parseCIDR("::1/128"); err == nil {
|
||
result = append(result, *v6Net)
|
||
}
|
||
continue
|
||
}
|
||
|
||
// 尝试 CIDR 解析
|
||
_, network, err := net.ParseCIDR(cidr)
|
||
if err == nil && network != nil {
|
||
result = append(result, *network)
|
||
continue
|
||
}
|
||
|
||
// fallback: 尝试作为单个 IP 解析
|
||
ip := net.ParseIP(cidr)
|
||
if ip == nil {
|
||
return nil, err // 返回原始 CIDR 解析错误
|
||
}
|
||
|
||
// 转换为 CIDR 格式
|
||
var ipNet *net.IPNet
|
||
if ip.To4() != nil {
|
||
ipNet, _ = parseCIDR(cidr + "/32")
|
||
} else {
|
||
ipNet, _ = parseCIDR(cidr + "/128")
|
||
}
|
||
if ipNet != nil {
|
||
result = append(result, *ipNet)
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// ParseCIDR 解析 CIDR 字符串或单个 IP 地址。
|
||
//
|
||
// 支持格式:
|
||
// - CIDR 格式:192.168.1.0/24、::1/128
|
||
// - 单个 IP:192.168.1.1(自动转换为 /32 或 /128)
|
||
//
|
||
// 参数:
|
||
// - cidr: CIDR 字符串或单个 IP 地址
|
||
//
|
||
// 返回值:
|
||
// - *net.IPNet: 解析后的 IP 网络对象
|
||
// - error: 解析失败时返回错误
|
||
func ParseCIDR(cidr string) (*net.IPNet, error) {
|
||
// 处理单个 IP(没有 /前缀)
|
||
if !strings.Contains(cidr, "/") {
|
||
ip := net.ParseIP(cidr)
|
||
if ip == nil {
|
||
return nil, fmt.Errorf("invalid IP address: %s", cidr)
|
||
}
|
||
|
||
// 转换为完整掩码的 CIDR
|
||
if ip.To4() != nil {
|
||
cidr = cidr + "/32"
|
||
} else {
|
||
cidr = cidr + "/128"
|
||
}
|
||
}
|
||
|
||
// 解析 CIDR
|
||
ip, network, err := net.ParseCIDR(cidr)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 确保 IP 为规范形式
|
||
network.IP = ip
|
||
|
||
return network, nil
|
||
}
|
||
|
||
// parseCIDR 是 ParseCIDR 的内部别名,保持向后兼容
|
||
func parseCIDR(cidr string) (*net.IPNet, error) {
|
||
return ParseCIDR(cidr)
|
||
}
|
||
|
||
// IPInAllowList 检查 IP 是否在白名单中。
|
||
//
|
||
// 参数:
|
||
// - ip: 要检查的 IP 地址
|
||
// - allowList: 白名单网络列表
|
||
//
|
||
// 返回值:
|
||
// - bool: IP 在白名单中返回 true
|
||
func IPInAllowList(ip net.IP, allowList []net.IPNet) bool {
|
||
if len(allowList) == 0 {
|
||
return false
|
||
}
|
||
for _, network := range allowList {
|
||
if network.Contains(ip) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|