Last active
December 29, 2023 06:20
-
-
Save yin1999/cfa50d5b9342380f003631a2ac683ea3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"net" | |
"os" | |
"syscall" | |
"time" | |
"github.com/jsimonetti/rtnetlink" | |
"github.com/mdlayher/netlink" | |
"golang.org/x/sys/unix" | |
) | |
func main() { | |
if len(os.Args) != 2 { | |
printError("Usage: %s <interface>\n", os.Args[0]) | |
} | |
ifaceName := os.Args[1] | |
// remove the old ipv6 addresses before monitoring | |
ifaceIdx, err := getInterfaceIndex(ifaceName) | |
if err != nil { | |
printError("%v\n", err) | |
} | |
if err = deleteOldIpv6Addr(ifaceIdx); err != nil { | |
printError("%v\n", err) | |
} | |
conn, err := rtnetlink.Dial(&netlink.Config{ | |
Groups: unix.RTMGRP_IPV6_IFADDR, | |
}) | |
if err != nil { | |
printError("%v\n", err) | |
} | |
defer conn.Close() | |
for { | |
// monitor ipv6 address changes | |
_, msgs, err := conn.Receive() | |
if err != nil { | |
printError("%v\n", err) | |
} | |
ifaceIdx, err = getInterfaceIndex(ifaceName) | |
if err != nil { | |
printError("%v\n", err) | |
} | |
modified := false | |
for _, msg := range msgs { | |
// check if the message is new address | |
if msg.Header.Type != syscall.RTM_NEWADDR { | |
continue | |
} | |
// decode the message | |
var addr rtnetlink.AddressMessage | |
if err := addr.UnmarshalBinary(msg.Data); err != nil { | |
printError("%v\n", err) | |
} | |
if checkAddress(addr, ifaceIdx, true) { | |
modified = true | |
break | |
} | |
} | |
if modified { | |
// delete the old addresses | |
retry := 1 | |
for retry <= 3 { | |
if err = deleteOldIpv6Addr(ifaceIdx); err != nil { | |
fmt.Fprintf(os.Stderr, "retry %d: %v\n", retry, err) | |
retry++ | |
// sleep 1 second before retry | |
// this is to avoid the case that the address is not yet ready | |
// when we try to delete it | |
time.Sleep(time.Second) | |
} else { | |
break | |
} | |
} | |
if err != nil { | |
printError("%v\n", err) | |
} | |
} | |
} | |
} | |
func printError(format string, a ...any) { | |
fmt.Fprintf(os.Stderr, format, a...) | |
os.Exit(1) | |
} | |
func getInterfaceIndex(ifaceName string) (uint32, error) { | |
iface, err := net.InterfaceByName(ifaceName) | |
if err != nil { | |
return 0, fmt.Errorf("failed to find the interface %q: %w", ifaceName, err) | |
} | |
return uint32(iface.Index), nil | |
} | |
type ipv6Addr struct { | |
ip net.IP | |
prefixLength uint8 | |
validLifetime uint32 | |
} | |
// checkAddress check if the address is for the interface we are looking for | |
// and if the address is global unicast and tentative | |
// tentativeCheck: check if the address is tentative | |
// | |
// - true: the address should be tentative, otherwise return false | |
// | |
// - false: there is no check for tentative | |
// | |
// tentative means the address is newly added and not yet verified | |
// https://man7.org/linux/man-pages/man8/ip-address.8.html | |
func checkAddress(addr rtnetlink.AddressMessage, ifaceIdx uint32, tentativeCheck bool) bool { | |
// check if the message is for the interface we are looking for | |
// and if the address is ipv6 | |
if addr.Index != ifaceIdx || addr.Family != unix.AF_INET6 { | |
return false | |
} | |
// check if the address is global unicast | |
if addr.Scope != syscall.RT_SCOPE_UNIVERSE { | |
return false | |
} | |
// check if the address is tentative | |
if tentativeCheck && addr.Flags&syscall.IFA_F_TENTATIVE != syscall.IFA_F_TENTATIVE { | |
return false | |
} | |
// check if the address is global | |
if addr.Attributes != nil && (!addr.Attributes.Address.IsGlobalUnicast() || addr.Attributes.Address.IsPrivate()) { | |
return false | |
} | |
return true | |
} | |
// deleteOldIpv6Addr delete the old ipv6 addresses except the one with the longest lifetime | |
func deleteOldIpv6Addr(ifaceIdx uint32) error { | |
// create a rtnetlink connection | |
conn, err := rtnetlink.Dial(nil) | |
if err != nil { | |
return err | |
} | |
defer conn.Close() | |
fmt.Fprintf(os.Stderr, "Deleting old ipv6 addresses...\n") | |
// query the ipv6 addresses | |
msgs, err := conn.Address.List() | |
if err != nil { | |
return err | |
} | |
var longestLifetime uint32 | |
var prefixAddrIdx int | |
var addrs []ipv6Addr | |
// parse the messages | |
for _, msg := range msgs { | |
if checkAddress(msg, ifaceIdx, false) { | |
attr := msg.Attributes | |
if attr == nil { | |
continue | |
} | |
// check if the address is global unicast | |
if !attr.Address.IsGlobalUnicast() || attr.Address.IsPrivate() { | |
continue | |
} | |
if attr.CacheInfo.Valid > longestLifetime { | |
longestLifetime = attr.CacheInfo.Valid | |
prefixAddrIdx = len(addrs) | |
} | |
addrs = append(addrs, ipv6Addr{ | |
ip: attr.Address, | |
prefixLength: msg.PrefixLength, | |
validLifetime: attr.CacheInfo.Valid, | |
}) | |
} | |
} | |
if len(addrs) == 0 { | |
fmt.Fprintf(os.Stderr, "No proper ipv6 address found\n") | |
return nil | |
} | |
ipPrefix := net.IPNet{ | |
IP: addrs[prefixAddrIdx].ip, | |
Mask: net.CIDRMask(int(addrs[prefixAddrIdx].prefixLength), 128), | |
} | |
// delete the old addresses | |
count := 0 | |
for _, addr := range addrs { | |
if addr.validLifetime == longestLifetime { | |
continue | |
} | |
// check if the address is in the same prefix | |
if ipPrefix.Contains(addr.ip) { | |
continue | |
} | |
// delete the address | |
err = conn.Address.Delete(&rtnetlink.AddressMessage{ | |
Family: unix.AF_INET6, | |
PrefixLength: addr.prefixLength, | |
Index: ifaceIdx, | |
Attributes: &rtnetlink.AddressAttributes{ | |
Address: addr.ip, | |
}, | |
}) | |
if err != nil { | |
return fmt.Errorf("failed to delete old ipv6 addr %s: %w", addr.ip, err) | |
} | |
fmt.Fprintf(os.Stderr, "Deleted old ipv6 addr %s\n", addr.ip) | |
count++ | |
} | |
fmt.Fprintf(os.Stderr, "Deleted %d old ipv6 addresses\n", count) | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment