From babcb62e02769857b1c5fabcb8726e4a96db30a9 Mon Sep 17 00:00:00 2001 From: spiritlhl <103393591+spiritLHLS@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:22:28 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dipv6=E5=89=8D=E7=BC=80?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=E8=8E=B7=E5=8F=96=E5=BD=B1=E5=93=8D=E5=AE=BF?= =?UTF-8?q?=E4=B8=BB=E6=9C=BA=E9=85=8D=E7=BD=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - ipv6/ipv6.go | 347 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 251 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index ad4e696..6cab613 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Include: https://github.com/oneclickvirt/gostun ## TODO -- [ ] 目前IPV4的CIDR已加入识别,IPV6的CIDR待处理加入识别 - [ ] 适配MACOS系统的识别 ## Usage diff --git a/ipv6/ipv6.go b/ipv6/ipv6.go index b28b5bb..cd12235 100644 --- a/ipv6/ipv6.go +++ b/ipv6/ipv6.go @@ -1,11 +1,15 @@ package ipv6 import ( + "context" "fmt" + "net" + "os" "os/exec" "regexp" "sort" "strings" + "syscall" "time" ) @@ -19,7 +23,7 @@ func getInterface() (string, error) { return strings.TrimSpace(string(output)), nil } -// 获取当前的 IPv6 地址 +// 获取当前的公网 IPv6 地址 func getCurrentIPv6() (string, error) { cmd := exec.Command("curl", "-s", "-6", "-m", "5", "ipv6.ip.sb") output, err := cmd.Output() @@ -29,58 +33,191 @@ func getCurrentIPv6() (string, error) { return strings.TrimSpace(string(output)), nil } -// 添加 IPv6 地址到指定接口 -func addIPv6(interfaceName, ipv6 string) error { - cmd := exec.Command("ip", "addr", "add", ipv6+"/128", "dev", interfaceName) - return cmd.Run() +// Router Advertisement前缀选项类型 +const ( + ICMPv6RouterAdvertisement = 134 + ICMPv6OptionPrefix = 3 +) + +// ICMPv6报文头部结构 +type ICMPv6Header struct { + Type uint8 + Code uint8 + Checksum uint16 } -// 删除指定接口上的 IPv6 地址 -func delIPv6(interfaceName, ipv6 string) error { - cmd := exec.Command("ip", "addr", "del", ipv6+"/128", "dev", interfaceName) - return cmd.Run() +// Router Advertisement报文结构 +type RouterAdvertisement struct { + CurHopLimit uint8 + Flags uint8 + RouterLifetime uint16 + ReachableTime uint32 + RetransTimer uint32 + Options []byte } -// ifconfig -// func getIPv6PrefixLength(interfaceName string) (string, error) { -// cmd := exec.Command("ifconfig", interfaceName) -// output, err := cmd.Output() -// if err != nil { -// return "", err -// } -// // 匹配 inet6 地址和前缀长度 -// re := regexp.MustCompile(`\s*inet6\s+([a-fA-F0-9:]+)\s+prefixlen\s+(\d+)\s*`) -// matches := re.FindAllStringSubmatch(string(output), -1) -// if len(matches) == 0 { -// return "", nil -// } -// var prefixLens []string -// 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") { // 组播地址 -// continue -// } -// // 提取 prefixlen -// if len(match) > 2 { -// prefixLens = append(prefixLens, match[2]) -// } -// } -// if len(prefixLens) >= 2 { -// sort.Strings(prefixLens) -// return prefixLens[0], nil -// } else if len(prefixLens) == 1 { -// return prefixLens[0], nil -// } -// return "", nil -// } +// 前缀信息选项结构 +type PrefixInfoOption struct { + Type uint8 + Length uint8 + PrefixLength uint8 + Flags uint8 + ValidLifetime uint32 + PreferredLifetime uint32 + Reserved uint32 + Prefix [16]byte +} -// 获取接口的子网掩码前缀 ip -func getIPv6PrefixLength(interfaceName string) (string, error) { +// 从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字节为单位 + // 确保选项长度有效 + 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) { + prefixLengths = append(prefixLengths, fmt.Sprintf("%d", prefixLen)) + } + } + } + optionStart += int(optionLen) + } + return prefixLengths +} + +// 判断是否为非全局单播地址前缀 +func isNonGlobalPrefix(prefix [16]byte) bool { + // 链路本地地址 fe80::/10 + if prefix[0] == 0xfe && (prefix[1]&0xc0) == 0x80 { + return true + } + // 唯一本地地址 fc00::/7 + if (prefix[0] & 0xfe) == 0xfc { + return true + } + // 回环地址 ::1 + if prefix[0] == 0 && prefix[1] == 0 && prefix[15] == 1 { + return true + } + // 组播地址 ff00::/8 + if prefix[0] == 0xff { + return true + } + return false +} + +// 方法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) + output, err := cmd.Output() + if err == nil { + re := regexp.MustCompile(`(?i)prefix\s+([a-fA-F0-9:]+)/(\d+)`) + matches := re.FindAllStringSubmatch(string(output), -1) + for _, match := range matches { + prefix := match[1] + prefixLen := match[2] + // 排除非公网地址前缀 + if !strings.HasPrefix(prefix, "fe80") && + !strings.HasPrefix(prefix, "::1") && + !strings.HasPrefix(prefix, "fc") && + !strings.HasPrefix(prefix, "fd") && + !strings.HasPrefix(prefix, "ff") { + return prefixLen, nil + } + } + } + } + // 如果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秒超时 + 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 + go func() { + // 实际应用中可能需要构造并发送Router Solicitation消息 + // 这里为简化代码,仅依赖网络上周期性的Router Advertisement + }() + // 接收Router Advertisement + buffer := make([]byte, 1500) + for { + select { + case <-ctx.Done(): + return "", fmt.Errorf("等待Router Advertisement超时") + default: + n, _, err := syscall.Recvfrom(fd, buffer, 0) + if err != nil { + if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK { + continue + } + 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 + } + } + } + } +} + +// 方法2:从ip命令获取前缀长度 +func getPrefixFromIPCommand(interfaceName string) (string, error) { cmd := exec.Command("ip", "-o", "-6", "addr", "show", interfaceName) output, err := cmd.Output() if err != nil { @@ -90,17 +227,17 @@ func getIPv6PrefixLength(interfaceName string) (string, error) { 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 "", nil + return "", fmt.Errorf("未找到全局IPv6地址") } var prefixLens []string 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 @@ -108,62 +245,80 @@ func getIPv6PrefixLength(interfaceName string) (string, error) { prefixLens = append(prefixLens, match[2]) } } - if len(prefixLens) >= 2 { + if len(prefixLens) >= 1 { sort.Strings(prefixLens) return prefixLens[0], nil - } else if len(prefixLens) == 1 { - return prefixLens[0], nil } - return "", nil + return "", fmt.Errorf("未找到有效的IPv6前缀长度") +} + +// 方法3:从配置文件获取前缀长度 +func getPrefixFromConfigFiles() (string, error) { + // 尝试从常见的网络配置文件中读取 + configFiles := []string{ + "/etc/network/interfaces", + "/etc/netplan/01-netcfg.yaml", + "/etc/netplan/50-cloud-init.yaml", + "/etc/sysconfig/network-scripts/ifcfg-eth0", + } + for _, file := range configFiles { + if _, err := os.Stat(file); err == nil { + content, err := os.ReadFile(file) + if err != nil { + continue + } + // 在配置文件中查找IPv6前缀长度 + re := regexp.MustCompile(`(?i)(prefix-length|prefixlen|netmask)[\s:=]+(\d+)`) + matches := re.FindStringSubmatch(string(content)) + if len(matches) >= 3 { + return matches[2], nil + } + } + } + return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息") } +// 获取 IPv6 子网掩码 // 获取 IPv6 子网掩码 func GetIPv6Mask(language string) (string, error) { + // 首先检查是否有公网IPv6地址 + publicIPv6, err := getCurrentIPv6() + if err != nil || publicIPv6 == "" { + // 没有公网IPv6,返回空字符串 + return "", nil + } // 获取网络接口 interfaceName, err := getInterface() if err != nil || interfaceName == "" { - return "", fmt.Errorf("Failed to get network interface: %v", err) + return "", fmt.Errorf("获取网络接口失败: %v", err) } - // fmt.Println(interfaceName) - // 获取当前 IPv6 地址 - currentIPv6, err := getCurrentIPv6() - if err != nil || currentIPv6 == "" { - return "", fmt.Errorf("Failed to get current IPv6 address: %v", err) - } - // 生成新的 IPv6 地址 - newIPv6 := currentIPv6[:strings.LastIndex(currentIPv6, ":")] + ":3" - // 添加新的 IPv6 地址 - if err := addIPv6(interfaceName, newIPv6); err != nil { - return "", fmt.Errorf("Failed to add IPv6 address: %v", err) - } - time.Sleep(5 * time.Second) - // 获取更新后的 IPv6 地址 - updatedIPv6, err := getCurrentIPv6() - if err != nil { - return "", fmt.Errorf("Failed to get updated IPv6 address: %v", err) - } - // 删除添加的 IPv6 地址 - if err := delIPv6(interfaceName, newIPv6); err != nil { - return "", fmt.Errorf("Failed to delete IPv6 address: %v", err) - } - time.Sleep(5 * time.Second) - // 获取子网掩码前缀长度 - ipv6Prefixlen, err := getIPv6PrefixLength(interfaceName) - if err != nil { - return "", fmt.Errorf("Failed to get IPv6 prefix length: %v", err) - } - if ipv6Prefixlen == "" { - return "", fmt.Errorf("get IPv6 prefix length is null") - } - // 输出结果 - if updatedIPv6 == currentIPv6 || updatedIPv6 == "" { + // 优先级1:尝试从RA报文获取前缀长度 + prefixLen, err := getPrefixFromRA(interfaceName) + if err == nil && prefixLen != "" { if language == "en" { - return " IPv6 Mask : /128", nil + return fmt.Sprintf(" IPv6 Mask : /%s", prefixLen), nil } - return " IPv6 子网掩码 : /128", nil + return fmt.Sprintf(" IPv6 子网掩码 : /%s", prefixLen), nil } + // 优先级2:从ip命令获取前缀长度 + prefixLen, err = getPrefixFromIPCommand(interfaceName) + if err == nil && prefixLen != "" { + if language == "en" { + return fmt.Sprintf(" IPv6 Mask : /%s", prefixLen), nil + } + return fmt.Sprintf(" IPv6 子网掩码 : /%s", prefixLen), nil + } + // 优先级3:从配置文件获取前缀长度 + prefixLen, err = getPrefixFromConfigFiles() + if err == nil && prefixLen != "" { + if language == "en" { + return fmt.Sprintf(" IPv6 Mask : /%s", prefixLen), nil + } + return fmt.Sprintf(" IPv6 子网掩码 : /%s", prefixLen), nil + } + // 如果以上方法都失败但确实有公网IPv6,使用/128作为默认子网掩码 if language == "en" { - return fmt.Sprintf(" IPv6 Mask : /%s\n", ipv6Prefixlen), nil + return " IPv6 Mask : /128", nil } - return fmt.Sprintf(" IPv6 子网掩码 : /%s\n", ipv6Prefixlen), nil + return " IPv6 子网掩码 : /128", nil }