mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/kube-vip/kube-vip.git
synced 2026-09-20 08:03:47 +08:00
Serialize the interface link cache, route tracker and address configuration so concurrent Services cannot corrupt shared state or deadlock on nested address locks. Signed-off-by: Marcel Fest <marcel.fest@telekom.de>
270 lines
7.4 KiB
Go
270 lines
7.4 KiB
Go
package vip
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
log "log/slog"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/vishvananda/netlink"
|
|
)
|
|
|
|
var errDefaultInterfaceSubscriptionClosed = errors.New("default interface subscription closed")
|
|
|
|
// getHostName return the hostname from the fqdn
|
|
func getHostName(dnsName string) string {
|
|
if dnsName == "" {
|
|
return ""
|
|
}
|
|
|
|
fields := strings.Split(dnsName, ".")
|
|
return fields[0]
|
|
}
|
|
|
|
// GetDefaultGatewayInterface return default gateway interface link
|
|
func GetDefaultGatewayInterface() (iface *net.Interface, err error) {
|
|
// Attempt IPv4 first (usually the default)
|
|
if iface, err = getDefaultRoute(syscall.AF_INET); err == nil {
|
|
return iface, nil
|
|
}
|
|
|
|
// If the IPv4 default route is not found, then attempt IPv6 default route.
|
|
if iface, err = getDefaultRoute(syscall.AF_INET6); err == nil {
|
|
return iface, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unable to find interface with default route: %w", err)
|
|
}
|
|
|
|
// getDefaultRoute attempts to find the default route for the specified address family.
|
|
func getDefaultRoute(family int) (*net.Interface, error) {
|
|
// only search for default routes
|
|
filter := &netlink.Route{Dst: nil}
|
|
mask := netlink.RT_FILTER_DST
|
|
|
|
routes, err := netlink.RouteListFiltered(family, filter, mask)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listing routes: %w", err)
|
|
}
|
|
|
|
for _, route := range routes {
|
|
// double check
|
|
if route.Dst != nil && route.Dst.String() != "0.0.0.0/0" && route.Dst.String() != "::/0" {
|
|
continue
|
|
}
|
|
|
|
idx := route.LinkIndex
|
|
|
|
// handle MultiPath
|
|
if idx <= 0 && len(route.MultiPath) > 0 {
|
|
for _, nh := range route.MultiPath {
|
|
if nh.LinkIndex > 0 {
|
|
idx = nh.LinkIndex
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if idx > 0 {
|
|
return net.InterfaceByIndex(idx)
|
|
}
|
|
}
|
|
return nil, errors.New("default route not found")
|
|
}
|
|
|
|
// MonitorDefaultInterface monitors the default interface for route removal or link loss.
|
|
func MonitorDefaultInterface(ctx context.Context, defaultIF *net.Interface) error {
|
|
return monitorDefaultInterfaceWithRetry(ctx, defaultIF, subscribeDefaultInterface, GetDefaultGatewayInterface, 100*time.Millisecond)
|
|
}
|
|
|
|
func monitorDefaultInterfaceWithRetry(ctx context.Context, defaultIF *net.Interface,
|
|
subscribe func(context.Context) (chan netlink.RouteUpdate, chan netlink.LinkUpdate, error),
|
|
lookup func() (*net.Interface, error), retryDelay time.Duration) error {
|
|
for {
|
|
monitorCtx, cancel := context.WithCancel(ctx)
|
|
routeCh, linkCh, err := subscribe(monitorCtx)
|
|
if err == nil {
|
|
err = monitorDefaultInterface(monitorCtx, defaultIF, routeCh, linkCh)
|
|
}
|
|
cancel()
|
|
drainDefaultInterfaceSubscriptions(routeCh, linkCh)
|
|
if ctx.Err() != nil {
|
|
return nil
|
|
}
|
|
if err != nil && !errors.Is(err, errDefaultInterfaceSubscriptionClosed) {
|
|
log.Warn("default interface subscription failed, retrying", "err", err)
|
|
} else if err == nil {
|
|
return nil
|
|
}
|
|
if refreshed, lookupErr := lookup(); lookupErr == nil {
|
|
defaultIF = refreshed
|
|
} else {
|
|
log.Warn("failed to refresh default interface while resubscribing", "err", lookupErr)
|
|
}
|
|
timer := time.NewTimer(retryDelay)
|
|
select {
|
|
case <-ctx.Done():
|
|
timer.Stop()
|
|
return nil
|
|
case <-timer.C:
|
|
}
|
|
}
|
|
}
|
|
|
|
func subscribeDefaultInterface(ctx context.Context) (chan netlink.RouteUpdate, chan netlink.LinkUpdate, error) {
|
|
const subscriptionBuffer = 64
|
|
routeCh := make(chan netlink.RouteUpdate, subscriptionBuffer)
|
|
if err := netlink.RouteSubscribe(routeCh, ctx.Done()); err != nil {
|
|
return nil, nil, fmt.Errorf("subscribe route failed, error: %w", err)
|
|
}
|
|
linkCh := make(chan netlink.LinkUpdate, subscriptionBuffer)
|
|
if err := netlink.LinkSubscribe(linkCh, ctx.Done()); err != nil {
|
|
return routeCh, nil, fmt.Errorf("subscribe link failed, error: %w", err)
|
|
}
|
|
|
|
return routeCh, linkCh, nil
|
|
}
|
|
|
|
func monitorDefaultInterface(ctx context.Context, defaultIF *net.Interface, routeCh <-chan netlink.RouteUpdate, linkCh <-chan netlink.LinkUpdate) error {
|
|
for {
|
|
select {
|
|
case r, ok := <-routeCh:
|
|
if !ok {
|
|
return subscriptionClosed(ctx, "route")
|
|
}
|
|
log.Debug(fmt.Sprintf("type: %d, route: %+v", r.Type, r.Route))
|
|
if r.Type == syscall.RTM_DELROUTE && isDefaultRoute(r.Dst) && r.LinkIndex == defaultIF.Index {
|
|
return fmt.Errorf("default route deleted and the default interface may be invalid")
|
|
}
|
|
case update, ok := <-linkCh:
|
|
if !ok {
|
|
return subscriptionClosed(ctx, "link")
|
|
}
|
|
if update.Link == nil {
|
|
continue
|
|
}
|
|
attrs := update.Attrs()
|
|
if attrs != nil && attrs.Index == defaultIF.Index && attrs.Flags&net.FlagUp == 0 {
|
|
return fmt.Errorf("default interface %q is down", defaultIF.Name)
|
|
}
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func subscriptionClosed(ctx context.Context, subscription string) error {
|
|
if ctx.Err() != nil {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%w: %s subscription closed", errDefaultInterfaceSubscriptionClosed, subscription)
|
|
}
|
|
|
|
// isDefaultRoute accepts both families, matching the selection made by
|
|
// GetDefaultGatewayInterface.
|
|
func isDefaultRoute(dst *net.IPNet) bool {
|
|
if dst == nil {
|
|
return true
|
|
}
|
|
return dst.String() == "0.0.0.0/0" || dst.String() == "::/0"
|
|
}
|
|
|
|
func drainDefaultInterfaceSubscriptions(routeCh <-chan netlink.RouteUpdate, linkCh <-chan netlink.LinkUpdate) {
|
|
timer := time.NewTimer(100 * time.Millisecond)
|
|
defer timer.Stop()
|
|
|
|
for routeCh != nil || linkCh != nil {
|
|
select {
|
|
case _, ok := <-routeCh:
|
|
if !ok {
|
|
routeCh = nil
|
|
}
|
|
case _, ok := <-linkCh:
|
|
if !ok {
|
|
linkCh = nil
|
|
}
|
|
case <-timer.C:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func GenerateMac() (mac string) {
|
|
buf := make([]byte, 3)
|
|
_, err := rand.Read(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
/**
|
|
* The first 3 bytes need to match a real manufacturer
|
|
* you can refer to the following lists for examples:
|
|
* - https://gist.github.com/aallan/b4bb86db86079509e6159810ae9bd3e4
|
|
* - https://macaddress.io/database-download
|
|
*/
|
|
mac = fmt.Sprintf("%s:%s:%s:%02x:%02x:%02x", "00", "00", "6C", buf[0], buf[1], buf[2])
|
|
log.Info("Generated mac", "address", mac)
|
|
return mac
|
|
}
|
|
|
|
func Split(values string) []string {
|
|
result := strings.Split(values, ",")
|
|
for i := range result {
|
|
result[i] = strings.TrimSpace(result[i])
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetInterfaceByIP returns the network interface that has the specified IP address assigned.
|
|
func GetInterfaceByIP(ipAddr string) (*netlink.Link, error) {
|
|
ip := net.ParseIP(ipAddr)
|
|
if ip == nil {
|
|
return nil, fmt.Errorf("invalid IP address: %s", ipAddr)
|
|
}
|
|
|
|
links, err := netlink.LinkList()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list network interfaces: %v", err)
|
|
}
|
|
|
|
for i := range links {
|
|
addrs, err := netlink.AddrList(links[i], netlink.FAMILY_ALL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list addresses for interface %s: %v", links[i].Attrs().Name, err)
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
if addr.IP.Equal(ip) {
|
|
return &links[i], nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("no interface found with IP address: %s", ipAddr)
|
|
}
|
|
|
|
// GetNonLinkLocalIP returns the first non link-local IPv4/IPv6 address on the given interface.
|
|
func GetNonLinkLocalIP(iface *netlink.Link, family int) (string, error) {
|
|
a, err := netlink.AddrList(*iface, family)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to list addresses for interface %s: %v", (*iface).Attrs().Name, err)
|
|
}
|
|
|
|
for _, addr := range a {
|
|
if addr.IPNet != nil {
|
|
ip := addr.IPNet.IP
|
|
if !ip.IsLinkLocalUnicast() {
|
|
return ip.String(), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("failed to find non-local IP on interface: %s", (*iface).Attrs().Name)
|
|
}
|