diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e0896b4..d93fcd1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: run: | git config --global user.name 'github-actions' 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 push origin $TAG echo "TAG=$TAG" >> $GITHUB_ENV diff --git a/model/model.go b/model/model.go index 6cdb754..6e5f5a3 100644 --- a/model/model.go +++ b/model/model.go @@ -1,6 +1,6 @@ package model -const BasicsVersion = "v0.0.11" +const BasicsVersion = "v0.0.12" var EnableLoger bool diff --git a/network/ipv6/ipv6_mask_darwin.go b/network/ipv6/ipv6_mask_darwin.go index 017fb19..f5d57f5 100644 --- a/network/ipv6/ipv6_mask_darwin.go +++ b/network/ipv6/ipv6_mask_darwin.go @@ -7,6 +7,7 @@ import ( "fmt" "os/exec" "regexp" + "strconv" "strings" ) @@ -17,20 +18,14 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) { if err != nil { return "", err } - - // 匹配 inet6 地址和前缀长度 re := regexp.MustCompile(`inet6\s+([a-fA-F0-9:]+)%?\w*\s+prefixlen\s+(\d+)`) matches := re.FindAllStringSubmatch(string(output), -1) - for _, match := range matches { if len(match) < 3 { continue } - ipv6Addr := match[1] prefixLen := match[2] - - // 排除非公网地址前缀 if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 @@ -38,29 +33,26 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) { strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 continue } - + prefixLenInt, err := strconv.Atoi(prefixLen) + if err != nil || prefixLenInt < 0 || prefixLenInt > 128 { + continue + } return prefixLen, nil } - return "", fmt.Errorf("未找到全局IPv6地址") } // macOS平台上使用networksetup命令获取更多信息 func getPrefixFromNetworksetup(interfaceName string) (string, error) { - // 首先需要获取网络服务名称 cmd := exec.Command("networksetup", "-listallhardwareports") output, err := cmd.Output() if err != nil { return "", err } - - // 解析输出查找对应的网络服务名称 lines := strings.Split(string(output), "\n") var serviceName string - for i := 0; i < len(lines); i++ { if strings.Contains(lines[i], "Device: "+interfaceName) && i > 0 { - // 服务名称在上一行 serviceNameLine := lines[i-1] if strings.HasPrefix(serviceNameLine, "Hardware Port: ") { serviceName = strings.TrimPrefix(serviceNameLine, "Hardware Port: ") @@ -68,35 +60,32 @@ func getPrefixFromNetworksetup(interfaceName string) (string, error) { } } } - if serviceName == "" { return "", fmt.Errorf("未找到网络接口对应的服务名称") } - - // 使用服务名称获取IPv6配置 cmd = exec.Command("networksetup", "-getinfo", serviceName) output, err = cmd.Output() if err != nil { 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+)`) match := re.FindStringSubmatch(string(output)) - 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前缀长度信息") } -// 获取 IPv6 子网掩码 - macOS 实现 +// 获取 IPv6 子网掩码 func GetIPv6Mask(publicIPv6, language string) (string, error) { if publicIPv6 == "" { return "", fmt.Errorf("无公网IPV6地址") } - // 获取网络接口 interfaceName, err := getInterface() if err != nil || interfaceName == "" { return "", fmt.Errorf("获取网络接口失败: %v", err) @@ -111,6 +100,5 @@ func GetIPv6Mask(publicIPv6, language string) (string, error) { if err == nil && prefixLen != "" { return formatIPv6Mask(prefixLen, language), nil } - // 如果以上方法都失败但确实有公网IPv6,使用/128作为默认子网掩码 return formatIPv6Mask("128", language), nil } diff --git a/network/ipv6/ipv6_mask_freebsd.go b/network/ipv6/ipv6_mask_freebsd.go index 65d2495..6590202 100644 --- a/network/ipv6/ipv6_mask_freebsd.go +++ b/network/ipv6/ipv6_mask_freebsd.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "regexp" + "strconv" "strings" ) @@ -18,7 +19,6 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) { if err != nil { return "", err } - // 匹配 inet6 地址和前缀长度 re := regexp.MustCompile(`inet6\s+([a-fA-F0-9:]+)%?\w*\s+prefixlen\s+(\d+)`) matches := re.FindAllStringSubmatch(string(output), -1) for _, match := range matches { @@ -27,7 +27,6 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) { } ipv6Addr := match[1] prefixLen := match[2] - // 排除非公网地址前缀 if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 @@ -35,6 +34,11 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) { strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 continue } + prefixLenInt, err := strconv.Atoi(prefixLen) + if err != nil || prefixLenInt < 0 || prefixLenInt > 128 { + continue + } + return prefixLen, nil } return "", fmt.Errorf("未找到全局IPv6地址") @@ -42,7 +46,6 @@ func getPrefixFromIfconfig(interfaceName string) (string, error) { // FreeBSD平台专用的配置文件获取方法 func getPrefixFromConfigFiles() (string, error) { - // FreeBSD 的网络配置文件 configFiles := []string{ "/etc/rc.conf", "/etc/rc.conf.local", @@ -53,23 +56,26 @@ func getPrefixFromConfigFiles() (string, error) { if err != nil { continue } - // 在配置文件中查找IPv6前缀长度 re := regexp.MustCompile(`(?i)ipv6_prefix(len)?="?(\d+)"?`) matches := re.FindStringSubmatch(string(content)) 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前缀长度信息") } -// 获取 IPv6 子网掩码 - FreeBSD 实现 +// 获取 IPv6 子网掩码 func GetIPv6Mask(publicIPv6, language string) (string, error) { if publicIPv6 == "" { return "", fmt.Errorf("无公网IPV6地址") } - // 获取网络接口 interfaceName, err := getInterface() if err != nil || interfaceName == "" { return "", fmt.Errorf("获取网络接口失败: %v", err) @@ -84,6 +90,5 @@ func GetIPv6Mask(publicIPv6, language string) (string, error) { if err == nil && prefixLen != "" { return formatIPv6Mask(prefixLen, language), nil } - // 如果以上方法都失败但确实有公网IPv6,使用/128作为默认子网掩码 return formatIPv6Mask("128", language), nil } diff --git a/network/ipv6/ipv6_mask_linux.go b/network/ipv6/ipv6_mask_linux.go index 0c93bf1..44873b1 100644 --- a/network/ipv6/ipv6_mask_linux.go +++ b/network/ipv6/ipv6_mask_linux.go @@ -11,46 +11,37 @@ import ( "os/exec" "regexp" "sort" + "strconv" "strings" "syscall" "time" ) -// Router Advertisement前缀选项类型 const ( ICMPv6RouterAdvertisement = 134 ICMPv6RouterSolicitation = 133 ICMPv6OptionPrefix = 3 ) -// 从RA报文中提取前缀长度信息 func extractPrefixFromRAOption(data []byte) []string { var prefixLengths []string - // 跳过ICMPv6头部(4字节)和Router Advertisement基本信息(12字节) optionStart := 16 - // 遍历所有选项 for optionStart < len(data) { - // 确保有足够的数据可读 if optionStart+2 > len(data) { break } optionType := data[optionStart] - optionLen := data[optionStart+1] * 8 // 长度以8字节为单位 - // 确保选项长度有效 + optionLen := data[optionStart+1] * 8 if optionLen == 0 || optionStart+int(optionLen) > len(data) { break } - // 解析前缀信息选项 if optionType == ICMPv6OptionPrefix && optionLen >= 32 { - // 前缀长度在选项开始后2字节处 prefixLen := data[optionStart+2] - // 前缀值从选项开始后16字节处 prefixStart := optionStart + 16 if prefixStart+16 <= len(data) { var prefix [16]byte copy(prefix[:], data[prefixStart:prefixStart+16]) - // 排除非全局单播地址 - if !isNonGlobalPrefix(prefix) { + if !isNonGlobalPrefix(prefix) && isPrefixLengthValid(int(prefixLen)) { prefixLengths = append(prefixLengths, fmt.Sprintf("%d", prefixLen)) } } @@ -60,46 +51,36 @@ func extractPrefixFromRAOption(data []byte) []string { return prefixLengths } -// 发送Router Solicitation消息 +func isPrefixLengthValid(prefixLen int) bool { + return prefixLen >= 1 && prefixLen <= 128 +} + func sendRouterSolicitation(fd int, interfaceName string) error { - // 获取接口信息 intf, err := net.InterfaceByName(interfaceName) if err != nil { return fmt.Errorf("获取接口信息失败: %v", err) } - // 构造ICMPv6 Router Solicitation消息 - // ICMPv6头部(4字节): 类型(1字节) + 代码(1字节) + 校验和(2字节) msg := make([]byte, 8) - msg[0] = ICMPv6RouterSolicitation // 类型:Router Solicitation - msg[1] = 0 // 代码:0 - // msg[2]和msg[3]是校验和字段,暂时为0,稍后计算 - // 可选:添加Source Link-Layer Address选项(MAC地址) - // 这对一些路由器来说可能是必要的 - if len(intf.HardwareAddr) == 6 { // 确保MAC地址有效 - // 添加源链路层地址选项 - // 选项类型(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字节对齐 + msg[0] = ICMPv6RouterSolicitation + msg[1] = 0 + if len(intf.HardwareAddr) == 6 { + msg = append(msg, 1) + msg = append(msg, 1) + msg = append(msg, intf.HardwareAddr...) + msg = append(msg, 0, 0) } - // 设置ICMPv6的目标地址为All Routers组播地址 var allRoutersAddr [16]byte copy(allRoutersAddr[:], net.ParseIP("ff02::2").To16()) - // 构造sockaddr_in6结构 var addr syscall.SockaddrInet6 addr.ZoneId = uint32(intf.Index) copy(addr.Addr[:], allRoutersAddr[:]) - // 发送数据包 if err := syscall.Sendto(fd, msg, 0, &addr); err != nil { return fmt.Errorf("发送Router Solicitation失败: %v", err) } return nil } -// 方法1:尝试原生实现从Router Advertisement获取前缀长度 func getPrefixFromRA(interfaceName string) (string, error) { - // 尝试先用radvdump,如果存在的话 radvdumpPath, err := exec.LookPath("radvdump") if err == nil && radvdumpPath != "" { cmd := exec.Command("radvdump", "-i", interfaceName) @@ -110,7 +91,10 @@ func getPrefixFromRA(interfaceName string) (string, error) { for _, match := range matches { prefix := match[1] prefixLen := match[2] - // 排除非公网地址前缀 + prefixLenInt, err := strconv.Atoi(prefixLen) + if err != nil || !isPrefixLengthValid(prefixLenInt) { + continue + } if !strings.HasPrefix(prefix, "fe80") && !strings.HasPrefix(prefix, "::1") && !strings.HasPrefix(prefix, "fc") && @@ -121,42 +105,34 @@ func getPrefixFromRA(interfaceName string) (string, error) { } } } - // 如果radvdump不可用或未找到有效前缀,使用原生实现 intf, err := net.InterfaceByName(interfaceName) if err != nil { return "", fmt.Errorf("获取网络接口失败: %v", err) } - // 创建原始套接字接收ICMPv6消息 fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_ICMPV6) if err != nil { return "", fmt.Errorf("创建原始套接字失败: %v", err) } defer syscall.Close(fd) - // 绑定到指定接口 if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, intf.Index); err != nil { return "", fmt.Errorf("绑定套接字到接口失败: %v", err) } - // 设置过滤器,只接收Router Advertisement消息 if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil { return "", fmt.Errorf("设置套接字选项失败: %v", err) } - // 设置接收超时 tv := syscall.Timeval{ - Sec: 5, // 5秒超时 + Sec: 5, Usec: 0, } if err := syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv); err != nil { return "", fmt.Errorf("设置接收超时失败: %v", err) } - // 创建上下文,用于发送Router Solicitation并等待Router Advertisement ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - // 发送Router Solicitation err = sendRouterSolicitation(fd, interfaceName) if err != nil { return "", fmt.Errorf("发送Router Solicitation失败: %v", err) } - // 接收Router Advertisement buffer := make([]byte, 1500) for { select { @@ -170,16 +146,12 @@ func getPrefixFromRA(interfaceName string) (string, error) { } return "", fmt.Errorf("接收数据失败: %v", err) } - // 确保收到的是ICMPv6消息 if n < 4 { continue } - // 检查是否为Router Advertisement if buffer[0] == ICMPv6RouterAdvertisement { - // 解析数据包提取前缀长度 prefixLengths := extractPrefixFromRAOption(buffer[:n]) if len(prefixLengths) > 0 { - // 返回第一个有效前缀长度 return prefixLengths[0], nil } } @@ -187,45 +159,43 @@ func getPrefixFromRA(interfaceName string) (string, error) { } } -// 方法2:从ip命令获取前缀长度 func getPrefixFromIPCommand(interfaceName string) (string, error) { cmd := exec.Command("ip", "-o", "-6", "addr", "show", interfaceName) output, err := cmd.Output() if err != nil { return "", err } - // 匹配 inet6 地址和前缀长度 re := regexp.MustCompile(`\s*inet6\s+([a-fA-F0-9:]+)/(\d+)\s+scope\s+global`) matches := re.FindAllStringSubmatch(string(output), -1) if len(matches) == 0 { return "", fmt.Errorf("未找到全局IPv6地址") } - var prefixLens []string + var prefixLens []int for _, match := range matches { ipv6Addr := match[1] - // 排除非公网地址前缀 - if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 - strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 - strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 - strings.HasPrefix(ipv6Addr, "fd") || // 本地唯一地址 - strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 + if strings.HasPrefix(ipv6Addr, "fe80") || + strings.HasPrefix(ipv6Addr, "::1") || + strings.HasPrefix(ipv6Addr, "fc") || + strings.HasPrefix(ipv6Addr, "fd") || + strings.HasPrefix(ipv6Addr, "ff") { continue } - // 提取 prefixlen 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 { - sort.Strings(prefixLens) - return prefixLens[0], nil + sort.Ints(prefixLens) + return strconv.Itoa(prefixLens[0]), nil } return "", fmt.Errorf("未找到有效的IPv6前缀长度") } -// Linux平台专用的配置文件获取方法 func getPrefixFromConfigFiles() (string, error) { - // 尝试从常见的网络配置文件中读取 configFiles := []string{ "/etc/network/interfaces", "/etc/netplan/01-netcfg.yaml", @@ -238,10 +208,13 @@ func getPrefixFromConfigFiles() (string, error) { if err != nil { continue } - // 在配置文件中查找IPv6前缀长度 re := regexp.MustCompile(`(?i)(prefix-length|prefixlen|netmask)[\s:=]+(\d+)`) matches := re.FindStringSubmatch(string(content)) if len(matches) >= 3 { + prefixLen, err := strconv.Atoi(matches[2]) + if err != nil || !isPrefixLengthValid(prefixLen) { + continue + } return matches[2], nil } } @@ -249,31 +222,35 @@ func getPrefixFromConfigFiles() (string, error) { return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息") } -// 获取 IPv6 子网掩码 - Linux 实现 func GetIPv6Mask(publicIPv6, language string) (string, error) { if publicIPv6 == "" { return "", fmt.Errorf("无公网IPV6地址") } - // 获取网络接口 interfaceName, err := getInterface() if err != nil || interfaceName == "" { return "", fmt.Errorf("获取网络接口失败: %v", err) } - // 优先级1:尝试从RA报文获取前缀长度 - prefixLen, err := getPrefixFromRA(interfaceName) + var prefixLen string + // 优先级1:从RA报文获取前缀长度 + prefixLen, err = getPrefixFromRA(interfaceName) if err == nil && prefixLen != "" { - return formatIPv6Mask(prefixLen, language), nil + if len, err := strconv.Atoi(prefixLen); err == nil && isPrefixLengthValid(len) { + return formatIPv6Mask(prefixLen, language), nil + } } // 优先级2:从ip命令获取前缀长度 prefixLen, err = getPrefixFromIPCommand(interfaceName) if err == nil && prefixLen != "" { - return formatIPv6Mask(prefixLen, language), nil + if len, err := strconv.Atoi(prefixLen); err == nil && isPrefixLengthValid(len) { + return formatIPv6Mask(prefixLen, language), nil + } } // 优先级3:从配置文件获取前缀长度 prefixLen, err = getPrefixFromConfigFiles() if err == nil && prefixLen != "" { - return formatIPv6Mask(prefixLen, language), nil + if len, err := strconv.Atoi(prefixLen); err == nil && isPrefixLengthValid(len) { + return formatIPv6Mask(prefixLen, language), nil + } } - // 如果以上方法都失败但确实有公网IPv6,使用/128作为默认子网掩码 return formatIPv6Mask("128", language), nil } diff --git a/network/ipv6/ipv6_mask_windows.go b/network/ipv6/ipv6_mask_windows.go index d9fe490..da5197d 100644 --- a/network/ipv6/ipv6_mask_windows.go +++ b/network/ipv6/ipv6_mask_windows.go @@ -7,40 +7,34 @@ import ( "fmt" "os/exec" "regexp" + "strconv" "strings" ) // Windows上获取前缀长度 func getPrefixFromNetsh(interfaceName string) (string, error) { - // Windows上使用netsh命令获取IPv6配置 cmd := exec.Command("netsh", "interface", "ipv6", "show", "addresses") output, err := cmd.Output() if err != nil { return "", err } - // 转换输出为字符串并按行分割 lines := strings.Split(string(output), "\n") - // 解析输出查找IPv6地址和前缀长度 currentInterface := "" for _, line := range lines { line = strings.TrimSpace(line) - // 查找接口名称 if strings.HasSuffix(line, ":") { currentInterface = strings.TrimSuffix(line, ":") continue } - // 只处理匹配的接口 if currentInterface == "" || !strings.Contains(strings.ToLower(currentInterface), strings.ToLower(interfaceName)) { continue } - // 查找地址和前缀长度 if strings.Contains(line, "Address") && strings.Contains(line, "Parameters") { re := regexp.MustCompile(`([a-fA-F0-9:]+)/(\d+)`) match := re.FindStringSubmatch(line) if len(match) >= 3 { ipv6Addr := match[1] prefixLen := match[2] - // 排除非公网地址前缀 if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 @@ -48,6 +42,10 @@ func getPrefixFromNetsh(interfaceName string) (string, error) { strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 continue } + prefixLenInt, err := strconv.Atoi(prefixLen) + if err != nil || prefixLenInt < 0 || prefixLenInt > 128 { + continue + } return prefixLen, nil } } @@ -57,7 +55,6 @@ func getPrefixFromNetsh(interfaceName string) (string, error) { // Windows特有的PowerShell方法获取IPv6信息 func getPrefixFromPowerShell(interfaceName string) (string, error) { - // 使用PowerShell获取网络适配器信息 cmd := exec.Command("powershell", "-Command", "Get-NetIPAddress -AddressFamily IPv6 | Where-Object { $_.InterfaceAlias -like '*"+interfaceName+"*' -and $_.PrefixOrigin -ne 'WellKnown' } | Select-Object IPAddress, PrefixLength | ConvertTo-Json") output, err := cmd.Output() @@ -74,7 +71,6 @@ func getPrefixFromPowerShell(interfaceName string) (string, error) { } for i := 0; i < len(ipMatches); i++ { ipv6Addr := ipMatches[i][1] - // 排除非公网地址前缀 if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 @@ -82,12 +78,17 @@ func getPrefixFromPowerShell(interfaceName string) (string, error) { strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 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地址") } -// 获取 IPv6 子网掩码 - Windows 实现 +// 获取 IPv6 子网掩码 func GetIPv6Mask(publicIPv6, language string) (string, error) { if publicIPv6 == "" { return "", fmt.Errorf("无公网IPV6地址")