mirror of
https://github.com/oneclickvirt/basics.git
synced 2025-10-05 16:48:09 +08:00
257 lines
7.3 KiB
Go
257 lines
7.3 KiB
Go
//go:build linux
|
||
// +build linux
|
||
|
||
package ipv6
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net"
|
||
"os"
|
||
"os/exec"
|
||
"regexp"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"syscall"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
ICMPv6RouterAdvertisement = 134
|
||
ICMPv6RouterSolicitation = 133
|
||
ICMPv6OptionPrefix = 3
|
||
)
|
||
|
||
func extractPrefixFromRAOption(data []byte) []string {
|
||
var prefixLengths []string
|
||
optionStart := 16
|
||
for optionStart < len(data) {
|
||
if optionStart+2 > len(data) {
|
||
break
|
||
}
|
||
optionType := data[optionStart]
|
||
optionLen := data[optionStart+1] * 8
|
||
if optionLen == 0 || optionStart+int(optionLen) > len(data) {
|
||
break
|
||
}
|
||
if optionType == ICMPv6OptionPrefix && optionLen >= 32 {
|
||
prefixLen := data[optionStart+2]
|
||
prefixStart := optionStart + 16
|
||
if prefixStart+16 <= len(data) {
|
||
var prefix [16]byte
|
||
copy(prefix[:], data[prefixStart:prefixStart+16])
|
||
if !isNonGlobalPrefix(prefix) && isPrefixLengthValid(int(prefixLen)) {
|
||
prefixLengths = append(prefixLengths, fmt.Sprintf("%d", prefixLen))
|
||
}
|
||
}
|
||
}
|
||
optionStart += int(optionLen)
|
||
}
|
||
return prefixLengths
|
||
}
|
||
|
||
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)
|
||
}
|
||
msg := make([]byte, 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)
|
||
}
|
||
var allRoutersAddr [16]byte
|
||
copy(allRoutersAddr[:], net.ParseIP("ff02::2").To16())
|
||
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
|
||
}
|
||
|
||
func getPrefixFromRA(interfaceName string) (string, error) {
|
||
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]
|
||
prefixLenInt, err := strconv.Atoi(prefixLen)
|
||
if err != nil || !isPrefixLengthValid(prefixLenInt) {
|
||
continue
|
||
}
|
||
if !strings.HasPrefix(prefix, "fe80") &&
|
||
!strings.HasPrefix(prefix, "::1") &&
|
||
!strings.HasPrefix(prefix, "fc") &&
|
||
!strings.HasPrefix(prefix, "fd") &&
|
||
!strings.HasPrefix(prefix, "ff") {
|
||
return prefixLen, nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
intf, err := net.InterfaceByName(interfaceName)
|
||
if err != nil {
|
||
return "", fmt.Errorf("获取网络接口失败: %v", err)
|
||
}
|
||
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)
|
||
}
|
||
if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil {
|
||
return "", fmt.Errorf("设置套接字选项失败: %v", err)
|
||
}
|
||
tv := syscall.Timeval{
|
||
Sec: 5,
|
||
Usec: 0,
|
||
}
|
||
if err := syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv); err != nil {
|
||
return "", fmt.Errorf("设置接收超时失败: %v", err)
|
||
}
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
defer cancel()
|
||
err = sendRouterSolicitation(fd, interfaceName)
|
||
if err != nil {
|
||
return "", fmt.Errorf("发送Router Solicitation失败: %v", err)
|
||
}
|
||
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)
|
||
}
|
||
if n < 4 {
|
||
continue
|
||
}
|
||
if buffer[0] == ICMPv6RouterAdvertisement {
|
||
prefixLengths := extractPrefixFromRAOption(buffer[:n])
|
||
if len(prefixLengths) > 0 {
|
||
return prefixLengths[0], nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func getPrefixFromIPCommand(interfaceName string) (string, error) {
|
||
cmd := exec.Command("ip", "-o", "-6", "addr", "show", interfaceName)
|
||
output, err := cmd.Output()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
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 []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") {
|
||
continue
|
||
}
|
||
if len(match) > 2 {
|
||
prefixLen, err := strconv.Atoi(match[2])
|
||
if err != nil || !isPrefixLengthValid(prefixLen) {
|
||
continue
|
||
}
|
||
prefixLens = append(prefixLens, prefixLen)
|
||
}
|
||
}
|
||
if len(prefixLens) >= 1 {
|
||
sort.Ints(prefixLens)
|
||
return strconv.Itoa(prefixLens[0]), nil
|
||
}
|
||
return "", fmt.Errorf("未找到有效的IPv6前缀长度")
|
||
}
|
||
|
||
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
|
||
}
|
||
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
|
||
}
|
||
}
|
||
}
|
||
return "", fmt.Errorf("在配置文件中未找到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)
|
||
}
|
||
var prefixLen string
|
||
// 优先级1:从RA报文获取前缀长度
|
||
prefixLen, err = getPrefixFromRA(interfaceName)
|
||
if err == nil && prefixLen != "" {
|
||
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 != "" {
|
||
if len, err := strconv.Atoi(prefixLen); err == nil && isPrefixLengthValid(len) {
|
||
return formatIPv6Mask(prefixLen, language), nil
|
||
}
|
||
}
|
||
// 优先级3:从配置文件获取前缀长度
|
||
prefixLen, err = getPrefixFromConfigFiles()
|
||
if err == nil && prefixLen != "" {
|
||
if len, err := strconv.Atoi(prefixLen); err == nil && isPrefixLengthValid(len) {
|
||
return formatIPv6Mask(prefixLen, language), nil
|
||
}
|
||
}
|
||
return formatIPv6Mask("128", language), nil
|
||
}
|