Files
basics/ipv6/ipv6.go

325 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ipv6
import (
"context"
"fmt"
"net"
"os"
"os/exec"
"regexp"
"sort"
"strings"
"syscall"
"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
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
// 跳过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 {
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
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) >= 1 {
sort.Strings(prefixLens)
return prefixLens[0], 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("获取网络接口失败: %v", err)
}
// 优先级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
}
// 优先级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 " IPv6 Mask : /128", nil
}
return " IPv6 子网掩码 : /128", nil
}