fix: 修复IPV6子网掩码识别错误的问题

This commit is contained in:
spiritlhl
2025-05-17 10:16:34 +00:00
parent 30eb9bc600
commit bf07ce0aa0
6 changed files with 87 additions and 116 deletions

View File

@@ -27,7 +27,7 @@ jobs:
run: | run: |
git config --global user.name 'github-actions' git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com' git config --global user.email 'github-actions@github.com'
TAG="v0.0.11-$(date +'%Y%m%d%H%M%S')" TAG="v0.0.12-$(date +'%Y%m%d%H%M%S')"
git tag $TAG git tag $TAG
git push origin $TAG git push origin $TAG
echo "TAG=$TAG" >> $GITHUB_ENV echo "TAG=$TAG" >> $GITHUB_ENV

View File

@@ -1,6 +1,6 @@
package model package model
const BasicsVersion = "v0.0.11" const BasicsVersion = "v0.0.12"
var EnableLoger bool var EnableLoger bool

View File

@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"os/exec" "os/exec"
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
@@ -17,20 +18,14 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
// 匹配 inet6 地址和前缀长度
re := regexp.MustCompile(`inet6\s+([a-fA-F0-9:]+)%?\w*\s+prefixlen\s+(\d+)`) re := regexp.MustCompile(`inet6\s+([a-fA-F0-9:]+)%?\w*\s+prefixlen\s+(\d+)`)
matches := re.FindAllStringSubmatch(string(output), -1) matches := re.FindAllStringSubmatch(string(output), -1)
for _, match := range matches { for _, match := range matches {
if len(match) < 3 { if len(match) < 3 {
continue continue
} }
ipv6Addr := match[1] ipv6Addr := match[1]
prefixLen := match[2] prefixLen := match[2]
// 排除非公网地址前缀
if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址
strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "::1") || // 回环地址
strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址
@@ -38,29 +33,26 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) {
strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 strings.HasPrefix(ipv6Addr, "ff") { // 组播地址
continue continue
} }
prefixLenInt, err := strconv.Atoi(prefixLen)
if err != nil || prefixLenInt < 0 || prefixLenInt > 128 {
continue
}
return prefixLen, nil return prefixLen, nil
} }
return "", fmt.Errorf("未找到全局IPv6地址") return "", fmt.Errorf("未找到全局IPv6地址")
} }
// macOS平台上使用networksetup命令获取更多信息 // macOS平台上使用networksetup命令获取更多信息
func getPrefixFromNetworksetup(interfaceName string) (string, error) { func getPrefixFromNetworksetup(interfaceName string) (string, error) {
// 首先需要获取网络服务名称
cmd := exec.Command("networksetup", "-listallhardwareports") cmd := exec.Command("networksetup", "-listallhardwareports")
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
return "", err return "", err
} }
// 解析输出查找对应的网络服务名称
lines := strings.Split(string(output), "\n") lines := strings.Split(string(output), "\n")
var serviceName string var serviceName string
for i := 0; i < len(lines); i++ { for i := 0; i < len(lines); i++ {
if strings.Contains(lines[i], "Device: "+interfaceName) && i > 0 { if strings.Contains(lines[i], "Device: "+interfaceName) && i > 0 {
// 服务名称在上一行
serviceNameLine := lines[i-1] serviceNameLine := lines[i-1]
if strings.HasPrefix(serviceNameLine, "Hardware Port: ") { if strings.HasPrefix(serviceNameLine, "Hardware Port: ") {
serviceName = strings.TrimPrefix(serviceNameLine, "Hardware Port: ") serviceName = strings.TrimPrefix(serviceNameLine, "Hardware Port: ")
@@ -68,35 +60,32 @@ func getPrefixFromNetworksetup(interfaceName string) (string, error) {
} }
} }
} }
if serviceName == "" { if serviceName == "" {
return "", fmt.Errorf("未找到网络接口对应的服务名称") return "", fmt.Errorf("未找到网络接口对应的服务名称")
} }
// 使用服务名称获取IPv6配置
cmd = exec.Command("networksetup", "-getinfo", serviceName) cmd = exec.Command("networksetup", "-getinfo", serviceName)
output, err = cmd.Output() output, err = cmd.Output()
if err != nil { if err != nil {
return "", err return "", err
} }
// 查找IPv6前缀长度
re := regexp.MustCompile(`IPv6:\s*Automatic\s*\nIPv6\s*Address:\s*([a-fA-F0-9:]+)\s*\nIPv6\s*Prefix\s*Length:\s*(\d+)`) re := regexp.MustCompile(`IPv6:\s*Automatic\s*\nIPv6\s*Address:\s*([a-fA-F0-9:]+)\s*\nIPv6\s*Prefix\s*Length:\s*(\d+)`)
match := re.FindStringSubmatch(string(output)) match := re.FindStringSubmatch(string(output))
if len(match) >= 3 { if len(match) >= 3 {
return match[2], nil prefixLen := match[2]
prefixLenInt, err := strconv.Atoi(prefixLen)
if err != nil || prefixLenInt < 0 || prefixLenInt > 128 {
return "", fmt.Errorf("无效的IPv6前缀长度: %s", prefixLen)
}
return prefixLen, nil
} }
return "", fmt.Errorf("未找到IPv6前缀长度信息") return "", fmt.Errorf("未找到IPv6前缀长度信息")
} }
// 获取 IPv6 子网掩码 - macOS 实现 // 获取 IPv6 子网掩码
func GetIPv6Mask(publicIPv6, language string) (string, error) { func GetIPv6Mask(publicIPv6, language string) (string, error) {
if publicIPv6 == "" { if publicIPv6 == "" {
return "", fmt.Errorf("无公网IPV6地址") return "", fmt.Errorf("无公网IPV6地址")
} }
// 获取网络接口
interfaceName, err := getInterface() interfaceName, err := getInterface()
if err != nil || interfaceName == "" { if err != nil || interfaceName == "" {
return "", fmt.Errorf("获取网络接口失败: %v", err) return "", fmt.Errorf("获取网络接口失败: %v", err)
@@ -111,6 +100,5 @@ func GetIPv6Mask(publicIPv6, language string) (string, error) {
if err == nil && prefixLen != "" { if err == nil && prefixLen != "" {
return formatIPv6Mask(prefixLen, language), nil return formatIPv6Mask(prefixLen, language), nil
} }
// 如果以上方法都失败但确实有公网IPv6使用/128作为默认子网掩码
return formatIPv6Mask("128", language), nil return formatIPv6Mask("128", language), nil
} }

View File

@@ -8,6 +8,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
@@ -18,7 +19,6 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
// 匹配 inet6 地址和前缀长度
re := regexp.MustCompile(`inet6\s+([a-fA-F0-9:]+)%?\w*\s+prefixlen\s+(\d+)`) re := regexp.MustCompile(`inet6\s+([a-fA-F0-9:]+)%?\w*\s+prefixlen\s+(\d+)`)
matches := re.FindAllStringSubmatch(string(output), -1) matches := re.FindAllStringSubmatch(string(output), -1)
for _, match := range matches { for _, match := range matches {
@@ -27,7 +27,6 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) {
} }
ipv6Addr := match[1] ipv6Addr := match[1]
prefixLen := match[2] prefixLen := match[2]
// 排除非公网地址前缀
if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址
strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "::1") || // 回环地址
strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址
@@ -35,6 +34,11 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) {
strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 strings.HasPrefix(ipv6Addr, "ff") { // 组播地址
continue continue
} }
prefixLenInt, err := strconv.Atoi(prefixLen)
if err != nil || prefixLenInt < 0 || prefixLenInt > 128 {
continue
}
return prefixLen, nil return prefixLen, nil
} }
return "", fmt.Errorf("未找到全局IPv6地址") return "", fmt.Errorf("未找到全局IPv6地址")
@@ -42,7 +46,6 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) {
// FreeBSD平台专用的配置文件获取方法 // FreeBSD平台专用的配置文件获取方法
func getPrefixFromConfigFiles() (string, error) { func getPrefixFromConfigFiles() (string, error) {
// FreeBSD 的网络配置文件
configFiles := []string{ configFiles := []string{
"/etc/rc.conf", "/etc/rc.conf",
"/etc/rc.conf.local", "/etc/rc.conf.local",
@@ -53,23 +56,26 @@ func getPrefixFromConfigFiles() (string, error) {
if err != nil { if err != nil {
continue continue
} }
// 在配置文件中查找IPv6前缀长度
re := regexp.MustCompile(`(?i)ipv6_prefix(len)?="?(\d+)"?`) re := regexp.MustCompile(`(?i)ipv6_prefix(len)?="?(\d+)"?`)
matches := re.FindStringSubmatch(string(content)) matches := re.FindStringSubmatch(string(content))
if len(matches) >= 3 { if len(matches) >= 3 {
return matches[2], nil prefixLen := matches[2]
prefixLenInt, err := strconv.Atoi(prefixLen)
if err != nil || prefixLenInt < 0 || prefixLenInt > 128 {
continue
}
return prefixLen, nil
} }
} }
} }
return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息") return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息")
} }
// 获取 IPv6 子网掩码 - FreeBSD 实现 // 获取 IPv6 子网掩码
func GetIPv6Mask(publicIPv6, language string) (string, error) { func GetIPv6Mask(publicIPv6, language string) (string, error) {
if publicIPv6 == "" { if publicIPv6 == "" {
return "", fmt.Errorf("无公网IPV6地址") return "", fmt.Errorf("无公网IPV6地址")
} }
// 获取网络接口
interfaceName, err := getInterface() interfaceName, err := getInterface()
if err != nil || interfaceName == "" { if err != nil || interfaceName == "" {
return "", fmt.Errorf("获取网络接口失败: %v", err) return "", fmt.Errorf("获取网络接口失败: %v", err)
@@ -84,6 +90,5 @@ func GetIPv6Mask(publicIPv6, language string) (string, error) {
if err == nil && prefixLen != "" { if err == nil && prefixLen != "" {
return formatIPv6Mask(prefixLen, language), nil return formatIPv6Mask(prefixLen, language), nil
} }
// 如果以上方法都失败但确实有公网IPv6使用/128作为默认子网掩码
return formatIPv6Mask("128", language), nil return formatIPv6Mask("128", language), nil
} }

View File

@@ -11,46 +11,37 @@ import (
"os/exec" "os/exec"
"regexp" "regexp"
"sort" "sort"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
) )
// Router Advertisement前缀选项类型
const ( const (
ICMPv6RouterAdvertisement = 134 ICMPv6RouterAdvertisement = 134
ICMPv6RouterSolicitation = 133 ICMPv6RouterSolicitation = 133
ICMPv6OptionPrefix = 3 ICMPv6OptionPrefix = 3
) )
// 从RA报文中提取前缀长度信息
func extractPrefixFromRAOption(data []byte) []string { func extractPrefixFromRAOption(data []byte) []string {
var prefixLengths []string var prefixLengths []string
// 跳过ICMPv6头部(4字节)和Router Advertisement基本信息(12字节)
optionStart := 16 optionStart := 16
// 遍历所有选项
for optionStart < len(data) { for optionStart < len(data) {
// 确保有足够的数据可读
if optionStart+2 > len(data) { if optionStart+2 > len(data) {
break break
} }
optionType := data[optionStart] optionType := data[optionStart]
optionLen := data[optionStart+1] * 8 // 长度以8字节为单位 optionLen := data[optionStart+1] * 8
// 确保选项长度有效
if optionLen == 0 || optionStart+int(optionLen) > len(data) { if optionLen == 0 || optionStart+int(optionLen) > len(data) {
break break
} }
// 解析前缀信息选项
if optionType == ICMPv6OptionPrefix && optionLen >= 32 { if optionType == ICMPv6OptionPrefix && optionLen >= 32 {
// 前缀长度在选项开始后2字节处
prefixLen := data[optionStart+2] prefixLen := data[optionStart+2]
// 前缀值从选项开始后16字节处
prefixStart := optionStart + 16 prefixStart := optionStart + 16
if prefixStart+16 <= len(data) { if prefixStart+16 <= len(data) {
var prefix [16]byte var prefix [16]byte
copy(prefix[:], data[prefixStart:prefixStart+16]) copy(prefix[:], data[prefixStart:prefixStart+16])
// 排除非全局单播地址 if !isNonGlobalPrefix(prefix) && isPrefixLengthValid(int(prefixLen)) {
if !isNonGlobalPrefix(prefix) {
prefixLengths = append(prefixLengths, fmt.Sprintf("%d", prefixLen)) prefixLengths = append(prefixLengths, fmt.Sprintf("%d", prefixLen))
} }
} }
@@ -60,46 +51,36 @@ func extractPrefixFromRAOption(data []byte) []string {
return prefixLengths return prefixLengths
} }
// 发送Router Solicitation消息 func isPrefixLengthValid(prefixLen int) bool {
return prefixLen >= 1 && prefixLen <= 128
}
func sendRouterSolicitation(fd int, interfaceName string) error { func sendRouterSolicitation(fd int, interfaceName string) error {
// 获取接口信息
intf, err := net.InterfaceByName(interfaceName) intf, err := net.InterfaceByName(interfaceName)
if err != nil { if err != nil {
return fmt.Errorf("获取接口信息失败: %v", err) return fmt.Errorf("获取接口信息失败: %v", err)
} }
// 构造ICMPv6 Router Solicitation消息
// ICMPv6头部(4字节): 类型(1字节) + 代码(1字节) + 校验和(2字节)
msg := make([]byte, 8) msg := make([]byte, 8)
msg[0] = ICMPv6RouterSolicitation // 类型:Router Solicitation msg[0] = ICMPv6RouterSolicitation
msg[1] = 0 // 代码:0 msg[1] = 0
// msg[2]和msg[3]是校验和字段暂时为0稍后计算 if len(intf.HardwareAddr) == 6 {
// 可选添加Source Link-Layer Address选项MAC地址 msg = append(msg, 1)
// 这对一些路由器来说可能是必要的 msg = append(msg, 1)
if len(intf.HardwareAddr) == 6 { // 确保MAC地址有效 msg = append(msg, intf.HardwareAddr...)
// 添加源链路层地址选项 msg = append(msg, 0, 0)
// 选项类型(1) + 长度(1) + MAC地址(6)
msg = append(msg, 1) // 选项类型:1 (Source Link-Layer Address)
msg = append(msg, 1) // 长度:1 (以8字节为单位这里是8字节)
msg = append(msg, intf.HardwareAddr...) // MAC地址
msg = append(msg, 0, 0) // 填充到8字节对齐
} }
// 设置ICMPv6的目标地址为All Routers组播地址
var allRoutersAddr [16]byte var allRoutersAddr [16]byte
copy(allRoutersAddr[:], net.ParseIP("ff02::2").To16()) copy(allRoutersAddr[:], net.ParseIP("ff02::2").To16())
// 构造sockaddr_in6结构
var addr syscall.SockaddrInet6 var addr syscall.SockaddrInet6
addr.ZoneId = uint32(intf.Index) addr.ZoneId = uint32(intf.Index)
copy(addr.Addr[:], allRoutersAddr[:]) copy(addr.Addr[:], allRoutersAddr[:])
// 发送数据包
if err := syscall.Sendto(fd, msg, 0, &addr); err != nil { if err := syscall.Sendto(fd, msg, 0, &addr); err != nil {
return fmt.Errorf("发送Router Solicitation失败: %v", err) return fmt.Errorf("发送Router Solicitation失败: %v", err)
} }
return nil return nil
} }
// 方法1尝试原生实现从Router Advertisement获取前缀长度
func getPrefixFromRA(interfaceName string) (string, error) { func getPrefixFromRA(interfaceName string) (string, error) {
// 尝试先用radvdump如果存在的话
radvdumpPath, err := exec.LookPath("radvdump") radvdumpPath, err := exec.LookPath("radvdump")
if err == nil && radvdumpPath != "" { if err == nil && radvdumpPath != "" {
cmd := exec.Command("radvdump", "-i", interfaceName) cmd := exec.Command("radvdump", "-i", interfaceName)
@@ -110,7 +91,10 @@ func getPrefixFromRA(interfaceName string) (string, error) {
for _, match := range matches { for _, match := range matches {
prefix := match[1] prefix := match[1]
prefixLen := match[2] prefixLen := match[2]
// 排除非公网地址前缀 prefixLenInt, err := strconv.Atoi(prefixLen)
if err != nil || !isPrefixLengthValid(prefixLenInt) {
continue
}
if !strings.HasPrefix(prefix, "fe80") && if !strings.HasPrefix(prefix, "fe80") &&
!strings.HasPrefix(prefix, "::1") && !strings.HasPrefix(prefix, "::1") &&
!strings.HasPrefix(prefix, "fc") && !strings.HasPrefix(prefix, "fc") &&
@@ -121,42 +105,34 @@ func getPrefixFromRA(interfaceName string) (string, error) {
} }
} }
} }
// 如果radvdump不可用或未找到有效前缀使用原生实现
intf, err := net.InterfaceByName(interfaceName) intf, err := net.InterfaceByName(interfaceName)
if err != nil { if err != nil {
return "", fmt.Errorf("获取网络接口失败: %v", err) return "", fmt.Errorf("获取网络接口失败: %v", err)
} }
// 创建原始套接字接收ICMPv6消息
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_ICMPV6) fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_ICMPV6)
if err != nil { if err != nil {
return "", fmt.Errorf("创建原始套接字失败: %v", err) return "", fmt.Errorf("创建原始套接字失败: %v", err)
} }
defer syscall.Close(fd) defer syscall.Close(fd)
// 绑定到指定接口
if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, intf.Index); err != nil { if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, intf.Index); err != nil {
return "", fmt.Errorf("绑定套接字到接口失败: %v", err) return "", fmt.Errorf("绑定套接字到接口失败: %v", err)
} }
// 设置过滤器只接收Router Advertisement消息
if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil { if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil {
return "", fmt.Errorf("设置套接字选项失败: %v", err) return "", fmt.Errorf("设置套接字选项失败: %v", err)
} }
// 设置接收超时
tv := syscall.Timeval{ tv := syscall.Timeval{
Sec: 5, // 5秒超时 Sec: 5,
Usec: 0, Usec: 0,
} }
if err := syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv); err != nil { if err := syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv); err != nil {
return "", fmt.Errorf("设置接收超时失败: %v", err) return "", fmt.Errorf("设置接收超时失败: %v", err)
} }
// 创建上下文用于发送Router Solicitation并等待Router Advertisement
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
// 发送Router Solicitation
err = sendRouterSolicitation(fd, interfaceName) err = sendRouterSolicitation(fd, interfaceName)
if err != nil { if err != nil {
return "", fmt.Errorf("发送Router Solicitation失败: %v", err) return "", fmt.Errorf("发送Router Solicitation失败: %v", err)
} }
// 接收Router Advertisement
buffer := make([]byte, 1500) buffer := make([]byte, 1500)
for { for {
select { select {
@@ -170,16 +146,12 @@ func getPrefixFromRA(interfaceName string) (string, error) {
} }
return "", fmt.Errorf("接收数据失败: %v", err) return "", fmt.Errorf("接收数据失败: %v", err)
} }
// 确保收到的是ICMPv6消息
if n < 4 { if n < 4 {
continue continue
} }
// 检查是否为Router Advertisement
if buffer[0] == ICMPv6RouterAdvertisement { if buffer[0] == ICMPv6RouterAdvertisement {
// 解析数据包提取前缀长度
prefixLengths := extractPrefixFromRAOption(buffer[:n]) prefixLengths := extractPrefixFromRAOption(buffer[:n])
if len(prefixLengths) > 0 { if len(prefixLengths) > 0 {
// 返回第一个有效前缀长度
return prefixLengths[0], nil return prefixLengths[0], nil
} }
} }
@@ -187,45 +159,43 @@ func getPrefixFromRA(interfaceName string) (string, error) {
} }
} }
// 方法2从ip命令获取前缀长度
func getPrefixFromIPCommand(interfaceName string) (string, error) { func getPrefixFromIPCommand(interfaceName string) (string, error) {
cmd := exec.Command("ip", "-o", "-6", "addr", "show", interfaceName) cmd := exec.Command("ip", "-o", "-6", "addr", "show", interfaceName)
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
return "", err return "", err
} }
// 匹配 inet6 地址和前缀长度
re := regexp.MustCompile(`\s*inet6\s+([a-fA-F0-9:]+)/(\d+)\s+scope\s+global`) re := regexp.MustCompile(`\s*inet6\s+([a-fA-F0-9:]+)/(\d+)\s+scope\s+global`)
matches := re.FindAllStringSubmatch(string(output), -1) matches := re.FindAllStringSubmatch(string(output), -1)
if len(matches) == 0 { if len(matches) == 0 {
return "", fmt.Errorf("未找到全局IPv6地址") return "", fmt.Errorf("未找到全局IPv6地址")
} }
var prefixLens []string var prefixLens []int
for _, match := range matches { for _, match := range matches {
ipv6Addr := match[1] ipv6Addr := match[1]
// 排除非公网地址前缀 if strings.HasPrefix(ipv6Addr, "fe80") ||
if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 strings.HasPrefix(ipv6Addr, "::1") ||
strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "fc") ||
strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 strings.HasPrefix(ipv6Addr, "fd") ||
strings.HasPrefix(ipv6Addr, "fd") || // 本地唯一地址 strings.HasPrefix(ipv6Addr, "ff") {
strings.HasPrefix(ipv6Addr, "ff") { // 组播地址
continue continue
} }
// 提取 prefixlen
if len(match) > 2 { if len(match) > 2 {
prefixLens = append(prefixLens, match[2]) prefixLen, err := strconv.Atoi(match[2])
if err != nil || !isPrefixLengthValid(prefixLen) {
continue
}
prefixLens = append(prefixLens, prefixLen)
} }
} }
if len(prefixLens) >= 1 { if len(prefixLens) >= 1 {
sort.Strings(prefixLens) sort.Ints(prefixLens)
return prefixLens[0], nil return strconv.Itoa(prefixLens[0]), nil
} }
return "", fmt.Errorf("未找到有效的IPv6前缀长度") return "", fmt.Errorf("未找到有效的IPv6前缀长度")
} }
// Linux平台专用的配置文件获取方法
func getPrefixFromConfigFiles() (string, error) { func getPrefixFromConfigFiles() (string, error) {
// 尝试从常见的网络配置文件中读取
configFiles := []string{ configFiles := []string{
"/etc/network/interfaces", "/etc/network/interfaces",
"/etc/netplan/01-netcfg.yaml", "/etc/netplan/01-netcfg.yaml",
@@ -238,10 +208,13 @@ func getPrefixFromConfigFiles() (string, error) {
if err != nil { if err != nil {
continue continue
} }
// 在配置文件中查找IPv6前缀长度
re := regexp.MustCompile(`(?i)(prefix-length|prefixlen|netmask)[\s:=]+(\d+)`) re := regexp.MustCompile(`(?i)(prefix-length|prefixlen|netmask)[\s:=]+(\d+)`)
matches := re.FindStringSubmatch(string(content)) matches := re.FindStringSubmatch(string(content))
if len(matches) >= 3 { if len(matches) >= 3 {
prefixLen, err := strconv.Atoi(matches[2])
if err != nil || !isPrefixLengthValid(prefixLen) {
continue
}
return matches[2], nil return matches[2], nil
} }
} }
@@ -249,31 +222,35 @@ func getPrefixFromConfigFiles() (string, error) {
return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息") return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息")
} }
// 获取 IPv6 子网掩码 - Linux 实现
func GetIPv6Mask(publicIPv6, language string) (string, error) { func GetIPv6Mask(publicIPv6, language string) (string, error) {
if publicIPv6 == "" { if publicIPv6 == "" {
return "", fmt.Errorf("无公网IPV6地址") return "", fmt.Errorf("无公网IPV6地址")
} }
// 获取网络接口
interfaceName, err := getInterface() interfaceName, err := getInterface()
if err != nil || interfaceName == "" { if err != nil || interfaceName == "" {
return "", fmt.Errorf("获取网络接口失败: %v", err) return "", fmt.Errorf("获取网络接口失败: %v", err)
} }
// 优先级1尝试从RA报文获取前缀长度 var prefixLen string
prefixLen, err := getPrefixFromRA(interfaceName) // 优先级1从RA报文获取前缀长度
prefixLen, err = getPrefixFromRA(interfaceName)
if err == nil && prefixLen != "" { if err == nil && prefixLen != "" {
if len, err := strconv.Atoi(prefixLen); err == nil && isPrefixLengthValid(len) {
return formatIPv6Mask(prefixLen, language), nil return formatIPv6Mask(prefixLen, language), nil
} }
}
// 优先级2从ip命令获取前缀长度 // 优先级2从ip命令获取前缀长度
prefixLen, err = getPrefixFromIPCommand(interfaceName) prefixLen, err = getPrefixFromIPCommand(interfaceName)
if err == nil && prefixLen != "" { if err == nil && prefixLen != "" {
if len, err := strconv.Atoi(prefixLen); err == nil && isPrefixLengthValid(len) {
return formatIPv6Mask(prefixLen, language), nil return formatIPv6Mask(prefixLen, language), nil
} }
}
// 优先级3从配置文件获取前缀长度 // 优先级3从配置文件获取前缀长度
prefixLen, err = getPrefixFromConfigFiles() prefixLen, err = getPrefixFromConfigFiles()
if err == nil && prefixLen != "" { if err == nil && prefixLen != "" {
if len, err := strconv.Atoi(prefixLen); err == nil && isPrefixLengthValid(len) {
return formatIPv6Mask(prefixLen, language), nil return formatIPv6Mask(prefixLen, language), nil
} }
// 如果以上方法都失败但确实有公网IPv6使用/128作为默认子网掩码 }
return formatIPv6Mask("128", language), nil return formatIPv6Mask("128", language), nil
} }

View File

@@ -7,40 +7,34 @@ import (
"fmt" "fmt"
"os/exec" "os/exec"
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
// Windows上获取前缀长度 // Windows上获取前缀长度
func getPrefixFromNetsh(interfaceName string) (string, error) { func getPrefixFromNetsh(interfaceName string) (string, error) {
// Windows上使用netsh命令获取IPv6配置
cmd := exec.Command("netsh", "interface", "ipv6", "show", "addresses") cmd := exec.Command("netsh", "interface", "ipv6", "show", "addresses")
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
return "", err return "", err
} }
// 转换输出为字符串并按行分割
lines := strings.Split(string(output), "\n") lines := strings.Split(string(output), "\n")
// 解析输出查找IPv6地址和前缀长度
currentInterface := "" currentInterface := ""
for _, line := range lines { for _, line := range lines {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
// 查找接口名称
if strings.HasSuffix(line, ":") { if strings.HasSuffix(line, ":") {
currentInterface = strings.TrimSuffix(line, ":") currentInterface = strings.TrimSuffix(line, ":")
continue continue
} }
// 只处理匹配的接口
if currentInterface == "" || !strings.Contains(strings.ToLower(currentInterface), strings.ToLower(interfaceName)) { if currentInterface == "" || !strings.Contains(strings.ToLower(currentInterface), strings.ToLower(interfaceName)) {
continue continue
} }
// 查找地址和前缀长度
if strings.Contains(line, "Address") && strings.Contains(line, "Parameters") { if strings.Contains(line, "Address") && strings.Contains(line, "Parameters") {
re := regexp.MustCompile(`([a-fA-F0-9:]+)/(\d+)`) re := regexp.MustCompile(`([a-fA-F0-9:]+)/(\d+)`)
match := re.FindStringSubmatch(line) match := re.FindStringSubmatch(line)
if len(match) >= 3 { if len(match) >= 3 {
ipv6Addr := match[1] ipv6Addr := match[1]
prefixLen := match[2] prefixLen := match[2]
// 排除非公网地址前缀
if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址
strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "::1") || // 回环地址
strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址
@@ -48,6 +42,10 @@ func getPrefixFromNetsh(interfaceName string) (string, error) {
strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 strings.HasPrefix(ipv6Addr, "ff") { // 组播地址
continue continue
} }
prefixLenInt, err := strconv.Atoi(prefixLen)
if err != nil || prefixLenInt < 0 || prefixLenInt > 128 {
continue
}
return prefixLen, nil return prefixLen, nil
} }
} }
@@ -57,7 +55,6 @@ func getPrefixFromNetsh(interfaceName string) (string, error) {
// Windows特有的PowerShell方法获取IPv6信息 // Windows特有的PowerShell方法获取IPv6信息
func getPrefixFromPowerShell(interfaceName string) (string, error) { func getPrefixFromPowerShell(interfaceName string) (string, error) {
// 使用PowerShell获取网络适配器信息
cmd := exec.Command("powershell", "-Command", cmd := exec.Command("powershell", "-Command",
"Get-NetIPAddress -AddressFamily IPv6 | Where-Object { $_.InterfaceAlias -like '*"+interfaceName+"*' -and $_.PrefixOrigin -ne 'WellKnown' } | Select-Object IPAddress, PrefixLength | ConvertTo-Json") "Get-NetIPAddress -AddressFamily IPv6 | Where-Object { $_.InterfaceAlias -like '*"+interfaceName+"*' -and $_.PrefixOrigin -ne 'WellKnown' } | Select-Object IPAddress, PrefixLength | ConvertTo-Json")
output, err := cmd.Output() output, err := cmd.Output()
@@ -74,7 +71,6 @@ func getPrefixFromPowerShell(interfaceName string) (string, error) {
} }
for i := 0; i < len(ipMatches); i++ { for i := 0; i < len(ipMatches); i++ {
ipv6Addr := ipMatches[i][1] ipv6Addr := ipMatches[i][1]
// 排除非公网地址前缀
if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址
strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "::1") || // 回环地址
strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址
@@ -82,12 +78,17 @@ func getPrefixFromPowerShell(interfaceName string) (string, error) {
strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 strings.HasPrefix(ipv6Addr, "ff") { // 组播地址
continue continue
} }
return prefixMatches[i][1], nil prefixLen := prefixMatches[i][1]
prefixLenInt, err := strconv.Atoi(prefixLen)
if err != nil || prefixLenInt < 0 || prefixLenInt > 128 {
continue
}
return prefixLen, nil
} }
return "", fmt.Errorf("未找到全局IPv6地址") return "", fmt.Errorf("未找到全局IPv6地址")
} }
// 获取 IPv6 子网掩码 - Windows 实现 // 获取 IPv6 子网掩码
func GetIPv6Mask(publicIPv6, language string) (string, error) { func GetIPv6Mask(publicIPv6, language string) (string, error) {
if publicIPv6 == "" { if publicIPv6 == "" {
return "", fmt.Errorf("无公网IPV6地址") return "", fmt.Errorf("无公网IPV6地址")