diff --git a/ipv6/ipv6_mask_common.go b/ipv6/ipv6_mask_common.go new file mode 100644 index 0000000..a4808c8 --- /dev/null +++ b/ipv6/ipv6_mask_common.go @@ -0,0 +1,90 @@ +package ipv6 + +import ( + "fmt" + "net" + "strings" + "time" + + "github.com/imroc/req/v3" +) + +// 获取第一个以 eth 或 en 开头的网络接口 +func getInterface() (string, error) { + interfaces, err := net.Interfaces() + if err != nil { + return "", err + } + // 优先查找以 eth 或 en 开头的接口 + for _, iface := range interfaces { + if strings.HasPrefix(iface.Name, "eth") || strings.HasPrefix(iface.Name, "en") { + return iface.Name, nil + } + } + // 如果没有找到,返回第一个非回环且启用的接口 + for _, iface := range interfaces { + if iface.Flags&net.FlagLoopback == 0 && iface.Flags&net.FlagUp != 0 { + return iface.Name, nil + } + } + return "", fmt.Errorf("未找到合适的网络接口") +} + +// 获取当前的公网 IPv6 地址 +func getCurrentIPv6() (string, error) { + // 创建一个新的req客户端,设置超时 + client := req.C().SetTimeout(5 * time.Second) + client.SetTimeout(6 * time.Second) + // 尝试多个 IPv6 检测服务 + urls := []string{ + "https://ipv6.ip.sb", + "https://api6.ipify.org", + "https://v6.ident.me", + } + var lastErr error + for _, url := range urls { + resp, err := client.R().Get(url) + if err != nil { + lastErr = err + continue + } + body := resp.String() + ipv6 := strings.TrimSpace(body) + if net.ParseIP(ipv6) != nil && strings.Contains(ipv6, ":") { + return ipv6, nil + } + } + if lastErr != nil { + return "", lastErr + } + return "", fmt.Errorf("无法获取公网IPv6地址") +} + +// 判断是否为非全局单播地址前缀 +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 +} + +// 格式化返回IPv6子网掩码 +func formatIPv6Mask(prefixLen string, language string) string { + if language == "en" { + return fmt.Sprintf(" IPv6 Mask : /%s", prefixLen) + } + return fmt.Sprintf(" IPv6 子网掩码 : /%s", prefixLen) +} diff --git a/ipv6/ipv6_mask_darwin.go b/ipv6/ipv6_mask_darwin.go new file mode 100644 index 0000000..7e1f793 --- /dev/null +++ b/ipv6/ipv6_mask_darwin.go @@ -0,0 +1,123 @@ +//go:build darwin +// +build darwin + +package ipv6 + +import ( + "fmt" + "os/exec" + "regexp" + "strings" +) + +// macOS上获取前缀长度 +func getPrefixFromIfconfig(interfaceName string) (string, error) { + cmd := exec.Command("ifconfig", interfaceName) + output, err := cmd.Output() + 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") || // 本地唯一地址 + strings.HasPrefix(ipv6Addr, "fd") || // 本地唯一地址 + strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 + 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: ") + break + } + } + } + + 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 + } + + return "", fmt.Errorf("未找到IPv6前缀长度信息") +} + +// 获取 IPv6 子网掩码 - macOS 实现 +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("获取网络接口失败: %v", err) + } + + // 方法1:从ifconfig获取前缀长度 + prefixLen, err := getPrefixFromIfconfig(interfaceName) + if err == nil && prefixLen != "" { + return formatIPv6Mask(prefixLen, language), nil + } + + // 方法2:从networksetup获取前缀长度 + prefixLen, err = getPrefixFromNetworksetup(interfaceName) + if err == nil && prefixLen != "" { + return formatIPv6Mask(prefixLen, language), nil + } + + // 如果以上方法都失败但确实有公网IPv6,使用/128作为默认子网掩码 + return formatIPv6Mask("128", language), nil +} diff --git a/ipv6/ipv6_mask_freebsd.go b/ipv6/ipv6_mask_freebsd.go new file mode 100644 index 0000000..2068e6c --- /dev/null +++ b/ipv6/ipv6_mask_freebsd.go @@ -0,0 +1,92 @@ +//go:build freebsd +// +build freebsd + +package ipv6 + +import ( + "fmt" + "os" + "os/exec" + "regexp" + "strings" +) + +// FreeBSD 上获取前缀长度 +func getPrefixFromIfconfig(interfaceName string) (string, error) { + cmd := exec.Command("ifconfig", interfaceName) + output, err := cmd.Output() + 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") || // 本地唯一地址 + strings.HasPrefix(ipv6Addr, "fd") || // 本地唯一地址 + strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 + continue + } + return prefixLen, nil + } + return "", fmt.Errorf("未找到全局IPv6地址") +} + +// FreeBSD平台专用的配置文件获取方法 +func getPrefixFromConfigFiles() (string, error) { + // FreeBSD 的网络配置文件 + configFiles := []string{ + "/etc/rc.conf", + "/etc/rc.conf.local", + } + 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)ipv6_prefix(len)?="?(\d+)"?`) + matches := re.FindStringSubmatch(string(content)) + if len(matches) >= 3 { + return matches[2], nil + } + } + } + return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息") +} + +// 获取 IPv6 子网掩码 - FreeBSD 实现 +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("获取网络接口失败: %v", err) + } + // 方法1:从ifconfig获取前缀长度 + prefixLen, err := getPrefixFromIfconfig(interfaceName) + if err == nil && prefixLen != "" { + return formatIPv6Mask(prefixLen, language), nil + } + // 方法2:从配置文件获取前缀长度 + prefixLen, err = getPrefixFromConfigFiles() + if err == nil && prefixLen != "" { + return formatIPv6Mask(prefixLen, language), nil + } + // 如果以上方法都失败但确实有公网IPv6,使用/128作为默认子网掩码 + return formatIPv6Mask("128", language), nil +} diff --git a/ipv6/ipv6.go b/ipv6/ipv6_mask_linux.go similarity index 74% rename from ipv6/ipv6.go rename to ipv6/ipv6_mask_linux.go index cd12235..ddc5291 100644 --- a/ipv6/ipv6.go +++ b/ipv6/ipv6_mask_linux.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package ipv6 import ( @@ -13,61 +16,13 @@ import ( "time" ) -// 获取第一个以 eth 或 en 开头的网络接口 -func getInterface() (string, error) { - cmd := exec.Command("sh", "-c", "ls /sys/class/net/ | grep -E '^(eth|en)' | head -n 1") - output, err := cmd.Output() - if err != nil { - return "", err - } - return strings.TrimSpace(string(output)), nil -} - -// 获取当前的公网 IPv6 地址 -func getCurrentIPv6() (string, error) { - cmd := exec.Command("curl", "-s", "-6", "-m", "5", "ipv6.ip.sb") - output, err := cmd.Output() - if err != nil { - return "", err - } - return strings.TrimSpace(string(output)), nil -} - // Router Advertisement前缀选项类型 const ( ICMPv6RouterAdvertisement = 134 + ICMPv6RouterSolicitation = 133 ICMPv6OptionPrefix = 3 ) -// ICMPv6报文头部结构 -type ICMPv6Header struct { - Type uint8 - Code uint8 - Checksum uint16 -} - -// Router Advertisement报文结构 -type RouterAdvertisement struct { - CurHopLimit uint8 - Flags uint8 - RouterLifetime uint16 - ReachableTime uint32 - RetransTimer uint32 - Options []byte -} - -// 前缀信息选项结构 -type PrefixInfoOption struct { - Type uint8 - Length uint8 - PrefixLength uint8 - Flags uint8 - ValidLifetime uint32 - PreferredLifetime uint32 - Reserved uint32 - Prefix [16]byte -} - // 从RA报文中提取前缀长度信息 func extractPrefixFromRAOption(data []byte) []string { var prefixLengths []string @@ -105,25 +60,41 @@ func extractPrefixFromRAOption(data []byte) []string { return prefixLengths } -// 判断是否为非全局单播地址前缀 -func isNonGlobalPrefix(prefix [16]byte) bool { - // 链路本地地址 fe80::/10 - if prefix[0] == 0xfe && (prefix[1]&0xc0) == 0x80 { - return true +// 发送Router Solicitation消息 +func sendRouterSolicitation(fd int, interfaceName string) error { + // 获取接口信息 + intf, err := net.InterfaceByName(interfaceName) + if err != nil { + return fmt.Errorf("获取接口信息失败: %v", err) } - // 唯一本地地址 fc00::/7 - if (prefix[0] & 0xfe) == 0xfc { - return true + // 构造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字节对齐 } - // 回环地址 ::1 - if prefix[0] == 0 && prefix[1] == 0 && prefix[15] == 1 { - return true + // 设置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) } - // 组播地址 ff00::/8 - if prefix[0] == 0xff { - return true - } - return false + return nil } // 方法1:尝试原生实现从Router Advertisement获取前缀长度 @@ -181,10 +152,10 @@ func getPrefixFromRA(interfaceName string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 发送Router Solicitation - go func() { - // 实际应用中可能需要构造并发送Router Solicitation消息 - // 这里为简化代码,仅依赖网络上周期性的Router Advertisement - }() + err = sendRouterSolicitation(fd, interfaceName) + if err != nil { + return "", fmt.Errorf("发送Router Solicitation失败: %v", err) + } // 接收Router Advertisement buffer := make([]byte, 1500) for { @@ -252,7 +223,7 @@ func getPrefixFromIPCommand(interfaceName string) (string, error) { return "", fmt.Errorf("未找到有效的IPv6前缀长度") } -// 方法3:从配置文件获取前缀长度 +// Linux平台专用的配置文件获取方法 func getPrefixFromConfigFiles() (string, error) { // 尝试从常见的网络配置文件中读取 configFiles := []string{ @@ -278,8 +249,7 @@ func getPrefixFromConfigFiles() (string, error) { return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息") } -// 获取 IPv6 子网掩码 -// 获取 IPv6 子网掩码 +// 获取 IPv6 子网掩码 - Linux 实现 func GetIPv6Mask(language string) (string, error) { // 首先检查是否有公网IPv6地址 publicIPv6, err := getCurrentIPv6() @@ -295,30 +265,18 @@ func GetIPv6Mask(language string) (string, error) { // 优先级1:尝试从RA报文获取前缀长度 prefixLen, err := getPrefixFromRA(interfaceName) if err == nil && prefixLen != "" { - if language == "en" { - return fmt.Sprintf(" IPv6 Mask : /%s", prefixLen), nil - } - return fmt.Sprintf(" IPv6 子网掩码 : /%s", prefixLen), nil + return formatIPv6Mask(prefixLen, language), 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 + return formatIPv6Mask(prefixLen, language), 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 + return formatIPv6Mask(prefixLen, language), nil } // 如果以上方法都失败但确实有公网IPv6,使用/128作为默认子网掩码 - if language == "en" { - return " IPv6 Mask : /128", nil - } - return " IPv6 子网掩码 : /128", nil + return formatIPv6Mask("128", language), nil } diff --git a/ipv6/ipv6_mask_windows.go b/ipv6/ipv6_mask_windows.go new file mode 100644 index 0000000..72fa5bf --- /dev/null +++ b/ipv6/ipv6_mask_windows.go @@ -0,0 +1,109 @@ +//go:build windows +// +build windows + +package ipv6 + +import ( + "fmt" + "os/exec" + "regexp" + "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") || // 本地唯一地址 + strings.HasPrefix(ipv6Addr, "fd") || // 本地唯一地址 + strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 + continue + } + return prefixLen, nil + } + } + } + return "", fmt.Errorf("未找到全局IPv6地址") +} + +// 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() + if err != nil { + return "", err + } + jsonStr := string(output) + rePrefixLength := regexp.MustCompile(`"PrefixLength"\s*:\s*(\d+)`) + reIPAddress := regexp.MustCompile(`"IPAddress"\s*:\s*"([^"]+)"`) + prefixMatches := rePrefixLength.FindAllStringSubmatch(jsonStr, -1) + ipMatches := reIPAddress.FindAllStringSubmatch(jsonStr, -1) + if len(prefixMatches) != len(ipMatches) { + return "", fmt.Errorf("解析PowerShell输出失败") + } + for i := 0; i < len(ipMatches); i++ { + ipv6Addr := ipMatches[i][1] + // 排除非公网地址前缀 + if strings.HasPrefix(ipv6Addr, "fe80") || // 链路本地地址 + strings.HasPrefix(ipv6Addr, "::1") || // 回环地址 + strings.HasPrefix(ipv6Addr, "fc") || // 本地唯一地址 + strings.HasPrefix(ipv6Addr, "fd") || // 本地唯一地址 + strings.HasPrefix(ipv6Addr, "ff") { // 组播地址 + continue + } + return prefixMatches[i][1], nil + } + return "", fmt.Errorf("未找到全局IPv6地址") +} + +// 获取 IPv6 子网掩码 - Windows 实现 +func GetIPv6Mask(language string) (string, error) { + publicIPv6, err := getCurrentIPv6() + if err != nil || publicIPv6 == "" { + return "", nil + } + interfaceName, err := getInterface() + if err != nil || interfaceName == "" { + return "", fmt.Errorf("获取网络接口失败: %v", err) + } + prefixLen, err := getPrefixFromNetsh(interfaceName) + if err == nil && prefixLen != "" { + return formatIPv6Mask(prefixLen, language), nil + } + prefixLen, err = getPrefixFromPowerShell(interfaceName) + if err == nil && prefixLen != "" { + return formatIPv6Mask(prefixLen, language), nil + } + return formatIPv6Mask("128", language), nil +} diff --git a/network/utils/utils.go b/network/utils/utils.go index 6eb8362..90f3615 100644 --- a/network/utils/utils.go +++ b/network/utils/utils.go @@ -24,7 +24,6 @@ func FetchJsonFromURL(url, netType string, enableHeader bool, additionalHeader s if netType != "tcp4" && netType != "tcp6" { return nil, fmt.Errorf("Invalid netType: %s. Expected 'tcp4' or 'tcp6'.", netType) } - // 创建 HTTP 客户端 client := req.C() client.SetTimeout(7 * time.Second).