Files
basics/network/ipv6/ipv6_mask_linux.go
2025-05-17 10:16:34 +00:00

257 lines
7.3 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.

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