lolly/internal/utils/ipallowlist.go
xfy edc135ae5f refactor(utils): enhance ParseCIDR to support single IP
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>
2026-05-08 18:20:09 +08:00

135 lines
3.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package utils 提供通用工具函数
package utils
import (
"fmt"
"net"
"strings"
)
// ParseIPAllowList 解析 IP/CIDR 白名单列表。
//
// 支持格式:
// - CIDR 格式192.168.1.0/24、::1/128
// - 单个 IP192.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
// - 单个 IP192.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
}