fix: 适配多架构和平台

This commit is contained in:
spiritlhl
2025-03-31 13:33:26 +00:00
parent babcb62e02
commit 6150948093
6 changed files with 460 additions and 89 deletions

90
ipv6/ipv6_mask_common.go Normal file
View File

@@ -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)
}

123
ipv6/ipv6_mask_darwin.go Normal file
View File

@@ -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
}

92
ipv6/ipv6_mask_freebsd.go Normal file
View File

@@ -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
}

View File

@@ -1,3 +1,6 @@
//go:build linux
// +build linux
package ipv6 package ipv6
import ( import (
@@ -13,61 +16,13 @@ import (
"time" "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前缀选项类型 // Router Advertisement前缀选项类型
const ( const (
ICMPv6RouterAdvertisement = 134 ICMPv6RouterAdvertisement = 134
ICMPv6RouterSolicitation = 133
ICMPv6OptionPrefix = 3 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报文中提取前缀长度信息 // 从RA报文中提取前缀长度信息
func extractPrefixFromRAOption(data []byte) []string { func extractPrefixFromRAOption(data []byte) []string {
var prefixLengths []string var prefixLengths []string
@@ -105,25 +60,41 @@ func extractPrefixFromRAOption(data []byte) []string {
return prefixLengths return prefixLengths
} }
// 判断是否为非全局单播地址前缀 // 发送Router Solicitation消息
func isNonGlobalPrefix(prefix [16]byte) bool { func sendRouterSolicitation(fd int, interfaceName string) error {
// 链路本地地址 fe80::/10 // 获取接口信息
if prefix[0] == 0xfe && (prefix[1]&0xc0) == 0x80 { intf, err := net.InterfaceByName(interfaceName)
return true if err != nil {
return fmt.Errorf("获取接口信息失败: %v", err)
} }
// 唯一本地地址 fc00::/7 // 构造ICMPv6 Router Solicitation消息
if (prefix[0] & 0xfe) == 0xfc { // ICMPv6头部(4字节): 类型(1字节) + 代码(1字节) + 校验和(2字节)
return true 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 // 设置ICMPv6的目标地址为All Routers组播地址
if prefix[0] == 0 && prefix[1] == 0 && prefix[15] == 1 { var allRoutersAddr [16]byte
return true 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 return nil
if prefix[0] == 0xff {
return true
}
return false
} }
// 方法1尝试原生实现从Router Advertisement获取前缀长度 // 方法1尝试原生实现从Router Advertisement获取前缀长度
@@ -181,10 +152,10 @@ func getPrefixFromRA(interfaceName string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
// 发送Router Solicitation // 发送Router Solicitation
go func() { err = sendRouterSolicitation(fd, interfaceName)
// 实际应用中可能需要构造并发送Router Solicitation消息 if err != nil {
// 这里为简化代码仅依赖网络上周期性的Router Advertisement return "", fmt.Errorf("发送Router Solicitation失败: %v", err)
}() }
// 接收Router Advertisement // 接收Router Advertisement
buffer := make([]byte, 1500) buffer := make([]byte, 1500)
for { for {
@@ -252,7 +223,7 @@ func getPrefixFromIPCommand(interfaceName string) (string, error) {
return "", fmt.Errorf("未找到有效的IPv6前缀长度") return "", fmt.Errorf("未找到有效的IPv6前缀长度")
} }
// 方法3配置文件获取前缀长度 // Linux平台专用的配置文件获取方法
func getPrefixFromConfigFiles() (string, error) { func getPrefixFromConfigFiles() (string, error) {
// 尝试从常见的网络配置文件中读取 // 尝试从常见的网络配置文件中读取
configFiles := []string{ configFiles := []string{
@@ -278,8 +249,7 @@ func getPrefixFromConfigFiles() (string, error) {
return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息") return "", fmt.Errorf("在配置文件中未找到IPv6前缀长度信息")
} }
// 获取 IPv6 子网掩码 // 获取 IPv6 子网掩码 - Linux 实现
// 获取 IPv6 子网掩码
func GetIPv6Mask(language string) (string, error) { func GetIPv6Mask(language string) (string, error) {
// 首先检查是否有公网IPv6地址 // 首先检查是否有公网IPv6地址
publicIPv6, err := getCurrentIPv6() publicIPv6, err := getCurrentIPv6()
@@ -295,30 +265,18 @@ func GetIPv6Mask(language string) (string, error) {
// 优先级1尝试从RA报文获取前缀长度 // 优先级1尝试从RA报文获取前缀长度
prefixLen, err := getPrefixFromRA(interfaceName) prefixLen, err := getPrefixFromRA(interfaceName)
if err == nil && prefixLen != "" { if err == nil && prefixLen != "" {
if language == "en" { return formatIPv6Mask(prefixLen, language), nil
return fmt.Sprintf(" IPv6 Mask : /%s", prefixLen), nil
}
return fmt.Sprintf(" IPv6 子网掩码 : /%s", prefixLen), nil
} }
// 优先级2从ip命令获取前缀长度 // 优先级2从ip命令获取前缀长度
prefixLen, err = getPrefixFromIPCommand(interfaceName) prefixLen, err = getPrefixFromIPCommand(interfaceName)
if err == nil && prefixLen != "" { if err == nil && prefixLen != "" {
if language == "en" { return formatIPv6Mask(prefixLen, language), nil
return fmt.Sprintf(" IPv6 Mask : /%s", prefixLen), nil
}
return fmt.Sprintf(" IPv6 子网掩码 : /%s", prefixLen), nil
} }
// 优先级3从配置文件获取前缀长度 // 优先级3从配置文件获取前缀长度
prefixLen, err = getPrefixFromConfigFiles() prefixLen, err = getPrefixFromConfigFiles()
if err == nil && prefixLen != "" { if err == nil && prefixLen != "" {
if language == "en" { return formatIPv6Mask(prefixLen, language), nil
return fmt.Sprintf(" IPv6 Mask : /%s", prefixLen), nil
}
return fmt.Sprintf(" IPv6 子网掩码 : /%s", prefixLen), nil
} }
// 如果以上方法都失败但确实有公网IPv6使用/128作为默认子网掩码 // 如果以上方法都失败但确实有公网IPv6使用/128作为默认子网掩码
if language == "en" { return formatIPv6Mask("128", language), nil
return " IPv6 Mask : /128", nil
}
return " IPv6 子网掩码 : /128", nil
} }

109
ipv6/ipv6_mask_windows.go Normal file
View File

@@ -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
}

View File

@@ -24,7 +24,6 @@ func FetchJsonFromURL(url, netType string, enableHeader bool, additionalHeader s
if netType != "tcp4" && netType != "tcp6" { if netType != "tcp4" && netType != "tcp6" {
return nil, fmt.Errorf("Invalid netType: %s. Expected 'tcp4' or 'tcp6'.", netType) return nil, fmt.Errorf("Invalid netType: %s. Expected 'tcp4' or 'tcp6'.", netType)
} }
// 创建 HTTP 客户端 // 创建 HTTP 客户端
client := req.C() client := req.C()
client.SetTimeout(7 * time.Second). client.SetTimeout(7 * time.Second).