mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/kube-vip/kube-vip.git
synced 2026-09-20 08:03:47 +08:00
Fixed service DNS resolve
Signed-off-by: Patryk Strusiewicz-Surmacki <patryk.pawel.strusiewicz-surmacki@external.telekom.de>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -133,6 +134,7 @@ func init() {
|
|||||||
kubeVipCmd.PersistentFlags().BoolVar(&initConfig.EnableNodeLabeling, "enableNodeLabeling", false, fmt.Sprintf("Enable leader node labeling with %q, defaults to false", kubevip.HasIP))
|
kubeVipCmd.PersistentFlags().BoolVar(&initConfig.EnableNodeLabeling, "enableNodeLabeling", false, fmt.Sprintf("Enable leader node labeling with %q, defaults to false", kubevip.HasIP))
|
||||||
kubeVipCmd.PersistentFlags().StringVar(&initConfig.ServicesLeaseName, "servicesLeaseName", "plndr-svcs-lock", "Name of the lease that is used for leader election for services (in arp mode)")
|
kubeVipCmd.PersistentFlags().StringVar(&initConfig.ServicesLeaseName, "servicesLeaseName", "plndr-svcs-lock", "Name of the lease that is used for leader election for services (in arp mode)")
|
||||||
kubeVipCmd.PersistentFlags().StringVar(&initConfig.DNSMode, "dnsMode", "first", "Name of the mode that DNS lookup will be performed (first, ipv4, ipv6, dual)")
|
kubeVipCmd.PersistentFlags().StringVar(&initConfig.DNSMode, "dnsMode", "first", "Name of the mode that DNS lookup will be performed (first, ipv4, ipv6, dual)")
|
||||||
|
kubeVipCmd.PersistentFlags().StringVar(&initConfig.DHCPMode, "dhcpMode", "", "Mode DHCP resolving will use to obtain IP addresses (ipv4, ipv6, dual)")
|
||||||
kubeVipCmd.PersistentFlags().BoolVar(&initConfig.DisableServiceUpdates, "disableServiceUpdates", false, "If true, kube-vip will process services as usual, but will not update service's Status.LoadBalancer.Ingress slice")
|
kubeVipCmd.PersistentFlags().BoolVar(&initConfig.DisableServiceUpdates, "disableServiceUpdates", false, "If true, kube-vip will process services as usual, but will not update service's Status.LoadBalancer.Ingress slice")
|
||||||
kubeVipCmd.PersistentFlags().BoolVar(&initConfig.EnableEndpoints, "enableEndpoints", false, "If enabled, kube-vip will only advertise services, but will use the (deprecated since v1.33) endpoints for IP addresses")
|
kubeVipCmd.PersistentFlags().BoolVar(&initConfig.EnableEndpoints, "enableEndpoints", false, "If enabled, kube-vip will only advertise services, but will use the (deprecated since v1.33) endpoints for IP addresses")
|
||||||
kubeVipCmd.PersistentFlags().BoolVar(&initConfig.LoInterfaceGlobalScope, "loInterfaceGlobalScope", false, "If true, kube-vip will set global scope when using the lo interface, otherwise a host scope will be used by default")
|
kubeVipCmd.PersistentFlags().BoolVar(&initConfig.LoInterfaceGlobalScope, "loInterfaceGlobalScope", false, "If true, kube-vip will set global scope when using the lo interface, otherwise a host scope will be used by default")
|
||||||
@@ -483,23 +485,23 @@ func GenerateCidrRange(address string, dnsMode string) (string, error) {
|
|||||||
ip := net.ParseIP(a)
|
ip := net.ParseIP(a)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
// we probably are a DNS name
|
// we probably are a DNS name
|
||||||
ips, err := utils.LookupHost(a, dnsMode)
|
ips, err := utils.LookupHost(a, dnsMode, true)
|
||||||
if len(ips) == 0 || err != nil {
|
if len(ips) == 0 || err != nil {
|
||||||
return "", fmt.Errorf("invalid IP address: %s from [%s], %v", a, address, err)
|
return "", fmt.Errorf("invalid IP address: %s from [%s], %v", a, address, err)
|
||||||
}
|
}
|
||||||
for _, addr := range ips {
|
for _, addr := range ips {
|
||||||
ip = net.ParseIP(addr)
|
ip = net.ParseIP(addr)
|
||||||
if ip.To4() != nil {
|
if ip.To4() != nil {
|
||||||
cidrs = append(cidrs, "32")
|
cidrs = append(cidrs, strconv.Itoa(vip.DefaultMaskIPv4))
|
||||||
} else {
|
} else {
|
||||||
cidrs = append(cidrs, "128")
|
cidrs = append(cidrs, strconv.Itoa(vip.DefaultMaskIPv6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ip.To4() != nil {
|
if ip.To4() != nil {
|
||||||
cidrs = append(cidrs, "32")
|
cidrs = append(cidrs, strconv.Itoa(vip.DefaultMaskIPv4))
|
||||||
} else {
|
} else {
|
||||||
cidrs = append(cidrs, "128")
|
cidrs = append(cidrs, strconv.Itoa(vip.DefaultMaskIPv6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
//nolint
|
//nolint
|
||||||
|
|
||||||
"github.com/kube-vip/kube-vip/pkg/kubevip"
|
"github.com/kube-vip/kube-vip/pkg/kubevip"
|
||||||
|
"github.com/kube-vip/kube-vip/pkg/vip"
|
||||||
api "github.com/osrg/gobgp/v3/api"
|
api "github.com/osrg/gobgp/v3/api"
|
||||||
|
|
||||||
"github.com/kube-vip/kube-vip/pkg/utils"
|
"github.com/kube-vip/kube-vip/pkg/utils"
|
||||||
@@ -74,11 +76,11 @@ func (b *Server) AddPeer(peer kubevip.BGPPeer) (err error) {
|
|||||||
return fmt.Errorf("failed to get MP-BGP addresses: %w", err)
|
return fmt.Errorf("failed to get MP-BGP addresses: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mask := "128"
|
mask := strconv.Itoa(vip.DefaultMaskIPv6)
|
||||||
address := ipv4Address
|
address := ipv4Address
|
||||||
family := api.Family_AFI_IP
|
family := api.Family_AFI_IP
|
||||||
if utils.IsIPv4(p.Conf.NeighborAddress) {
|
if utils.IsIPv4(p.Conf.NeighborAddress) {
|
||||||
mask = "32"
|
mask = strconv.Itoa(vip.DefaultMaskIPv4)
|
||||||
address = ipv6Address
|
address = ipv6Address
|
||||||
family = api.Family_AFI_IP6
|
family = api.Family_AFI_IP6
|
||||||
}
|
}
|
||||||
@@ -128,7 +130,7 @@ func (b *Server) getPath(ip net.IP) (path *api.Path) {
|
|||||||
//nolint
|
//nolint
|
||||||
nlri, _ := anypb.New(&api.IPAddressPrefix{
|
nlri, _ := anypb.New(&api.IPAddressPrefix{
|
||||||
Prefix: ip.String(),
|
Prefix: ip.String(),
|
||||||
PrefixLen: 32,
|
PrefixLen: vip.DefaultMaskIPv4,
|
||||||
})
|
})
|
||||||
|
|
||||||
//nolint
|
//nolint
|
||||||
@@ -148,7 +150,7 @@ func (b *Server) getPath(ip net.IP) (path *api.Path) {
|
|||||||
//nolint
|
//nolint
|
||||||
nlri, _ := anypb.New(&api.IPAddressPrefix{
|
nlri, _ := anypb.New(&api.IPAddressPrefix{
|
||||||
Prefix: ip.String(),
|
Prefix: ip.String(),
|
||||||
PrefixLen: 128,
|
PrefixLen: vip.DefaultMaskIPv6,
|
||||||
})
|
})
|
||||||
|
|
||||||
v6Family := &api.Family{
|
v6Family := &api.Family{
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ func startNetworking(c *kubevip.Config, intfMgr *networkinterface.Manager) ([]vi
|
|||||||
|
|
||||||
networks := []vip.Network{}
|
networks := []vip.Network{}
|
||||||
for _, addr := range addresses {
|
for _, addr := range addresses {
|
||||||
network, err := vip.NewConfig(addr, c.Interface, c.LoInterfaceGlobalScope, c.VIPSubnet, c.DDNS, c.RoutingTableID,
|
network, err := vip.NewConfig(addr, c.Interface, c.LoInterfaceGlobalScope, c.VIPSubnet, c.DDNS, c.DHCPMode,
|
||||||
c.RoutingTableType, c.RoutingProtocol, c.DNSMode, c.LoadBalancerForwardingMethod, c.IptablesBackend,
|
c.RequireDualStack, c.IsDualStack, c.RoutingTableID, c.RoutingTableType, c.RoutingProtocol, c.DNSMode,
|
||||||
c.EnableLoadBalancer, c.EnableServiceSecurity, intfMgr)
|
c.LoadBalancerForwardingMethod, c.IptablesBackend, c.EnableLoadBalancer, c.EnableServiceSecurity, intfMgr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
// dnsUpdater already have the functionality to keep trying resolve the IP
|
// dnsUpdater already have the functionality to keep trying resolve the IP
|
||||||
// and update the VIP configuration if it changes
|
// and update the VIP configuration if it changes
|
||||||
func (cluster *Cluster) StartDDNS(ctx context.Context, network vip.Network) error {
|
func (cluster *Cluster) StartDDNS(ctx context.Context, network vip.Network) error {
|
||||||
ddnsMgr := vip.NewDDNSManager(ctx, network)
|
ddnsMgr := vip.NewDDNSManager(network)
|
||||||
ip, err := ddnsMgr.Start()
|
ip, err := ddnsMgr.Start(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "log/slog"
|
log "log/slog"
|
||||||
|
|
||||||
@@ -276,9 +278,28 @@ func (cluster *Cluster) StartLoadBalancerService(ctx context.Context, c *kubevip
|
|||||||
|
|
||||||
var arpWG sync.WaitGroup
|
var arpWG sync.WaitGroup
|
||||||
|
|
||||||
log.Debug("StartLoadBalancerService")
|
log.Debug("StartLoadBalancerService", "networks", len(cluster.Network))
|
||||||
for i := range cluster.Network {
|
for i := range cluster.Network {
|
||||||
network := cluster.Network[i]
|
network := cluster.Network[i]
|
||||||
|
|
||||||
|
if network.IsDDNS() {
|
||||||
|
ddnsReady := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
ctxDDNS, ddnsCancel := context.WithCancel(ctx)
|
||||||
|
defer ddnsCancel()
|
||||||
|
|
||||||
|
// start the DDNS if requested
|
||||||
|
log.Debug("(svcs) start DDNS", "name", network.DNSName())
|
||||||
|
if err := cluster.StartDDNS(ctxDDNS, cluster.Network[i]); err != nil {
|
||||||
|
log.Error("failed to start DDNS", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ddnsReady)
|
||||||
|
<-cluster.stop
|
||||||
|
}()
|
||||||
|
<-ddnsReady
|
||||||
|
}
|
||||||
|
|
||||||
log.Debug("current ip to process", "ip", network.IP(), "mask", c.VIPSubnet)
|
log.Debug("current ip to process", "ip", network.IP(), "mask", c.VIPSubnet)
|
||||||
if err := network.SetMask(c.VIPSubnet); err != nil {
|
if err := network.SetMask(c.VIPSubnet); err != nil {
|
||||||
log.Error("failed to set mask", "subnet", c.VIPSubnet, "err", err)
|
log.Error("failed to set mask", "subnet", c.VIPSubnet, "err", err)
|
||||||
@@ -322,6 +343,20 @@ func (cluster *Cluster) StartLoadBalancerService(ctx context.Context, c *kubevip
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
for i := range cluster.Network {
|
||||||
|
network := cluster.Network[i]
|
||||||
|
|
||||||
|
ctxDNS, dnsCancel := context.WithCancel(ctx)
|
||||||
|
defer dnsCancel()
|
||||||
|
|
||||||
|
// start the dns updater if address is dns
|
||||||
|
if network.IsDNS() {
|
||||||
|
log.Info("(svcs) starting the DNS updater", "address", network.DNSName(), "ip", network.IP())
|
||||||
|
ipUpdater := vip.NewIPUpdater(network)
|
||||||
|
ipUpdater.Run(ctxDNS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<-cluster.stop
|
<-cluster.stop
|
||||||
// Stop the Arp context if it is running
|
// Stop the Arp context if it is running
|
||||||
cancelArp()
|
cancelArp()
|
||||||
@@ -387,16 +422,14 @@ func (cluster *Cluster) StartLoadBalancerService(ctx context.Context, c *kubevip
|
|||||||
// Layer2Update, handles the creation of the
|
// Layer2Update, handles the creation of the
|
||||||
func (cluster *Cluster) layer2Update(ctx context.Context, network vip.Network, c *kubevip.Config, arpWG *sync.WaitGroup) {
|
func (cluster *Cluster) layer2Update(ctx context.Context, network vip.Network, c *kubevip.Config, arpWG *sync.WaitGroup) {
|
||||||
defer arpWG.Done()
|
defer arpWG.Done()
|
||||||
log.Info("layer 2 broadcaster starting")
|
|
||||||
var ndp *vip.NdpResponder
|
var ndp *vip.NdpResponder
|
||||||
var err error
|
var err error
|
||||||
ipString := network.IP()
|
ipString := network.IP()
|
||||||
if utils.IsIPv6(ipString) {
|
if utils.IsIPv6(ipString) {
|
||||||
if network.IPisLinkLocal() {
|
if network.IPisLinkLocal() {
|
||||||
log.Error("layer2 is link-local can't use NDP", "address", ipString)
|
log.Error("layer2 is link-local can't use NDP", "address", ipString)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ndp, err = vip.NewNDPResponder(network.Interface())
|
ndp, err = waitNDPResponder(ctx, network.Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to create new NDP Responder", "error", err)
|
log.Error("failed to create new NDP Responder", "error", err)
|
||||||
} else {
|
} else {
|
||||||
@@ -407,6 +440,7 @@ func (cluster *Cluster) layer2Update(ctx context.Context, network vip.Network, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("layer 2 broadcaster starting", "IP", network.IP(), "device", network.Interface())
|
||||||
log.Debug("layer 2 update", "ip", ipString, "interface", network.Interface(), "ms", c.ArpBroadcastRate)
|
log.Debug("layer 2 update", "ip", ipString, "interface", network.Interface(), "ms", c.ArpBroadcastRate)
|
||||||
|
|
||||||
arpInstance := arp.NewInstance(network, ndp)
|
arpInstance := arp.NewInstance(network, ndp)
|
||||||
@@ -416,3 +450,30 @@ func (cluster *Cluster) layer2Update(ctx context.Context, network vip.Network, c
|
|||||||
log.Debug("ending layer 2 update", "ip", ipString, "interface", network.Interface(), "ms", c.ArpBroadcastRate)
|
log.Debug("ending layer 2 update", "ip", ipString, "interface", network.Interface(), "ms", c.ArpBroadcastRate)
|
||||||
cluster.arpMgr.RemoveOnLeadershipLoss(arpInstance)
|
cluster.arpMgr.RemoveOnLeadershipLoss(arpInstance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func waitNDPResponder(ctx context.Context, ifaceName string) (*vip.NdpResponder, error) {
|
||||||
|
ndp, err := vip.NewNDPResponder(ifaceName)
|
||||||
|
if err != nil && strings.Contains(err.Error(), "no such device") {
|
||||||
|
log.Warn("unable to create NDP responder at first try", "interface", ifaceName, "err", err)
|
||||||
|
ndpCreateCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ndpCreateCtx.Done():
|
||||||
|
return nil, fmt.Errorf("failed to create NDP responder for interface %q: %w", ifaceName, ndpCreateCtx.Err())
|
||||||
|
case <-ticker.C:
|
||||||
|
ndp, err = vip.NewNDPResponder(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("unable to create NDP responder on retry", "interface", ifaceName, "err", err)
|
||||||
|
} else {
|
||||||
|
return ndp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create NDP responder for interface %q: %w", ifaceName, err)
|
||||||
|
}
|
||||||
|
return ndp, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package instance
|
package instance
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -30,18 +32,24 @@ type Instance struct {
|
|||||||
Clusters []*cluster.Cluster
|
Clusters []*cluster.Cluster
|
||||||
|
|
||||||
// Service uses DHCP
|
// Service uses DHCP
|
||||||
IsDHCP bool
|
IsDHCPv4 bool
|
||||||
|
IsDHCPv6 bool
|
||||||
DHCPInterface string
|
DHCPInterface string
|
||||||
DHCPInterfaceHwaddr string
|
DHCPInterfaceHwaddr string
|
||||||
DHCPInterfaceIP string
|
DHCPInterfaceIP string
|
||||||
|
DHCPInterfaceIPv4 string
|
||||||
|
DHCPInterfaceIPv6 string
|
||||||
DHCPHostname string
|
DHCPHostname string
|
||||||
DHCPClient *vip.DHCPClient
|
DHCPv4Client vip.DHCPClient
|
||||||
|
DHCPv6Client vip.DHCPClient
|
||||||
|
|
||||||
// External Gateway IP the service is forwarded from
|
// External Gateway IP the service is forwarded from
|
||||||
UPNPGatewayIPs []string
|
UPNPGatewayIPs []string
|
||||||
|
|
||||||
// Kubernetes service mapping
|
// Kubernetes service mapping
|
||||||
ServiceSnapshot *v1.Service
|
ServiceSnapshot *v1.Service
|
||||||
|
|
||||||
|
dnsAddresses []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Port struct {
|
type Port struct {
|
||||||
@@ -49,12 +57,14 @@ type Port struct {
|
|||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInstance(svc *v1.Service, config *kubevip.Config, intfMgr *networkinterface.Manager, arpMgr *arp.Manager) (*Instance, error) {
|
func NewInstance(ctx context.Context, svc *v1.Service, config *kubevip.Config, intfMgr *networkinterface.Manager, arpMgr *arp.Manager) (*Instance, error) {
|
||||||
instanceAddresses, _ := FetchServiceAddresses(svc)
|
instanceAddresses, instanceHostnames := FetchServiceAddresses(svc)
|
||||||
|
log.Info("NewInstance used", "instanceAddresses", instanceAddresses, "instanceHostnames", instanceHostnames)
|
||||||
|
|
||||||
var newVips []*kubevip.Config
|
var newVips []*kubevip.Config
|
||||||
var link netlink.Link
|
var link netlink.Link
|
||||||
var err error
|
var err error
|
||||||
|
var dnsAddresses []string
|
||||||
|
|
||||||
for _, address := range instanceAddresses {
|
for _, address := range instanceAddresses {
|
||||||
// Detect if we're using a specific interface for services
|
// Detect if we're using a specific interface for services
|
||||||
@@ -130,7 +140,7 @@ func NewInstance(svc *v1.Service, config *kubevip.Config, intfMgr *networkinterf
|
|||||||
if cidrs[0] != "" && cidrs[0] != kubevip.Auto {
|
if cidrs[0] != "" && cidrs[0] != kubevip.Auto {
|
||||||
subnet = cidrs[0]
|
subnet = cidrs[0]
|
||||||
} else {
|
} else {
|
||||||
subnet = "32"
|
subnet = strconv.Itoa(vip.DefaultMaskIPv4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -143,7 +153,7 @@ func NewInstance(svc *v1.Service, config *kubevip.Config, intfMgr *networkinterf
|
|||||||
if len(cidrs) > 1 && cidrs[1] != "" && cidrs[1] != kubevip.Auto {
|
if len(cidrs) > 1 && cidrs[1] != "" && cidrs[1] != kubevip.Auto {
|
||||||
subnet = cidrs[1]
|
subnet = cidrs[1]
|
||||||
} else {
|
} else {
|
||||||
subnet = "128"
|
subnet = strconv.Itoa(vip.DefaultMaskIPv6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,6 +173,7 @@ func NewInstance(svc *v1.Service, config *kubevip.Config, intfMgr *networkinterf
|
|||||||
ArpBroadcastRate: config.ArpBroadcastRate,
|
ArpBroadcastRate: config.ArpBroadcastRate,
|
||||||
EnableServiceSecurity: config.EnableServiceSecurity,
|
EnableServiceSecurity: config.EnableServiceSecurity,
|
||||||
DNSMode: config.DNSMode,
|
DNSMode: config.DNSMode,
|
||||||
|
DHCPMode: config.DHCPMode,
|
||||||
DisableServiceUpdates: config.DisableServiceUpdates,
|
DisableServiceUpdates: config.DisableServiceUpdates,
|
||||||
EnableServicesElection: config.EnableServicesElection,
|
EnableServicesElection: config.EnableServicesElection,
|
||||||
PreserveVIPOnLeadershipLoss: config.PreserveVIPOnLeadershipLoss,
|
PreserveVIPOnLeadershipLoss: config.PreserveVIPOnLeadershipLoss,
|
||||||
@@ -172,22 +183,78 @@ func NewInstance(svc *v1.Service, config *kubevip.Config, intfMgr *networkinterf
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, hostname := range instanceHostnames {
|
||||||
|
log.Info("hostname", "addr", hostname)
|
||||||
|
// Detect if we're using a specific interface for services
|
||||||
|
var svcInterface string
|
||||||
|
svcInterface = svc.Annotations[kubevip.ServiceInterface] // If the service has a specific interface defined, then use it
|
||||||
|
|
||||||
|
// If it is still blank then use the
|
||||||
|
if svcInterface == "" {
|
||||||
|
switch config.ServicesInterface {
|
||||||
|
case "":
|
||||||
|
svcInterface = config.Interface
|
||||||
|
default:
|
||||||
|
svcInterface = config.ServicesInterface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if link == nil {
|
||||||
|
if link, err = netlink.LinkByName(svcInterface); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get interface %s: %w", svcInterface, err)
|
||||||
|
}
|
||||||
|
if link == nil {
|
||||||
|
return nil, fmt.Errorf("failed to get interface %s", svcInterface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new Virtual IP configuration
|
||||||
|
newVips = append(newVips, &kubevip.Config{
|
||||||
|
VIP: hostname,
|
||||||
|
Interface: svcInterface,
|
||||||
|
SingleNode: true,
|
||||||
|
EnableARP: config.EnableARP,
|
||||||
|
EnableBGP: config.EnableBGP,
|
||||||
|
VIPSubnet: config.VIPSubnet,
|
||||||
|
EnableRoutingTable: config.EnableRoutingTable,
|
||||||
|
RoutingTableID: config.RoutingTableID,
|
||||||
|
RoutingTableType: config.RoutingTableType,
|
||||||
|
RoutingProtocol: config.RoutingProtocol,
|
||||||
|
ArpBroadcastRate: config.ArpBroadcastRate,
|
||||||
|
EnableServiceSecurity: config.EnableServiceSecurity,
|
||||||
|
DNSMode: config.DNSMode,
|
||||||
|
DHCPMode: config.DHCPMode,
|
||||||
|
DisableServiceUpdates: config.DisableServiceUpdates,
|
||||||
|
EnableServicesElection: config.EnableServicesElection,
|
||||||
|
KubernetesLeaderElection: kubevip.KubernetesLeaderElection{
|
||||||
|
EnableLeaderElection: config.EnableLeaderElection,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Create new service
|
// Create new service
|
||||||
instance := &Instance{
|
instance := &Instance{
|
||||||
//UID: instanceUID,
|
|
||||||
//VIPs: instanceAddresses,
|
|
||||||
ServiceSnapshot: svc,
|
ServiceSnapshot: svc,
|
||||||
|
dnsAddresses: dnsAddresses,
|
||||||
}
|
}
|
||||||
// for _, port := range svc.Spec.Ports {
|
|
||||||
// instance.ExternalPorts = append(instance.ExternalPorts, Port{
|
|
||||||
// Port: uint16(port.Port), //nolint
|
|
||||||
// Type: string(port.Protocol),
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
if svc.Annotations != nil {
|
if svc.Annotations != nil {
|
||||||
instance.DHCPInterfaceHwaddr = svc.Annotations[kubevip.HwAddrKey]
|
instance.DHCPInterfaceHwaddr = svc.Annotations[kubevip.HwAddrKey]
|
||||||
instance.DHCPInterfaceIP = svc.Annotations[kubevip.RequestedIP]
|
requestedIP := svc.Annotations[kubevip.RequestedIP]
|
||||||
|
if requestedIP != "" {
|
||||||
|
requestedIPs := strings.Split(requestedIP, ",")
|
||||||
|
if len(requestedIPs) > 2 {
|
||||||
|
return nil, fmt.Errorf("annotation %q cannot request more than one IPv4 and one Ipv6 address", kubevip.RequestedIP)
|
||||||
|
}
|
||||||
|
for _, ip := range requestedIPs {
|
||||||
|
netip := net.ParseIP(ip)
|
||||||
|
if netip.To4() != nil {
|
||||||
|
instance.DHCPInterfaceIPv4 = ip
|
||||||
|
} else {
|
||||||
|
instance.DHCPInterfaceIPv6 = ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
instance.DHCPHostname = svc.Annotations[kubevip.LoadbalancerHostname]
|
instance.DHCPHostname = svc.Annotations[kubevip.LoadbalancerHostname]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,29 +278,82 @@ func NewInstance(svc *v1.Service, config *kubevip.Config, intfMgr *networkinterf
|
|||||||
// Create Add configuration to the new service
|
// Create Add configuration to the new service
|
||||||
instance.VIPConfigs = newVips
|
instance.VIPConfigs = newVips
|
||||||
|
|
||||||
// If this was purposely created with the address 0.0.0.0,
|
// If this was purposely created with the address '0.0.0.0', or '::'
|
||||||
// we will create a macvlan on the main interface and a DHCP client
|
// we will create a macvlan on the main interface and a DHCP client
|
||||||
// TODO: Consider how best to handle DHCP with multiple addresses
|
if len(instanceAddresses) > 2 && (slices.Contains(instanceAddresses, "0.0.0.0") || slices.Contains(instanceAddresses, "::")) {
|
||||||
if len(instanceAddresses) == 1 && instanceAddresses[0] == "0.0.0.0" {
|
return nil, fmt.Errorf("DHCP cannot be used if more than 2 addresses (one IPv4 and one IPv6) were specified")
|
||||||
err := instance.startDHCP()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case err := <-instance.DHCPClient.ErrorChannel():
|
|
||||||
return nil, fmt.Errorf("error starting DHCP for %s/%s: error: %s",
|
|
||||||
instance.ServiceSnapshot.Namespace, instance.ServiceSnapshot.Name, err)
|
|
||||||
case ip := <-instance.DHCPClient.IPChannel():
|
|
||||||
instance.VIPConfigs[0].Interface = instance.DHCPInterface
|
|
||||||
instance.VIPConfigs[0].VIP = ip
|
|
||||||
instance.DHCPInterfaceIP = ip
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for i := range instance.VIPConfigs {
|
||||||
|
if instance.VIPConfigs[i].VIP == "0.0.0.0" {
|
||||||
|
err := instance.startDHCP(ctx, i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case err := <-instance.DHCPv4Client.ErrorChannel():
|
||||||
|
return nil, fmt.Errorf("error starting DHCPv4 for %s/%s: error: %s",
|
||||||
|
instance.ServiceSnapshot.Namespace, instance.ServiceSnapshot.Name, err)
|
||||||
|
case ip := <-instance.DHCPv4Client.IPChannel():
|
||||||
|
instance.VIPConfigs[i].Interface = instance.DHCPInterface
|
||||||
|
instance.VIPConfigs[i].VIP = ip
|
||||||
|
instance.DHCPInterfaceIPv4 = ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if instance.VIPConfigs[i].VIP == "::" {
|
||||||
|
err := instance.startDHCP(ctx, i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case err := <-instance.DHCPv6Client.ErrorChannel():
|
||||||
|
return nil, fmt.Errorf("error starting DHCPv6 for %s/%s: error: %s",
|
||||||
|
instance.ServiceSnapshot.Namespace, instance.ServiceSnapshot.Name, err)
|
||||||
|
case ip := <-instance.DHCPv6Client.IPChannel():
|
||||||
|
instance.VIPConfigs[i].Interface = instance.DHCPInterface
|
||||||
|
instance.VIPConfigs[i].VIP = ip
|
||||||
|
instance.DHCPInterfaceIPv6 = ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, vipConfig := range instance.VIPConfigs {
|
ddnsAnnotation, exists := svc.Annotations[kubevip.ServiceDDNS]
|
||||||
c, err := cluster.InitCluster(vipConfig, false, intfMgr, arpMgr)
|
|
||||||
|
if exists {
|
||||||
|
instance.VIPConfigs[i].DDNS, err = strconv.ParseBool(ddnsAnnotation)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to add service", "err", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(svc.Spec.IPFamilies) > 0 {
|
||||||
|
if len(svc.Spec.IPFamilies) > 1 {
|
||||||
|
instance.VIPConfigs[i].DHCPMode = utils.DualFamily
|
||||||
|
instance.VIPConfigs[i].DNSMode = utils.DualFamily
|
||||||
|
switch *svc.Spec.IPFamilyPolicy {
|
||||||
|
case v1.IPFamilyPolicyRequireDualStack:
|
||||||
|
instance.VIPConfigs[i].IsDualStack = true
|
||||||
|
instance.VIPConfigs[i].RequireDualStack = true
|
||||||
|
case v1.IPFamilyPolicyPreferDualStack:
|
||||||
|
instance.VIPConfigs[i].IsDualStack = true
|
||||||
|
instance.VIPConfigs[i].RequireDualStack = false
|
||||||
|
default:
|
||||||
|
instance.VIPConfigs[i].IsDualStack = false
|
||||||
|
instance.VIPConfigs[i].RequireDualStack = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strings.EqualFold(string(svc.Spec.IPFamilies[0]), utils.IPv4Family) {
|
||||||
|
instance.VIPConfigs[i].DHCPMode = strings.ToLower(utils.IPv4Family)
|
||||||
|
instance.VIPConfigs[i].DNSMode = strings.ToLower(utils.IPv4Family)
|
||||||
|
} else {
|
||||||
|
instance.VIPConfigs[i].DHCPMode = strings.ToLower(utils.IPv6Family)
|
||||||
|
instance.VIPConfigs[i].DNSMode = strings.ToLower(utils.IPv6Family)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := cluster.InitCluster(instance.VIPConfigs[i], false, intfMgr, arpMgr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to add Service %s/%s", svc.Namespace, svc.Name)
|
log.Error("failed to add service", "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +362,7 @@ func NewInstance(svc *v1.Service, config *kubevip.Config, intfMgr *networkinterf
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance.Clusters = append(instance.Clusters, c)
|
instance.Clusters = append(instance.Clusters, c)
|
||||||
log.Info("(svcs) adding VIP", "ip", vipConfig.VIP, "interface", vipConfig.Interface, "namespace", svc.Namespace, "name", svc.Name)
|
log.Info("(svcs) adding VIP", "ip", instance.VIPConfigs[i].VIP, "interface", instance.VIPConfigs[i].Interface, "namespace", svc.Namespace, "name", svc.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance, nil
|
return instance, nil
|
||||||
@@ -305,11 +425,11 @@ func getAutoInterfaceName(link netlink.Link, defaultInterface string) string {
|
|||||||
return link.Attrs().Name
|
return link.Attrs().Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) startDHCP() error {
|
func (i *Instance) startDHCP(ctx context.Context, index int) error {
|
||||||
if len(i.VIPConfigs) != 1 {
|
if len(i.VIPConfigs) > 2 {
|
||||||
return fmt.Errorf("DHCP requires exactly 1 VIP config, got: %v", len(i.VIPConfigs))
|
return fmt.Errorf("DHCP can be used with 2 VIP config maximally, got: %v", len(i.VIPConfigs))
|
||||||
}
|
}
|
||||||
parent, err := netlink.LinkByName(i.VIPConfigs[0].Interface)
|
parent, err := netlink.LinkByName(i.VIPConfigs[index].Interface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding VIP Interface, for building DHCP Link : %v", err)
|
return fmt.Errorf("error finding VIP Interface, for building DHCP Link : %v", err)
|
||||||
}
|
}
|
||||||
@@ -360,34 +480,61 @@ func (i *Instance) startDHCP() error {
|
|||||||
log.Info("Using existing macvlan interface for DHCP", "interface", interfaceName)
|
log.Info("Using existing macvlan interface for DHCP", "interface", interfaceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default rp_filter setting (https://github.com/kube-vip/kube-vip/issues/1170)
|
var initRebootFlag bool
|
||||||
rpfilterSetting := "0"
|
ip := net.ParseIP(i.VIPConfigs[index].VIP)
|
||||||
|
|
||||||
// Check if we need to set an override rp_filter value for the interface
|
var client vip.DHCPClient
|
||||||
if i.ServiceSnapshot.Annotations[kubevip.RPFilter] != "" {
|
if ip.To4() != nil {
|
||||||
// Check the rp_filter value
|
// Default rp_filter setting (https://github.com/kube-vip/kube-vip/issues/1170)
|
||||||
rpFilter, err := strconv.Atoi(i.ServiceSnapshot.Annotations[kubevip.RPFilter])
|
rpfilterSetting := "0"
|
||||||
if err != nil {
|
|
||||||
log.Error("[DHCP] unable to process rp_filter", "value", rpFilter)
|
// Check if we need to set an override rp_filter value for the interface
|
||||||
} else {
|
if i.ServiceSnapshot.Annotations[kubevip.RPFilter] != "" {
|
||||||
if rpFilter >= 0 && rpFilter < 3 { // Ensure the value is 0,1,2
|
// Check the rp_filter value
|
||||||
rpfilterSetting = i.ServiceSnapshot.Annotations[kubevip.RPFilter]
|
rpFilter, err := strconv.Atoi(i.ServiceSnapshot.Annotations[kubevip.RPFilter])
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[DHCP] unable to process rp_filter", "value", rpFilter)
|
||||||
} else {
|
} else {
|
||||||
log.Error("[DHCP] rp_filter value not within range 0-2", "value", rpFilter)
|
if rpFilter >= 0 && rpFilter < 3 { // Ensure the value is 0,1,2
|
||||||
|
rpfilterSetting = i.ServiceSnapshot.Annotations[kubevip.RPFilter]
|
||||||
|
} else {
|
||||||
|
log.Error("[DHCP] rp_filter value not within range 0-2", "value", rpFilter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = sysctl.WriteProcSys("/proc/sys/net/ipv4/conf/"+interfaceName+"/rp_filter", rpfilterSetting)
|
err = sysctl.WriteProcSys("/proc/sys/net/ipv4/conf/"+interfaceName+"/rp_filter", rpfilterSetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("[DHCP] unable to write rp_filter", "value", rpfilterSetting, "err", err)
|
log.Error("[DHCP] unable to write rp_filter", "value", rpfilterSetting, "err", err)
|
||||||
}
|
}
|
||||||
var initRebootFlag bool
|
|
||||||
if i.DHCPInterfaceIP != "" {
|
|
||||||
initRebootFlag = true
|
|
||||||
}
|
|
||||||
|
|
||||||
client := vip.NewDHCPClient(iface, initRebootFlag, i.DHCPInterfaceIP)
|
if i.DHCPInterfaceIPv4 != "" {
|
||||||
|
initRebootFlag = true
|
||||||
|
}
|
||||||
|
|
||||||
|
client = vip.NewDHCPv4Client(iface, initRebootFlag, i.DHCPInterfaceIPv4)
|
||||||
|
|
||||||
|
// Add the client so that we can call it to stop function
|
||||||
|
i.DHCPv4Client = client
|
||||||
|
|
||||||
|
// Set that DHCPv4 is enabled
|
||||||
|
i.IsDHCPv4 = true
|
||||||
|
} else {
|
||||||
|
if i.DHCPInterfaceIPv6 != "" {
|
||||||
|
initRebootFlag = true
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err = vip.NewDHCPv6Client(iface, parent, initRebootFlag, i.DHCPInterfaceIPv6)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the client so that we can call it to stop function
|
||||||
|
i.DHCPv6Client = client
|
||||||
|
|
||||||
|
// Set that DHCPv6 is enabled
|
||||||
|
i.IsDHCPv6 = true
|
||||||
|
}
|
||||||
|
|
||||||
// Add hostname to dhcp client if annotated
|
// Add hostname to dhcp client if annotated
|
||||||
if i.DHCPHostname != "" {
|
if i.DHCPHostname != "" {
|
||||||
@@ -395,15 +542,15 @@ func (i *Instance) startDHCP() error {
|
|||||||
client.WithHostName(i.DHCPHostname)
|
client.WithHostName(i.DHCPHostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
go client.Start()
|
go func() {
|
||||||
|
if err := client.Start(ctx); err != nil {
|
||||||
|
log.Error("[instance] DHCP client error: %w")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Set that DHCP is enabled
|
|
||||||
i.IsDHCP = true
|
|
||||||
// Set the name of the interface so that it can be removed on Service deletion
|
// Set the name of the interface so that it can be removed on Service deletion
|
||||||
i.DHCPInterface = interfaceName
|
i.DHCPInterface = interfaceName
|
||||||
i.DHCPInterfaceHwaddr = iface.HardwareAddr.String()
|
i.DHCPInterfaceHwaddr = iface.HardwareAddr.String()
|
||||||
// Add the client so that we can call it to stop function
|
|
||||||
i.DHCPClient = client
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,9 @@ const (
|
|||||||
|
|
||||||
RPFilter = "kube-vip.io/rp_filter" // Set the return path filter for a specific service interface
|
RPFilter = "kube-vip.io/rp_filter" // Set the return path filter for a specific service interface
|
||||||
|
|
||||||
|
// Name of the service lease object
|
||||||
ServiceLease = "kube-vip.io/leaseName"
|
ServiceLease = "kube-vip.io/leaseName"
|
||||||
|
|
||||||
|
// Enable DDNS for the service
|
||||||
|
ServiceDDNS = "kube-vip.io/ddns"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kube-vip/kube-vip/pkg/detector"
|
"github.com/kube-vip/kube-vip/pkg/detector"
|
||||||
|
"github.com/kube-vip/kube-vip/pkg/utils"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -379,6 +380,18 @@ func ParseEnvironment(c *Config) error {
|
|||||||
c.DNSMode = env
|
c.DNSMode = env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DHCP mode
|
||||||
|
env = os.Getenv(dhcpMode)
|
||||||
|
if env != "" {
|
||||||
|
c.DHCPMode = env
|
||||||
|
} else {
|
||||||
|
if c.DNSMode != "first" {
|
||||||
|
c.DHCPMode = c.DNSMode
|
||||||
|
} else {
|
||||||
|
c.DHCPMode = strings.ToLower(utils.IPv4Family)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Disable updates for services (status.LoadBalancer.Ingress will not be updated)
|
// Disable updates for services (status.LoadBalancer.Ingress will not be updated)
|
||||||
env = os.Getenv(disableServiceUpdates)
|
env = os.Getenv(disableServiceUpdates)
|
||||||
if env != "" {
|
if env != "" {
|
||||||
@@ -878,6 +891,11 @@ func mergeConfigValues(baseConfig, fileConfig *Config) {
|
|||||||
baseConfig.DNSMode = fileConfig.DNSMode
|
baseConfig.DNSMode = fileConfig.DNSMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DHCP configuration
|
||||||
|
if baseConfig.DHCPMode == "" && fileConfig.DHCPMode != "" {
|
||||||
|
baseConfig.DHCPMode = fileConfig.DHCPMode
|
||||||
|
}
|
||||||
|
|
||||||
// Health check configuration
|
// Health check configuration
|
||||||
if baseConfig.HealthCheckPort == 0 && fileConfig.HealthCheckPort != 0 {
|
if baseConfig.HealthCheckPort == 0 && fileConfig.HealthCheckPort != 0 {
|
||||||
baseConfig.HealthCheckPort = fileConfig.HealthCheckPort
|
baseConfig.HealthCheckPort = fileConfig.HealthCheckPort
|
||||||
|
|||||||
@@ -212,6 +212,9 @@ const (
|
|||||||
// dnsMode defines mode that DNS lookup will be performed with (first, ipv4, ipv6, dual)
|
// dnsMode defines mode that DNS lookup will be performed with (first, ipv4, ipv6, dual)
|
||||||
dnsMode = "dns_mode"
|
dnsMode = "dns_mode"
|
||||||
|
|
||||||
|
// dhcpMode defines mode that DHCP lookup will be performed with (ipv4, ipv6, dual)
|
||||||
|
dhcpMode = "dhcp_mode"
|
||||||
|
|
||||||
// disableServiceUpdates disables service updating
|
// disableServiceUpdates disables service updating
|
||||||
disableServiceUpdates = "disable_service_updates"
|
disableServiceUpdates = "disable_service_updates"
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,17 @@ func generatePodSpec(c *Config, image, imageVersion string, inCluster bool) *cor
|
|||||||
newEnvironment = append(newEnvironment, dnsModeSelector...)
|
newEnvironment = append(newEnvironment, dnsModeSelector...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.DHCPMode != "" {
|
||||||
|
// build environment variables
|
||||||
|
dhcpModeSelector := []corev1.EnvVar{
|
||||||
|
{
|
||||||
|
Name: dhcpMode,
|
||||||
|
Value: c.DHCPMode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
newEnvironment = append(newEnvironment, dhcpModeSelector...)
|
||||||
|
}
|
||||||
|
|
||||||
// If we're doing the hybrid mode
|
// If we're doing the hybrid mode
|
||||||
if c.EnableControlPlane {
|
if c.EnableControlPlane {
|
||||||
cp := []corev1.EnvVar{
|
cp := []corev1.EnvVar{
|
||||||
|
|||||||
@@ -158,6 +158,16 @@ type Config struct {
|
|||||||
// DNSMode, this will set the mode DSN lookup will be performed (first, ipv4, ipv6, dual)
|
// DNSMode, this will set the mode DSN lookup will be performed (first, ipv4, ipv6, dual)
|
||||||
DNSMode string `yaml:"dnsDualStackMode"`
|
DNSMode string `yaml:"dnsDualStackMode"`
|
||||||
|
|
||||||
|
// IsDualStack reports if service is DualStack.
|
||||||
|
IsDualStack bool
|
||||||
|
|
||||||
|
// RequireDualStack defines if DualStack is required for the service. Based on service's Spec.ipFamilyPolicy field.
|
||||||
|
RequireDualStack bool
|
||||||
|
|
||||||
|
// DNSMode, this will set the mode DHCP lookup will be performed for DDNS (ipv4, ipv6, dual). By default will be the same as DNSMode.
|
||||||
|
// If DNSMode is 'first', IPv4 will be used.
|
||||||
|
DHCPMode string `yaml:"dhcpDualStackMode"`
|
||||||
|
|
||||||
// DisableServiceUpdates, if true, kube-vip will only advertise service, but it will not update service's Status.LoadBalancer.Ingress slice
|
// DisableServiceUpdates, if true, kube-vip will only advertise service, but it will not update service's Status.LoadBalancer.Ingress slice
|
||||||
DisableServiceUpdates bool `yaml:"disableServiceUpdates"`
|
DisableServiceUpdates bool `yaml:"disableServiceUpdates"`
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/kube-vip/kube-vip/pkg/backend"
|
"github.com/kube-vip/kube-vip/pkg/backend"
|
||||||
"github.com/kube-vip/kube-vip/pkg/sysctl"
|
"github.com/kube-vip/kube-vip/pkg/sysctl"
|
||||||
"github.com/kube-vip/kube-vip/pkg/utils"
|
"github.com/kube-vip/kube-vip/pkg/utils"
|
||||||
|
"github.com/kube-vip/kube-vip/pkg/vip"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,9 +91,9 @@ func NewIPVSLB(address string, port uint16, forwardingMethod string, backendHeal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
netMask := netmask.MaskFrom(31, 32) // For ipv4
|
netMask := netmask.MaskFrom(31, vip.DefaultMaskIPv4) // For ipv4
|
||||||
if family == ipvs.INET6 {
|
if family == ipvs.INET6 {
|
||||||
netMask = netmask.MaskFrom(128, 128) // For ipv6
|
netMask = netmask.MaskFrom(128, vip.DefaultMaskIPv6) // For ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate out API Server LoadBalancer instance
|
// Generate out API Server LoadBalancer instance
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package networkinterface
|
package networkinterface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
log "log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
@@ -23,6 +24,12 @@ func NewManager() *Manager {
|
|||||||
|
|
||||||
func (m *Manager) Get(intf netlink.Link) *Link {
|
func (m *Manager) Get(intf netlink.Link) *Link {
|
||||||
if l, ok := m.interfaces[intf.Attrs().Name]; ok {
|
if l, ok := m.interfaces[intf.Attrs().Name]; ok {
|
||||||
|
updated, err := netlink.LinkByName(l.Intf.Attrs().Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to get interface %q: %w", l.Intf.Attrs().Name, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
l.Intf = updated
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
result := &Link{
|
result := &Link{
|
||||||
|
|||||||
@@ -269,9 +269,9 @@ func (p *Processor) configureEgress(vipIP, podIP, namespace, serviceUUID string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mask := "/32"
|
mask := fmt.Sprintf("/%d", vip.DefaultMaskIPv4)
|
||||||
if !utils.IsIPv4(podIP) {
|
if !utils.IsIPv4(podIP) {
|
||||||
mask = "/128"
|
mask = fmt.Sprintf("/%d", vip.DefaultMaskIPv6)
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowedNetworks != "" {
|
if allowedNetworks != "" {
|
||||||
|
|||||||
@@ -54,12 +54,6 @@ func (p *Processor) StartServicesLeaderElection(ctx context.Context, service *v1
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
// wait for the service context to end and delete the lease then
|
|
||||||
<-ctx.Done()
|
|
||||||
p.leaseMgr.Delete(service)
|
|
||||||
}()
|
|
||||||
|
|
||||||
svcCtx, err := p.getServiceContext(service.UID)
|
svcCtx, err := p.getServiceContext(service.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get context for service %q with UID %q: %w", service.Name, service.UID, err)
|
return fmt.Errorf("failed to get context for service %q with UID %q: %w", service.Name, service.UID, err)
|
||||||
@@ -70,6 +64,13 @@ func (p *Processor) StartServicesLeaderElection(ctx context.Context, service *v1
|
|||||||
|
|
||||||
svcCtx.IsActive = true
|
svcCtx.IsActive = true
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// wait for the service context to end and delete the lease then
|
||||||
|
<-svcCtx.Ctx.Done()
|
||||||
|
svcCtx.IsActive = false
|
||||||
|
p.leaseMgr.Delete(service)
|
||||||
|
}()
|
||||||
|
|
||||||
svcLease, isNew := p.leaseMgr.Add(service)
|
svcLease, isNew := p.leaseMgr.Add(service)
|
||||||
// this service is sharing lease
|
// this service is sharing lease
|
||||||
if !isNew {
|
if !isNew {
|
||||||
@@ -77,10 +78,8 @@ func (p *Processor) StartServicesLeaderElection(ctx context.Context, service *v1
|
|||||||
select {
|
select {
|
||||||
case <-svcLease.Started:
|
case <-svcLease.Started:
|
||||||
case <-svcLease.Ctx.Done():
|
case <-svcLease.Ctx.Done():
|
||||||
svcCtx.IsActive = false
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
<-svcLease.Started
|
|
||||||
|
|
||||||
if lease.UsesCommon(service) {
|
if lease.UsesCommon(service) {
|
||||||
if err := p.SyncServices(ctx, service); err != nil {
|
if err := p.SyncServices(ctx, service); err != nil {
|
||||||
@@ -88,18 +87,19 @@ func (p *Processor) StartServicesLeaderElection(ctx context.Context, service *v1
|
|||||||
svcLease.Cancel()
|
svcLease.Cancel()
|
||||||
}
|
}
|
||||||
// just block until context is cancelled
|
// just block until context is cancelled
|
||||||
<-ctx.Done()
|
<-svcCtx.Ctx.Done()
|
||||||
if svcCtx.IsActive {
|
if svcCtx.IsActive {
|
||||||
if err := p.deleteService(service.UID); err != nil {
|
if err := p.deleteService(service.UID); err != nil {
|
||||||
log.Error("service deletion", "err", err)
|
log.Error("service deletion", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// wait for leaderelection to be finished
|
// Mark this service is inactive
|
||||||
<-svcLease.Ctx.Done()
|
svcCtx.IsActive = false
|
||||||
// Mark this service is inactive
|
|
||||||
svcCtx.IsActive = false
|
// wait for leaderelection to be finished
|
||||||
|
<-svcLease.Ctx.Done()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// start the leader election code loop
|
// start the leader election code loop
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func (p *Processor) AddOrModify(ctx context.Context, event watch.Event, serviceF
|
|||||||
svcAddresses, svcHostnames := instance.FetchServiceAddresses(svc)
|
svcAddresses, svcHostnames := instance.FetchServiceAddresses(svc)
|
||||||
|
|
||||||
// We only care about LoadBalancer services that have been allocated an address
|
// We only care about LoadBalancer services that have been allocated an address
|
||||||
if len(svcAddresses) <= 0 {
|
if len(svcAddresses) <= 0 && len(svcHostnames) <= 0 {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,13 @@ func (p *Processor) AddOrModify(ctx context.Context, event watch.Event, serviceF
|
|||||||
// Service hostnames changed
|
// Service hostnames changed
|
||||||
!reflect.DeepEqual(originalServiceHostnames, svcHostnames) ||
|
!reflect.DeepEqual(originalServiceHostnames, svcHostnames) ||
|
||||||
// ExternalTrafficPolicy changed
|
// ExternalTrafficPolicy changed
|
||||||
svc.Spec.ExternalTrafficPolicy != i.ServiceSnapshot.Spec.ExternalTrafficPolicy
|
svc.Spec.ExternalTrafficPolicy != i.ServiceSnapshot.Spec.ExternalTrafficPolicy ||
|
||||||
|
// IP stack configuration changed
|
||||||
|
!reflect.DeepEqual(svc.Spec.IPFamilies, i.ServiceSnapshot.Spec.IPFamilies) ||
|
||||||
|
*svc.Spec.IPFamilyPolicy != *i.ServiceSnapshot.Spec.IPFamilyPolicy ||
|
||||||
|
// DDNS was disabled/enabled
|
||||||
|
svc.Annotations[kubevip.ServiceDDNS] != i.ServiceSnapshot.Annotations[kubevip.ServiceDDNS]
|
||||||
|
|
||||||
}
|
}
|
||||||
if shouldGarbageCollect {
|
if shouldGarbageCollect {
|
||||||
for _, addr := range svcAddresses {
|
for _, addr := range svcAddresses {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package services
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -86,16 +87,30 @@ func (p *Processor) getServiceInstanceAction(svc *v1.Service) ServiceInstanceAct
|
|||||||
if instance != nil && instance.ServiceSnapshot.UID == svc.UID {
|
if instance != nil && instance.ServiceSnapshot.UID == svc.UID {
|
||||||
for _, address := range addresses {
|
for _, address := range addresses {
|
||||||
// handle the case where the service instance needs to be deleted
|
// handle the case where the service instance needs to be deleted
|
||||||
if instance.IsDHCP {
|
if instance.IsDHCPv4 {
|
||||||
if address != "0.0.0.0" && address != "::" {
|
if address != "0.0.0.0" {
|
||||||
return ActionDelete
|
return ActionDelete
|
||||||
}
|
}
|
||||||
if len(svc.Status.LoadBalancer.Ingress) > 0 && !slices.Contains(statusAddresses, instance.DHCPInterfaceIP) {
|
if len(svc.Status.LoadBalancer.Ingress) > 0 && !slices.Contains(statusAddresses, instance.DHCPInterfaceIPv4) {
|
||||||
|
return ActionDelete
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if address == "0.0.0.0" {
|
||||||
|
return ActionDelete
|
||||||
|
}
|
||||||
|
if len(svc.Status.LoadBalancer.Ingress) > 0 && !slices.Contains(statusAddresses, address) {
|
||||||
return ActionDelete
|
return ActionDelete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !instance.IsDHCP {
|
if instance.IsDHCPv6 {
|
||||||
if address == "0.0.0.0" || address == "::" {
|
if address != "::" {
|
||||||
|
return ActionDelete
|
||||||
|
}
|
||||||
|
if len(svc.Status.LoadBalancer.Ingress) > 0 && !slices.Contains(statusAddresses, instance.DHCPInterfaceIPv6) {
|
||||||
|
return ActionDelete
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if address == "::" {
|
||||||
return ActionDelete
|
return ActionDelete
|
||||||
}
|
}
|
||||||
if len(svc.Status.LoadBalancer.Ingress) > 0 && !slices.Contains(statusAddresses, address) {
|
if len(svc.Status.LoadBalancer.Ingress) > 0 && !slices.Contains(statusAddresses, address) {
|
||||||
@@ -140,7 +155,7 @@ func (p *Processor) addService(ctx context.Context, svc *v1.Service) error {
|
|||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
newService, err := instance.NewInstance(svc, p.config, p.intfMgr, p.arpMgr)
|
newService, err := instance.NewInstance(ctx, svc, p.config, p.intfMgr, p.arpMgr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -152,19 +167,60 @@ func (p *Processor) addService(ctx context.Context, svc *v1.Service) error {
|
|||||||
|
|
||||||
p.upnpMap(ctx, newService)
|
p.upnpMap(ctx, newService)
|
||||||
|
|
||||||
if newService.IsDHCP && len(newService.VIPConfigs) == 1 {
|
if newService.IsDHCPv4 {
|
||||||
go func() {
|
go func() {
|
||||||
for ip := range newService.DHCPClient.IPChannel() {
|
index := -1
|
||||||
log.Debug("IP changed", "ip", ip)
|
for i := range newService.VIPConfigs {
|
||||||
newService.VIPConfigs[0].VIP = ip
|
ip := net.ParseIP(newService.VIPConfigs[i].VIP)
|
||||||
newService.DHCPInterfaceIP = ip
|
if ip.To4() != nil {
|
||||||
if !p.config.DisableServiceUpdates {
|
index = i
|
||||||
if err := p.updateStatus(newService); err != nil {
|
break
|
||||||
log.Warn("updating svc", "err", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug("IP update channel closed, stopping")
|
if index == -1 {
|
||||||
|
log.Error("unable to find proper VIPConfig for the DHCPv4")
|
||||||
|
} else {
|
||||||
|
for ip := range newService.DHCPv4Client.IPChannel() {
|
||||||
|
log.Debug("IP changed", "ip", ip)
|
||||||
|
newService.VIPConfigs[index].VIP = ip
|
||||||
|
newService.DHCPInterfaceIPv4 = ip
|
||||||
|
if !p.config.DisableServiceUpdates {
|
||||||
|
if err := p.updateStatus(newService); err != nil {
|
||||||
|
log.Warn("updating svc", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debug("IPv4 update channel closed, stopping")
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if newService.IsDHCPv6 {
|
||||||
|
go func() {
|
||||||
|
index := -1
|
||||||
|
for i := range newService.VIPConfigs {
|
||||||
|
ip := net.ParseIP(newService.VIPConfigs[i].VIP)
|
||||||
|
if ip.To4() == nil {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if index == -1 {
|
||||||
|
log.Error("unable to find proper VIPConfig for the DHCPv6")
|
||||||
|
} else {
|
||||||
|
for ip := range newService.DHCPv4Client.IPChannel() {
|
||||||
|
log.Debug("IP changed", "ip", ip)
|
||||||
|
newService.VIPConfigs[index].VIP = ip
|
||||||
|
newService.DHCPInterfaceIPv6 = ip
|
||||||
|
if !p.config.DisableServiceUpdates {
|
||||||
|
if err := p.updateStatus(newService); err != nil {
|
||||||
|
log.Warn("updating svc", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debug("IPv6 update channel closed, stopping")
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +344,7 @@ func (p *Processor) deleteService(uid types.UID) error {
|
|||||||
if !found {
|
if !found {
|
||||||
// TODO: - fix UX
|
// TODO: - fix UX
|
||||||
// return fmt.Errorf("unable to find/stop service [%s]", uid)
|
// return fmt.Errorf("unable to find/stop service [%s]", uid)
|
||||||
log.Warn("unable to find/stop service", "uid", uid)
|
log.Error("unable to find/stop service", "uid", uid)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,8 +373,15 @@ func (p *Processor) deleteService(uid types.UID) error {
|
|||||||
for x := range serviceInstance.Clusters {
|
for x := range serviceInstance.Clusters {
|
||||||
serviceInstance.Clusters[x].Stop()
|
serviceInstance.Clusters[x].Stop()
|
||||||
}
|
}
|
||||||
if serviceInstance.IsDHCP {
|
if serviceInstance.IsDHCPv4 || serviceInstance.IsDHCPv6 {
|
||||||
serviceInstance.DHCPClient.Stop()
|
if serviceInstance.IsDHCPv4 {
|
||||||
|
serviceInstance.DHCPv4Client.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceInstance.IsDHCPv6 {
|
||||||
|
serviceInstance.DHCPv6Client.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
macvlan, err := netlink.LinkByName(serviceInstance.DHCPInterface)
|
macvlan, err := netlink.LinkByName(serviceInstance.DHCPInterface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("[service] error finding VIP Interface: %v", err)
|
return fmt.Errorf("[service] error finding VIP Interface: %v", err)
|
||||||
@@ -508,6 +571,7 @@ func (p *Processor) upnpMap(ctx context.Context, s *instance.Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Processor) updateStatus(i *instance.Instance) error {
|
func (p *Processor) updateStatus(i *instance.Instance) error {
|
||||||
|
log.Debug("updating status")
|
||||||
// let's retry status update every 10ms for 30s
|
// let's retry status update every 10ms for 30s
|
||||||
retryConfig := wait.Backoff{
|
retryConfig := wait.Backoff{
|
||||||
Steps: 3000,
|
Steps: 3000,
|
||||||
@@ -534,9 +598,19 @@ func (p *Processor) updateStatus(i *instance.Instance) error {
|
|||||||
// Add the current host
|
// Add the current host
|
||||||
currentServiceCopy.Annotations[kubevip.VipHost] = p.config.NodeName
|
currentServiceCopy.Annotations[kubevip.VipHost] = p.config.NodeName
|
||||||
}
|
}
|
||||||
if i.DHCPInterfaceHwaddr != "" || i.DHCPInterfaceIP != "" {
|
if i.DHCPInterfaceHwaddr != "" || i.DHCPInterfaceIPv4 != "" || i.DHCPInterfaceIPv6 != "" {
|
||||||
currentServiceCopy.Annotations[kubevip.HwAddrKey] = i.DHCPInterfaceHwaddr
|
currentServiceCopy.Annotations[kubevip.HwAddrKey] = i.DHCPInterfaceHwaddr
|
||||||
currentServiceCopy.Annotations[kubevip.RequestedIP] = i.DHCPInterfaceIP
|
dhcpInterfaceIP := ""
|
||||||
|
if i.DHCPInterfaceIPv4 != "" {
|
||||||
|
dhcpInterfaceIP = i.DHCPInterfaceIPv4
|
||||||
|
if i.DHCPInterfaceIPv6 != "" {
|
||||||
|
dhcpInterfaceIP += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i.DHCPInterfaceIPv6 != "" {
|
||||||
|
dhcpInterfaceIP += i.DHCPInterfaceIPv6
|
||||||
|
}
|
||||||
|
currentServiceCopy.Annotations[kubevip.RequestedIP] = dhcpInterfaceIP
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentService.Annotations["development.kube-vip.io/synthetic-api-server-error-on-update"] == "true" {
|
if currentService.Annotations["development.kube-vip.io/synthetic-api-server-error-on-update"] == "true" {
|
||||||
@@ -564,7 +638,7 @@ func (p *Processor) updateStatus(i *instance.Instance) error {
|
|||||||
|
|
||||||
for _, c := range i.VIPConfigs {
|
for _, c := range i.VIPConfigs {
|
||||||
if !utils.IsIP(c.VIP) {
|
if !utils.IsIP(c.VIP) {
|
||||||
ips, err := utils.LookupHost(c.VIP, p.config.DNSMode)
|
ips, err := utils.LookupHost(c.VIP, c.DNSMode, *i.ServiceSnapshot.Spec.IPFamilyPolicy == v1.IPFamilyPolicyRequireDualStack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// LookupHost resolves dnsName and return an IP or an error
|
// LookupHost resolves dnsName and return an IP or an error
|
||||||
func LookupHost(dnsName, dnsMode string) ([]string, error) {
|
func LookupHost(dnsName, dnsMode string, requireDualStack bool) ([]string, error) {
|
||||||
result, err := net.LookupHost(dnsName)
|
result, err := net.LookupHost(dnsName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -26,7 +27,7 @@ func LookupHost(dnsName, dnsMode string) ([]string, error) {
|
|||||||
addrs := []string{}
|
addrs := []string{}
|
||||||
switch dnsMode {
|
switch dnsMode {
|
||||||
case strings.ToLower(IPv4Family), strings.ToLower(IPv6Family), DualFamily:
|
case strings.ToLower(IPv4Family), strings.ToLower(IPv6Family), DualFamily:
|
||||||
a, err := getIPbyFamily(result, dnsMode)
|
a, err := getIPbyFamily(result, dnsMode, requireDualStack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -38,7 +39,7 @@ func LookupHost(dnsName, dnsMode string) ([]string, error) {
|
|||||||
return addrs, nil
|
return addrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIPbyFamily(addresses []string, family string) ([]string, error) {
|
func getIPbyFamily(addresses []string, family string, requireDualStack bool) ([]string, error) {
|
||||||
var checkers []func(string) bool
|
var checkers []func(string) bool
|
||||||
families := []string{}
|
families := []string{}
|
||||||
if family == DualFamily || family == strings.ToLower(IPv4Family) {
|
if family == DualFamily || family == strings.ToLower(IPv4Family) {
|
||||||
@@ -54,11 +55,19 @@ func getIPbyFamily(addresses []string, family string) ([]string, error) {
|
|||||||
for i, c := range checkers {
|
for i, c := range checkers {
|
||||||
addr, err := getIPbyChecker(addresses, c)
|
addr, err := getIPbyChecker(addresses, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if len(checkers) > 1 && !requireDualStack {
|
||||||
|
slog.Warn("no address found", "family", families[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("error getting %s address: %w", families[i], err)
|
return nil, fmt.Errorf("error getting %s address: %w", families[i], err)
|
||||||
}
|
}
|
||||||
addrs = append(addrs, addr)
|
addrs = append(addrs, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no addresses found")
|
||||||
|
}
|
||||||
|
|
||||||
return addrs, nil
|
return addrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
"github.com/kube-vip/kube-vip/pkg/iptables"
|
iptables "github.com/kube-vip/kube-vip/pkg/iptables"
|
||||||
"github.com/kube-vip/kube-vip/pkg/kubevip"
|
"github.com/kube-vip/kube-vip/pkg/kubevip"
|
||||||
"github.com/kube-vip/kube-vip/pkg/utils"
|
"github.com/kube-vip/kube-vip/pkg/utils"
|
||||||
|
|
||||||
@@ -28,8 +28,9 @@ const (
|
|||||||
defaultValidLft = 60
|
defaultValidLft = 60
|
||||||
iptablesComment = "%s kube-vip load balancer IP"
|
iptablesComment = "%s kube-vip load balancer IP"
|
||||||
iptablesCommentMarkRule = "kube-vip load balancer IP set mark for masquerade"
|
iptablesCommentMarkRule = "kube-vip load balancer IP set mark for masquerade"
|
||||||
defaultMaskIPv6 = 128
|
|
||||||
defaultMaskIPv4 = 32
|
DefaultMaskIPv4 = 32
|
||||||
|
DefaultMaskIPv6 = 128
|
||||||
)
|
)
|
||||||
|
|
||||||
// Network is an interface that enable managing operations for a given IP
|
// Network is an interface that enable managing operations for a given IP
|
||||||
@@ -56,6 +57,8 @@ type Network interface {
|
|||||||
SetHasEndpoints(value bool)
|
SetHasEndpoints(value bool)
|
||||||
HasEndpoints() bool
|
HasEndpoints() bool
|
||||||
ARPName() string
|
ARPName() string
|
||||||
|
GetPossibleSubnets() string
|
||||||
|
DHCPFamily() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// network - This allows network configuration
|
// network - This allows network configuration
|
||||||
@@ -82,12 +85,18 @@ type network struct {
|
|||||||
ipvsEnabled bool
|
ipvsEnabled bool
|
||||||
|
|
||||||
hasEndpoints bool
|
hasEndpoints bool
|
||||||
|
|
||||||
|
possibleSubnets string
|
||||||
|
|
||||||
|
// used by DHCP to get address of proper family
|
||||||
|
dhcpFamily string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig will attempt to provide an interface to the kernel network configuration
|
// NewConfig will attempt to provide an interface to the kernel network configuration
|
||||||
func NewConfig(address string, iface string, loGlobalScope bool, subnet string, isDDNS bool, tableID int, tableType int,
|
func NewConfig(address string, iface string, loGlobalScope bool, subnet string, isDDNS bool,
|
||||||
routingProtocol int, dnsMode, forwardMethod, iptablesBackend string,
|
dhcpMode string, requireDualStack, isDualStack bool, tableID int, tableType int, routingProtocol int,
|
||||||
ipvsEnabled, enableSecurity bool, intfMgr *networkinterface.Manager) ([]Network, error) {
|
dnsMode, forwardMethod, iptablesBackend string, ipvsEnabled, enableSecurity bool,
|
||||||
|
intfMgr *networkinterface.Manager) ([]Network, error) {
|
||||||
networks := []Network{}
|
networks := []Network{}
|
||||||
|
|
||||||
link, err := netlink.LinkByName(iface)
|
link, err := netlink.LinkByName(iface)
|
||||||
@@ -106,6 +115,7 @@ func NewConfig(address string, iface string, loGlobalScope bool, subnet string,
|
|||||||
forwardMethod: forwardMethod,
|
forwardMethod: forwardMethod,
|
||||||
iptablesBackend: iptablesBackend,
|
iptablesBackend: iptablesBackend,
|
||||||
ipvsEnabled: ipvsEnabled,
|
ipvsEnabled: ipvsEnabled,
|
||||||
|
possibleSubnets: subnet,
|
||||||
}
|
}
|
||||||
|
|
||||||
subnet, err = SelectSubnet(address, subnet)
|
subnet, err = SelectSubnet(address, subnet)
|
||||||
@@ -138,35 +148,50 @@ func NewConfig(address string, iface string, loGlobalScope bool, subnet string,
|
|||||||
} else {
|
} else {
|
||||||
// try to resolve the address
|
// try to resolve the address
|
||||||
log.Debug("looking up host", "address", address, "dnsMode", dnsMode)
|
log.Debug("looking up host", "address", address, "dnsMode", dnsMode)
|
||||||
ips, err := utils.LookupHost(address, dnsMode)
|
ips, err := utils.LookupHost(address, dnsMode, requireDualStack)
|
||||||
if err != nil {
|
if (dnsMode == utils.DualFamily && isDDNS && isDualStack) || err != nil {
|
||||||
// return early for ddns if no IP is allocated for the domain
|
// return early for ddns if no IP is allocated for the domain
|
||||||
// when leader starts, should do get IP from DHCP for the domain
|
// when leader starts, should do get IP from DHCP for the domain
|
||||||
if isDDNS {
|
if isDDNS {
|
||||||
result := &network{
|
log.Info("isDDNS true", "dhcpMode", dhcpMode)
|
||||||
link: networkLink,
|
if strings.EqualFold(dhcpMode, utils.IPv4Family) || strings.EqualFold(dhcpMode, utils.DualFamily) {
|
||||||
routeTable: tableID,
|
result := &network{
|
||||||
routingTableType: tableType,
|
link: networkLink,
|
||||||
routingProtocol: routingProtocol,
|
routeTable: tableID,
|
||||||
forwardMethod: forwardMethod,
|
routingTableType: tableType,
|
||||||
iptablesBackend: iptablesBackend,
|
routingProtocol: routingProtocol,
|
||||||
isDDNS: isDDNS,
|
forwardMethod: forwardMethod,
|
||||||
dnsName: address,
|
iptablesBackend: iptablesBackend,
|
||||||
ipvsEnabled: ipvsEnabled,
|
isDDNS: isDDNS,
|
||||||
enableSecurity: enableSecurity,
|
dnsName: address,
|
||||||
address: &netlink.Addr{ // create placeholder for the address
|
ipvsEnabled: ipvsEnabled,
|
||||||
IPNet: &net.IPNet{}, // that will be added later in the process
|
enableSecurity: enableSecurity,
|
||||||
Peer: &net.IPNet{},
|
possibleSubnets: subnet,
|
||||||
},
|
dhcpFamily: utils.IPv4Family,
|
||||||
|
}
|
||||||
|
|
||||||
|
networks = append(networks, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set address as deprecated so it isn't used as source address according to RFC 3484
|
if strings.EqualFold(dhcpMode, utils.IPv6Family) || strings.EqualFold(dhcpMode, utils.DualFamily) {
|
||||||
result.address.PreferedLft = 0
|
result := &network{
|
||||||
|
link: networkLink,
|
||||||
|
routeTable: tableID,
|
||||||
|
routingTableType: tableType,
|
||||||
|
routingProtocol: routingProtocol,
|
||||||
|
forwardMethod: forwardMethod,
|
||||||
|
iptablesBackend: iptablesBackend,
|
||||||
|
isDDNS: isDDNS,
|
||||||
|
dnsName: address,
|
||||||
|
ipvsEnabled: ipvsEnabled,
|
||||||
|
enableSecurity: enableSecurity,
|
||||||
|
possibleSubnets: subnet,
|
||||||
|
dhcpFamily: utils.IPv6Family,
|
||||||
|
}
|
||||||
|
|
||||||
// Also set ValidLft so the netlink library actually sets them
|
networks = append(networks, result)
|
||||||
result.address.ValidLft = math.MaxInt
|
}
|
||||||
|
|
||||||
networks = append(networks, result)
|
|
||||||
return networks, nil
|
return networks, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -184,15 +209,12 @@ func NewConfig(address string, iface string, loGlobalScope bool, subnet string,
|
|||||||
dnsName: address,
|
dnsName: address,
|
||||||
ipvsEnabled: ipvsEnabled,
|
ipvsEnabled: ipvsEnabled,
|
||||||
enableSecurity: enableSecurity,
|
enableSecurity: enableSecurity,
|
||||||
|
possibleSubnets: subnet,
|
||||||
}
|
}
|
||||||
|
|
||||||
// we're able to resolve store this as the initial IP
|
s, err := SelectSubnet(ip, subnet)
|
||||||
|
if err != nil {
|
||||||
subnets := Split(subnet)
|
return nil, fmt.Errorf("failed to select subnet: %w", err)
|
||||||
|
|
||||||
s := subnet
|
|
||||||
if len(subnets) > 1 {
|
|
||||||
s = selectSubnet(ip, subnets)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.address, err = netlink.ParseAddr(fmt.Sprintf("%s/%s", ip, s)); err != nil {
|
if result.address, err = netlink.ParseAddr(fmt.Sprintf("%s/%s", ip, s)); err != nil {
|
||||||
@@ -204,6 +226,11 @@ func NewConfig(address string, iface string, loGlobalScope bool, subnet string,
|
|||||||
// set address as deprecated so it isn't used as source address according to RFC 3484
|
// set address as deprecated so it isn't used as source address according to RFC 3484
|
||||||
result.address.PreferedLft = 0
|
result.address.PreferedLft = 0
|
||||||
|
|
||||||
|
result.dhcpFamily = strings.ToLower(utils.IPv6Family)
|
||||||
|
if net.ParseIP(ip).To4() != nil {
|
||||||
|
result.dhcpFamily = strings.ToLower(utils.IPv4Family)
|
||||||
|
}
|
||||||
|
|
||||||
networks = append(networks, result)
|
networks = append(networks, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +383,7 @@ func (configurator *network) AddIP(precheck bool, skipDAD bool) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := netlink.AddrReplace(configurator.link.Intf, configurator.address); err != nil {
|
if err := netlink.AddrReplace(configurator.link.Intf, configurator.address); err != nil {
|
||||||
return false, errors.Wrap(err, "could not add ip")
|
return false, errors.Wrap(err, fmt.Sprintf("could not add ip to device %q", configurator.link.Intf.Attrs().Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := configurator.configureIPTables(); err != nil {
|
if err := configurator.configureIPTables(); err != nil {
|
||||||
@@ -675,6 +702,25 @@ func (configurator *network) SetIP(ip string) error {
|
|||||||
if strings.Contains("/", ip) {
|
if strings.Contains("/", ip) {
|
||||||
return fmt.Errorf("ip should not contain CIDR notation got: %s", ip)
|
return fmt.Errorf("ip should not contain CIDR notation got: %s", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if configurator.address == nil {
|
||||||
|
log.Debug("possible", "subnets", configurator.possibleSubnets)
|
||||||
|
subnet, err := SelectSubnet(ip, configurator.possibleSubnets)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to select subnet for IP %q from %q: %w", ip, subnet, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the subnet needs overriding
|
||||||
|
cidr, err := utils.FormatIPWithSubnetMask(ip, subnet)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "2 could not format address %q with subnetMask %q", ip, subnet)
|
||||||
|
}
|
||||||
|
configurator.address, err = netlink.ParseAddr(cidr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not parse address %q", cidr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ones, _ := configurator.address.Mask.Size()
|
ones, _ := configurator.address.Mask.Size()
|
||||||
cidr, err := utils.FormatIPWithSubnetMask(ip, strconv.Itoa(ones))
|
cidr, err := utils.FormatIPWithSubnetMask(ip, strconv.Itoa(ones))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -804,11 +850,17 @@ func (configurator *network) SetMask(mask string) error {
|
|||||||
selectedMask := mask
|
selectedMask := mask
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
if mask == "" {
|
||||||
|
return fmt.Errorf("no mask provided")
|
||||||
|
}
|
||||||
|
|
||||||
if configurator.IP() != "" {
|
if configurator.IP() != "" {
|
||||||
selectedMask, err = SelectSubnet(configurator.IP(), mask)
|
selectedMask, err = SelectSubnet(configurator.IP(), mask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to select mask %q: %w", mask, err)
|
return fmt.Errorf("failed to select mask %q: %w", mask, err)
|
||||||
}
|
}
|
||||||
|
} else if len(strings.Split(mask, ",")) > 1 {
|
||||||
|
return fmt.Errorf("cannot select mask from %q when IP address is unknown", mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := strconv.Atoi(selectedMask)
|
m, err := strconv.Atoi(selectedMask)
|
||||||
@@ -816,19 +868,18 @@ func (configurator *network) SetMask(mask string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
size := defaultMaskIPv4
|
size := DefaultMaskIPv4
|
||||||
family := utils.IPv4Family
|
family := utils.IPv4Family
|
||||||
|
|
||||||
if configurator.IP() != "" {
|
if configurator.IP() != "" {
|
||||||
if utils.IsIPv6(configurator.IP()) {
|
if utils.IsIPv6(configurator.IP()) {
|
||||||
size = defaultMaskIPv6
|
size = DefaultMaskIPv6
|
||||||
family = utils.IPv6Family
|
family = utils.IPv6Family
|
||||||
}
|
}
|
||||||
|
|
||||||
if m > size {
|
if m > size {
|
||||||
return fmt.Errorf("provided CIDR mask '%d' is greater than the highest mask value for the %s family (%d)", m, family, size)
|
return fmt.Errorf("provided CIDR mask '%d' is greater than the highest mask value for the %s family (%d)", m, family, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toSet := net.CIDRMask(m, size)
|
toSet := net.CIDRMask(m, size)
|
||||||
@@ -857,6 +908,14 @@ func (configurator *network) ARPName() string {
|
|||||||
return fmt.Sprintf("%s-%s", configurator.CIDR(), configurator.Interface())
|
return fmt.Sprintf("%s-%s", configurator.CIDR(), configurator.Interface())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (configurator *network) GetPossibleSubnets() string {
|
||||||
|
return configurator.possibleSubnets
|
||||||
|
}
|
||||||
|
|
||||||
|
func (configurator *network) DHCPFamily() string {
|
||||||
|
return configurator.dhcpFamily
|
||||||
|
}
|
||||||
|
|
||||||
// SelectSubnet formats an IP address with the appropriate CIDR based on the input.
|
// SelectSubnet formats an IP address with the appropriate CIDR based on the input.
|
||||||
// The input SubnetMasks can be "32,128" (dual-stack), "32", "128" (SingleStack).
|
// The input SubnetMasks can be "32,128" (dual-stack), "32", "128" (SingleStack).
|
||||||
func SelectSubnet(rawIP string, subnetMasks string) (string, error) {
|
func SelectSubnet(rawIP string, subnetMasks string) (string, error) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package vip
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "log/slog"
|
log "log/slog"
|
||||||
@@ -14,42 +14,41 @@ import (
|
|||||||
// for the dDNSHostName
|
// for the dDNSHostName
|
||||||
// will return the IP allocated
|
// will return the IP allocated
|
||||||
type DDNSManager interface {
|
type DDNSManager interface {
|
||||||
Start() (string, error)
|
Start(ctx context.Context) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ddnsManager struct {
|
type ddnsManager struct {
|
||||||
ctx context.Context
|
|
||||||
network Network
|
network Network
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDDNSManager returns a newly created Dynamic DNS manager
|
// NewDDNSManager returns a newly created Dynamic DNS manager
|
||||||
func NewDDNSManager(ctx context.Context, network Network) DDNSManager {
|
func NewDDNSManager(network Network) DDNSManager {
|
||||||
return &ddnsManager{
|
return &ddnsManager{
|
||||||
ctx: ctx,
|
|
||||||
network: network,
|
network: network,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start will start the dhcpclient routine to keep the lease
|
// Start will start the dhcpclient routine to keep the lease
|
||||||
// and return the IP it got from DHCP
|
// and return the IP it got from DHCP
|
||||||
func (ddns *ddnsManager) Start() (string, error) {
|
func (ddns *ddnsManager) Start(ctx context.Context) (string, error) {
|
||||||
interfaceName := ddns.network.Interface()
|
client, err := NewDHCPClient(ddns.network)
|
||||||
iface, err := net.InterfaceByName(interfaceName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("unable to create DHCP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := NewDHCPClient(iface, false, "")
|
|
||||||
|
|
||||||
client.WithHostName(ddns.network.DDNSHostName())
|
client.WithHostName(ddns.network.DDNSHostName())
|
||||||
|
|
||||||
go client.Start()
|
go func() {
|
||||||
|
if err := client.Start(ctx); err != nil {
|
||||||
|
log.Error("[ddns] DHCP client error: %w")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
log.Info("waiting for ip from dhcp")
|
log.Info("waiting for ip from dhcp")
|
||||||
ip, timeout := "", time.After(1*time.Minute)
|
ip, timeout := "", time.After(1*time.Minute)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ddns.ctx.Done():
|
case <-ctx.Done():
|
||||||
client.Stop()
|
client.Stop()
|
||||||
return "", errors.New("context cancelled")
|
return "", errors.New("context cancelled")
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
@@ -78,7 +77,7 @@ func (ddns *ddnsManager) Start() (string, error) {
|
|||||||
log.Info("got address from dhcp", "ip", ip)
|
log.Info("got address from dhcp", "ip", ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(ddns.ctx)
|
}(ctx)
|
||||||
|
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
|
|||||||
290
pkg/vip/dhcp.go
290
pkg/vip/dhcp.go
@@ -1,289 +1,39 @@
|
|||||||
package vip
|
package vip
|
||||||
|
|
||||||
// DHCP client implementation that refers to https://www.rfc-editor.org/rfc/rfc2131.html
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"strings"
|
||||||
|
|
||||||
log "log/slog"
|
"github.com/kube-vip/kube-vip/pkg/utils"
|
||||||
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
|
||||||
"github.com/jpillora/backoff"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const dhcpClientPort = "68"
|
type DHCPClient interface {
|
||||||
const defaultDHCPRenew = time.Hour
|
ErrorChannel() chan error
|
||||||
const maxBackoffAttempts = 3
|
IPChannel() chan string
|
||||||
|
Start(ctx context.Context) error
|
||||||
// DHCPClient is responsible for maintaining ipv4 lease for one specified interface
|
Stop()
|
||||||
type DHCPClient struct {
|
WithHostName(hostname string) DHCPClient
|
||||||
iface *net.Interface
|
|
||||||
ddnsHostName string
|
|
||||||
lease *nclient4.Lease
|
|
||||||
initRebootFlag bool
|
|
||||||
requestedIP net.IP
|
|
||||||
stopChan chan struct{} // used as a signal to release the IP and stop the dhcp client daemon
|
|
||||||
releasedChan chan struct{} // indicate that the IP has been released
|
|
||||||
errorChan chan error // indicates there was an error on the IP request
|
|
||||||
ipChan chan string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDHCPClient returns a new DHCP Client.
|
func NewDHCPClient(network Network) (DHCPClient, error) {
|
||||||
func NewDHCPClient(iface *net.Interface, initRebootFlag bool, requestedIP string) *DHCPClient {
|
interfaceName := network.Interface()
|
||||||
return &DHCPClient{
|
iface, err := net.InterfaceByName(interfaceName)
|
||||||
iface: iface,
|
if err != nil {
|
||||||
stopChan: make(chan struct{}),
|
return nil, fmt.Errorf("failed to get interface %q: %w", interfaceName, err)
|
||||||
releasedChan: make(chan struct{}),
|
|
||||||
errorChan: make(chan error),
|
|
||||||
initRebootFlag: initRebootFlag,
|
|
||||||
requestedIP: net.ParseIP(requestedIP),
|
|
||||||
ipChan: make(chan string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DHCPClient) WithHostName(hostname string) *DHCPClient {
|
|
||||||
c.ddnsHostName = hostname
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop state-transition process and close dhcp client
|
|
||||||
func (c *DHCPClient) Stop() {
|
|
||||||
close(c.ipChan)
|
|
||||||
close(c.stopChan)
|
|
||||||
<-c.releasedChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the IPChannel for consumption
|
|
||||||
func (c *DHCPClient) IPChannel() chan string {
|
|
||||||
return c.ipChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the ErrorChannel for consumption
|
|
||||||
func (c *DHCPClient) ErrorChannel() chan error {
|
|
||||||
return c.errorChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start state-transition process of dhcp client
|
|
||||||
//
|
|
||||||
// -------- -------
|
|
||||||
//
|
|
||||||
// | | +-------------------------->| |<-------------------+
|
|
||||||
// | INIT- | | +-------------------->| INIT | |
|
|
||||||
// | REBOOT |DHCPNAK/ +---------->| |<---+ |
|
|
||||||
// | |Restart| | ------- | |
|
|
||||||
//
|
|
||||||
// -------- | DHCPNAK/ | | |
|
|
||||||
// | Discard offer | -/Send DHCPDISCOVER |
|
|
||||||
//
|
|
||||||
// -/Send DHCPREQUEST | | |
|
|
||||||
//
|
|
||||||
// | | | DHCPACK v | |
|
|
||||||
// ----------- | (not accept.)/ ----------- | |
|
|
||||||
//
|
|
||||||
// | | | Send DHCPDECLINE | | |
|
|
||||||
// | REBOOTING | | | | SELECTING |<----+ |
|
|
||||||
// | | | / | | |DHCPOFFER/ |
|
|
||||||
//
|
|
||||||
// ----------- | / ----------- | |Collect |
|
|
||||||
// | | / | | | replies |
|
|
||||||
//
|
|
||||||
// DHCPACK/ | / +----------------+ +-------+ |
|
|
||||||
// Record lease, set| | v Select offer/ |
|
|
||||||
// timers T1, T2 ------------ send DHCPREQUEST | |
|
|
||||||
//
|
|
||||||
// | +----->| | DHCPNAK, Lease expired/ |
|
|
||||||
// | | | REQUESTING | Halt network |
|
|
||||||
// DHCPOFFER/ | | | |
|
|
||||||
// Discard ------------ | |
|
|
||||||
// | | | | ----------- |
|
|
||||||
// | +--------+ DHCPACK/ | | |
|
|
||||||
// | Record lease, set -----| REBINDING | |
|
|
||||||
// | timers T1, T2 / | | |
|
|
||||||
// | | DHCPACK/ ----------- |
|
|
||||||
// | v Record lease, set ^ |
|
|
||||||
// +----------------> ------- /timers T1,T2 | |
|
|
||||||
// +----->| |<---+ | |
|
|
||||||
// | | BOUND |<---+ | |
|
|
||||||
// DHCPOFFER, DHCPACK, | | | T2 expires/ DHCPNAK/
|
|
||||||
// DHCPNAK/Discard ------- | Broadcast Halt network
|
|
||||||
// | | | | DHCPREQUEST |
|
|
||||||
// +-------+ | DHCPACK/ | |
|
|
||||||
// T1 expires/ Record lease, set | |
|
|
||||||
// Send DHCPREQUEST timers T1, T2 | |
|
|
||||||
// to leasing server | | |
|
|
||||||
// | ---------- | |
|
|
||||||
// | | |------------+ |
|
|
||||||
// +->| RENEWING | |
|
|
||||||
// | |----------------------------+
|
|
||||||
// ----------
|
|
||||||
// Figure: State-transition diagram for DHCP clients
|
|
||||||
func (c *DHCPClient) Start() {
|
|
||||||
lease := c.requestWithBackoff()
|
|
||||||
|
|
||||||
c.initRebootFlag = false
|
|
||||||
c.lease = lease
|
|
||||||
|
|
||||||
// Set up two ticker to renew/rebind regularly
|
|
||||||
t1Timeout := c.lease.ACK.IPAddressLeaseTime(defaultDHCPRenew) / 2
|
|
||||||
t2Timeout := (c.lease.ACK.IPAddressLeaseTime(defaultDHCPRenew) / 8) * 7
|
|
||||||
log.Debug("dhcp timeouts", "timeout1", t1Timeout, "timeoute2", t2Timeout)
|
|
||||||
t1, t2 := time.NewTicker(t1Timeout), time.NewTicker(t2Timeout)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t1.C:
|
|
||||||
// renew is a unicast request of the IP renewal
|
|
||||||
// A point on renew is: the library does not return the right message (NAK)
|
|
||||||
// on renew error due to IP Change, but instead it returns a different error
|
|
||||||
// This way there's not much to do other than log and continue, as the renew error
|
|
||||||
// may be an offline server, or may be an incorrect package match
|
|
||||||
lease, err := c.renew()
|
|
||||||
if err == nil {
|
|
||||||
c.lease = lease
|
|
||||||
log.Info("renew", "lease", lease)
|
|
||||||
t2.Reset(t2Timeout)
|
|
||||||
} else {
|
|
||||||
log.Error("renew failed", "err", err)
|
|
||||||
}
|
|
||||||
case <-t2.C:
|
|
||||||
// rebind is just like a request, but forcing to provide a new IP address
|
|
||||||
lease, err := c.request(true)
|
|
||||||
if err == nil {
|
|
||||||
c.lease = lease
|
|
||||||
log.Info("rebind", "lease", lease)
|
|
||||||
} else {
|
|
||||||
if _, ok := err.(*nclient4.ErrNak); !ok {
|
|
||||||
t1.Stop()
|
|
||||||
t2.Stop()
|
|
||||||
log.Error("rebind failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Warn("ip may have changed", "ip", c.lease.ACK.YourIPAddr, "err", err)
|
|
||||||
c.initRebootFlag = false
|
|
||||||
c.lease = c.requestWithBackoff()
|
|
||||||
}
|
|
||||||
t1.Reset(t1Timeout)
|
|
||||||
t2.Reset(t2Timeout)
|
|
||||||
|
|
||||||
case <-c.stopChan:
|
|
||||||
// release is a unicast request of the IP release.
|
|
||||||
if err := c.release(); err != nil {
|
|
||||||
log.Error("release lease failed", "lease", lease, "err", err)
|
|
||||||
} else {
|
|
||||||
log.Info("release", "lease", lease)
|
|
||||||
}
|
|
||||||
t1.Stop()
|
|
||||||
t2.Stop()
|
|
||||||
|
|
||||||
close(c.releasedChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// | |INIT-REBOOT | RENEWING |REBINDING |
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// |broad/unicast |broadcast | unicast |broadcast |
|
|
||||||
// |server-ip |MUST NOT | MUST NOT |MUST NOT |
|
|
||||||
// |requested-ip |MUST | MUST NOT |MUST NOT |
|
|
||||||
// |ciaddr |zero | IP address |IP address|
|
|
||||||
// --------------------------------------------------------
|
|
||||||
|
|
||||||
func (c *DHCPClient) requestWithBackoff() *nclient4.Lease {
|
|
||||||
backoff := backoff.Backoff{
|
|
||||||
Factor: 2,
|
|
||||||
Jitter: true,
|
|
||||||
Min: 10 * time.Second,
|
|
||||||
Max: 1 * time.Minute,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var lease *nclient4.Lease
|
var client DHCPClient
|
||||||
var err error
|
|
||||||
|
|
||||||
for {
|
if strings.EqualFold(network.DHCPFamily(), utils.IPv6Family) {
|
||||||
log.Debug("trying to get a new IP", "attempt", backoff.Attempt())
|
client, err = NewDHCPv6Client(iface, nil, false, "")
|
||||||
lease, err = c.request(false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dur := backoff.Duration()
|
return nil, fmt.Errorf("failed to create DHCP client: %w", err)
|
||||||
if backoff.Attempt() > maxBackoffAttempts-1 {
|
|
||||||
errMsg := fmt.Errorf("failed to get an IP address after %d attempts, error %s, giving up", maxBackoffAttempts, err.Error())
|
|
||||||
log.Error(errMsg.Error())
|
|
||||||
c.errorChan <- errMsg
|
|
||||||
c.Stop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Error("request failed", "err", err.Error(), "waiting", dur)
|
|
||||||
time.Sleep(dur)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
backoff.Reset()
|
} else {
|
||||||
break
|
client = NewDHCPv4Client(iface, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ipChan != nil {
|
return client, nil
|
||||||
log.Debug("using channel")
|
|
||||||
c.ipChan <- lease.ACK.YourIPAddr.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return lease
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DHCPClient) request(rebind bool) (*nclient4.Lease, error) {
|
|
||||||
dhclient, err := nclient4.New(c.iface.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create a client for iface %s failed, error: %w", c.iface.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer dhclient.Close()
|
|
||||||
|
|
||||||
modifiers := make([]dhcpv4.Modifier, 0)
|
|
||||||
|
|
||||||
if c.ddnsHostName != "" {
|
|
||||||
modifiers = append(modifiers,
|
|
||||||
dhcpv4.WithOption(dhcpv4.OptHostName(c.ddnsHostName)),
|
|
||||||
dhcpv4.WithOption(dhcpv4.OptClientIdentifier([]byte(c.ddnsHostName))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if initRebootFlag is set, this means we have an IP already set on c.requestedIP that should be used
|
|
||||||
if c.initRebootFlag {
|
|
||||||
log.Debug("init-reboot", "ip", c.requestedIP)
|
|
||||||
modifiers = append(modifiers, dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(c.requestedIP)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this is a rebind, then the IP we should set is the one that already exists in lease
|
|
||||||
if rebind {
|
|
||||||
log.Debug("rebinding", "ip", c.lease.ACK.YourIPAddr)
|
|
||||||
modifiers = append(modifiers, dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(c.lease.ACK.YourIPAddr)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return dhclient.Request(context.TODO(), modifiers...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DHCPClient) release() error {
|
|
||||||
dhclient, err := nclient4.New(c.iface.Name)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create release client failed, error: %w, iface: %s, server ip: %v", err, c.iface.Name, c.lease.ACK.ServerIPAddr)
|
|
||||||
}
|
|
||||||
defer dhclient.Close()
|
|
||||||
|
|
||||||
// TODO modify lease
|
|
||||||
return dhclient.Release(c.lease)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DHCPClient) renew() (*nclient4.Lease, error) {
|
|
||||||
// renew needs a unicast client. This is due to some servers (like dnsmasq) require the exact request coming from the vip interface
|
|
||||||
dhclient, err := nclient4.New(c.iface.Name,
|
|
||||||
nclient4.WithUnicast(&net.UDPAddr{IP: c.lease.ACK.YourIPAddr, Port: nclient4.ClientPort}))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create renew client failed, error: %w, server ip: %v", err, c.lease.ACK.ServerIPAddr)
|
|
||||||
}
|
|
||||||
defer dhclient.Close()
|
|
||||||
|
|
||||||
return dhclient.Renew(context.TODO(), c.lease)
|
|
||||||
}
|
}
|
||||||
|
|||||||
292
pkg/vip/dhcpv4.go
Normal file
292
pkg/vip/dhcpv4.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
package vip
|
||||||
|
|
||||||
|
// DHCP client implementation that refers to https://www.rfc-editor.org/rfc/rfc2131.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "log/slog"
|
||||||
|
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
||||||
|
"github.com/jpillora/backoff"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dhcpClientPort = "68"
|
||||||
|
const defaultDHCPRenew = time.Hour
|
||||||
|
const maxBackoffAttempts = 3
|
||||||
|
|
||||||
|
// DHCPv4Client is responsible for maintaining ipv4 lease for one specified interface
|
||||||
|
type DHCPv4Client struct {
|
||||||
|
iface *net.Interface
|
||||||
|
ddnsHostName string
|
||||||
|
lease *nclient4.Lease
|
||||||
|
initRebootFlag bool
|
||||||
|
requestedIP net.IP
|
||||||
|
stopChan chan struct{} // used as a signal to release the IP and stop the dhcp client daemon
|
||||||
|
releasedChan chan struct{} // indicate that the IP has been released
|
||||||
|
errorChan chan error // indicates there was an error on the IP request
|
||||||
|
ipChan chan string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDHCPv4Client returns a new DHCP Client.
|
||||||
|
func NewDHCPv4Client(iface *net.Interface, initRebootFlag bool, requestedIP string) *DHCPv4Client {
|
||||||
|
return &DHCPv4Client{
|
||||||
|
iface: iface,
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
releasedChan: make(chan struct{}),
|
||||||
|
errorChan: make(chan error),
|
||||||
|
initRebootFlag: initRebootFlag,
|
||||||
|
requestedIP: net.ParseIP(requestedIP),
|
||||||
|
ipChan: make(chan string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv4Client) WithHostName(hostname string) DHCPClient {
|
||||||
|
c.ddnsHostName = hostname
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop state-transition process and close dhcp client
|
||||||
|
func (c *DHCPv4Client) Stop() {
|
||||||
|
close(c.ipChan)
|
||||||
|
close(c.stopChan)
|
||||||
|
<-c.releasedChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the IPChannel for consumption
|
||||||
|
func (c *DHCPv4Client) IPChannel() chan string {
|
||||||
|
return c.ipChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the ErrorChannel for consumption
|
||||||
|
func (c *DHCPv4Client) ErrorChannel() chan error {
|
||||||
|
return c.errorChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start state-transition process of dhcp client
|
||||||
|
//
|
||||||
|
// -------- -------
|
||||||
|
//
|
||||||
|
// | | +-------------------------->| |<-------------------+
|
||||||
|
// | INIT- | | +-------------------->| INIT | |
|
||||||
|
// | REBOOT |DHCPNAK/ +---------->| |<---+ |
|
||||||
|
// | |Restart| | ------- | |
|
||||||
|
//
|
||||||
|
// -------- | DHCPNAK/ | | |
|
||||||
|
// | Discard offer | -/Send DHCPDISCOVER |
|
||||||
|
//
|
||||||
|
// -/Send DHCPREQUEST | | |
|
||||||
|
//
|
||||||
|
// | | | DHCPACK v | |
|
||||||
|
// ----------- | (not accept.)/ ----------- | |
|
||||||
|
//
|
||||||
|
// | | | Send DHCPDECLINE | | |
|
||||||
|
// | REBOOTING | | | | SELECTING |<----+ |
|
||||||
|
// | | | / | | |DHCPOFFER/ |
|
||||||
|
//
|
||||||
|
// ----------- | / ----------- | |Collect |
|
||||||
|
// | | / | | | replies |
|
||||||
|
//
|
||||||
|
// DHCPACK/ | / +----------------+ +-------+ |
|
||||||
|
// Record lease, set| | v Select offer/ |
|
||||||
|
// timers T1, T2 ------------ send DHCPREQUEST | |
|
||||||
|
//
|
||||||
|
// | +----->| | DHCPNAK, Lease expired/ |
|
||||||
|
// | | | REQUESTING | Halt network |
|
||||||
|
// DHCPOFFER/ | | | |
|
||||||
|
// Discard ------------ | |
|
||||||
|
// | | | | ----------- |
|
||||||
|
// | +--------+ DHCPACK/ | | |
|
||||||
|
// | Record lease, set -----| REBINDING | |
|
||||||
|
// | timers T1, T2 / | | |
|
||||||
|
// | | DHCPACK/ ----------- |
|
||||||
|
// | v Record lease, set ^ |
|
||||||
|
// +----------------> ------- /timers T1,T2 | |
|
||||||
|
// +----->| |<---+ | |
|
||||||
|
// | | BOUND |<---+ | |
|
||||||
|
// DHCPOFFER, DHCPACK, | | | T2 expires/ DHCPNAK/
|
||||||
|
// DHCPNAK/Discard ------- | Broadcast Halt network
|
||||||
|
// | | | | DHCPREQUEST |
|
||||||
|
// +-------+ | DHCPACK/ | |
|
||||||
|
// T1 expires/ Record lease, set | |
|
||||||
|
// Send DHCPREQUEST timers T1, T2 | |
|
||||||
|
// to leasing server | | |
|
||||||
|
// | ---------- | |
|
||||||
|
// | | |------------+ |
|
||||||
|
// +->| RENEWING | |
|
||||||
|
// | |----------------------------+
|
||||||
|
// ----------
|
||||||
|
// Figure: State-transition diagram for DHCP clients
|
||||||
|
func (c *DHCPv4Client) Start(ctx context.Context) error {
|
||||||
|
dhcpContext, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
lease := c.requestWithBackoff(dhcpContext)
|
||||||
|
|
||||||
|
c.initRebootFlag = false
|
||||||
|
c.lease = lease
|
||||||
|
|
||||||
|
// Set up two ticker to renew/rebind regularly
|
||||||
|
t1Timeout := c.lease.ACK.IPAddressLeaseTime(defaultDHCPRenew) / 2
|
||||||
|
t2Timeout := (c.lease.ACK.IPAddressLeaseTime(defaultDHCPRenew) / 8) * 7
|
||||||
|
log.Debug("[DHCPv4] timeouts", "timeout1", t1Timeout, "timeoute2", t2Timeout)
|
||||||
|
t1, t2 := time.NewTicker(t1Timeout), time.NewTicker(t2Timeout)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t1.C:
|
||||||
|
// renew is a unicast request of the IP renewal
|
||||||
|
// A point on renew is: the library does not return the right message (NAK)
|
||||||
|
// on renew error due to IP Change, but instead it returns a different error
|
||||||
|
// This way there's not much to do other than log and continue, as the renew error
|
||||||
|
// may be an offline server, or may be an incorrect package match
|
||||||
|
lease, err := c.renew(dhcpContext)
|
||||||
|
if err == nil {
|
||||||
|
c.lease = lease
|
||||||
|
log.Info("[DHCPv4] renew", "lease", lease)
|
||||||
|
t2.Reset(t2Timeout)
|
||||||
|
} else {
|
||||||
|
log.Error("[DHCPv4] renew failed", "err", err)
|
||||||
|
}
|
||||||
|
case <-t2.C:
|
||||||
|
// rebind is just like a request, but forcing to provide a new IP address
|
||||||
|
lease, err := c.request(dhcpContext, true)
|
||||||
|
if err == nil {
|
||||||
|
c.lease = lease
|
||||||
|
log.Info("[DHCPv4] rebind", "lease", lease)
|
||||||
|
} else {
|
||||||
|
if _, ok := err.(*nclient4.ErrNak); !ok {
|
||||||
|
t1.Stop()
|
||||||
|
t2.Stop()
|
||||||
|
log.Error("[DHCPv4] rebind failed", "err", err)
|
||||||
|
}
|
||||||
|
log.Warn("[DHCPv4] ip may have changed", "ip", c.lease.ACK.YourIPAddr, "err", err)
|
||||||
|
c.initRebootFlag = false
|
||||||
|
c.lease = c.requestWithBackoff(dhcpContext)
|
||||||
|
}
|
||||||
|
t1.Reset(t1Timeout)
|
||||||
|
t2.Reset(t2Timeout)
|
||||||
|
|
||||||
|
case <-c.stopChan:
|
||||||
|
// release is a unicast request of the IP release.
|
||||||
|
var err error
|
||||||
|
if err = c.release(); err != nil {
|
||||||
|
log.Error("[DHCPv4] release lease failed", "lease", lease, "err", err)
|
||||||
|
} else {
|
||||||
|
log.Info("[DHCPv4] release", "lease", lease)
|
||||||
|
}
|
||||||
|
t1.Stop()
|
||||||
|
t2.Stop()
|
||||||
|
|
||||||
|
close(c.releasedChan)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------
|
||||||
|
// | |INIT-REBOOT | RENEWING |REBINDING |
|
||||||
|
// --------------------------------------------------------
|
||||||
|
// |broad/unicast |broadcast | unicast |broadcast |
|
||||||
|
// |server-ip |MUST NOT | MUST NOT |MUST NOT |
|
||||||
|
// |requested-ip |MUST | MUST NOT |MUST NOT |
|
||||||
|
// |ciaddr |zero | IP address |IP address|
|
||||||
|
// --------------------------------------------------------
|
||||||
|
|
||||||
|
func (c *DHCPv4Client) requestWithBackoff(ctx context.Context) *nclient4.Lease {
|
||||||
|
backoff := backoff.Backoff{
|
||||||
|
Factor: 2,
|
||||||
|
Jitter: true,
|
||||||
|
Min: 10 * time.Second,
|
||||||
|
Max: 1 * time.Minute,
|
||||||
|
}
|
||||||
|
|
||||||
|
var lease *nclient4.Lease
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for {
|
||||||
|
log.Debug("[DHCPv4] trying to get a new IP", "attempt", backoff.Attempt())
|
||||||
|
lease, err = c.request(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
dur := backoff.Duration()
|
||||||
|
if backoff.Attempt() > maxBackoffAttempts-1 {
|
||||||
|
errMsg := fmt.Errorf("failed to get an IP address after %d attempts, error %s, giving up", maxBackoffAttempts, err.Error())
|
||||||
|
log.Error(errMsg.Error())
|
||||||
|
c.errorChan <- errMsg
|
||||||
|
c.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Error("[DHCPv4] request failed", "err", err.Error(), "waiting", dur)
|
||||||
|
time.Sleep(dur)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
backoff.Reset()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ipChan != nil {
|
||||||
|
log.Debug("[DHCPv4] using channel")
|
||||||
|
c.ipChan <- lease.ACK.YourIPAddr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return lease
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv4Client) request(ctx context.Context, rebind bool) (*nclient4.Lease, error) {
|
||||||
|
dhclient, err := nclient4.New(c.iface.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create a client for iface %s failed, error: %w", c.iface.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer dhclient.Close()
|
||||||
|
|
||||||
|
modifiers := make([]dhcpv4.Modifier, 0)
|
||||||
|
|
||||||
|
if c.ddnsHostName != "" {
|
||||||
|
modifiers = append(modifiers,
|
||||||
|
dhcpv4.WithOption(dhcpv4.OptHostName(c.ddnsHostName)),
|
||||||
|
dhcpv4.WithOption(dhcpv4.OptClientIdentifier([]byte(c.ddnsHostName))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if initRebootFlag is set, this means we have an IP already set on c.requestedIP that should be used
|
||||||
|
if c.initRebootFlag {
|
||||||
|
log.Debug("[DHCPv4] init-reboot", "ip", c.requestedIP)
|
||||||
|
modifiers = append(modifiers, dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(c.requestedIP)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is a rebind, then the IP we should set is the one that already exists in lease
|
||||||
|
if rebind {
|
||||||
|
log.Debug("[DHCPv4] rebinding", "ip", c.lease.ACK.YourIPAddr)
|
||||||
|
modifiers = append(modifiers, dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(c.lease.ACK.YourIPAddr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return dhclient.Request(ctx, modifiers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv4Client) release() error {
|
||||||
|
dhclient, err := nclient4.New(c.iface.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create release client failed, error: %w, iface: %s, server ip: %v", err, c.iface.Name, c.lease.ACK.ServerIPAddr)
|
||||||
|
}
|
||||||
|
defer dhclient.Close()
|
||||||
|
|
||||||
|
// TODO modify lease
|
||||||
|
return dhclient.Release(c.lease)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv4Client) renew(ctx context.Context) (*nclient4.Lease, error) {
|
||||||
|
// renew needs a unicast client. This is due to some servers (like dnsmasq) require the exact request coming from the vip interface
|
||||||
|
dhclient, err := nclient4.New(c.iface.Name,
|
||||||
|
nclient4.WithUnicast(&net.UDPAddr{IP: c.lease.ACK.YourIPAddr, Port: nclient4.ClientPort}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create renew client failed, error: %w, server ip: %v", err, c.lease.ACK.ServerIPAddr)
|
||||||
|
}
|
||||||
|
defer dhclient.Close()
|
||||||
|
|
||||||
|
return dhclient.Renew(ctx, c.lease)
|
||||||
|
}
|
||||||
387
pkg/vip/dhcpv6.go
Normal file
387
pkg/vip/dhcpv6.go
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
package vip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
log "log/slog"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||||
|
"github.com/insomniacslk/dhcp/dhcpv6/nclient6"
|
||||||
|
"github.com/insomniacslk/dhcp/iana"
|
||||||
|
"github.com/jpillora/backoff"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dhcpv6ClientManager *DHCPv6ClientManager
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dhcpv6ClientManager = NewDHCPv6ClientManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DHCPv6ClientManager struct {
|
||||||
|
clients map[string]*DHCPv6InternalClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDHCPv6ClientManager() *DHCPv6ClientManager {
|
||||||
|
return &DHCPv6ClientManager{
|
||||||
|
clients: map[string]*DHCPv6InternalClient{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DHCPv6ClientManager) Get(iface string) *DHCPv6InternalClient {
|
||||||
|
c, exists := m.clients[iface]
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DHCPv6ClientManager) Add(iface string) (*DHCPv6InternalClient, error) {
|
||||||
|
c := m.Get(iface)
|
||||||
|
|
||||||
|
if c != nil {
|
||||||
|
c.references.Add(1)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
client, err := NewDHCPv6InternalClient(iface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.clients[iface] = client
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DHCPv6ClientManager) Delete(iface string) {
|
||||||
|
c := m.Get(iface)
|
||||||
|
|
||||||
|
if c != nil {
|
||||||
|
c.references.Add(-1)
|
||||||
|
ref := c.references.Load()
|
||||||
|
if ref < 1 {
|
||||||
|
c.client.Close()
|
||||||
|
delete(m.clients, iface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DHCPv6InternalClient struct {
|
||||||
|
client *nclient6.Client
|
||||||
|
references *atomic.Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDHCPv6InternalClient(iface string) (*DHCPv6InternalClient, error) {
|
||||||
|
client, err := nclient6.New(iface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create DHCPv6 client for interface %q: %w", iface, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := &atomic.Int32{}
|
||||||
|
ref.Store(1)
|
||||||
|
return &DHCPv6InternalClient{
|
||||||
|
client: client,
|
||||||
|
references: ref,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DHCPv6Client struct {
|
||||||
|
iface *net.Interface
|
||||||
|
ddnsHostName string
|
||||||
|
initRebootFlag bool
|
||||||
|
requestedIP net.IP
|
||||||
|
stopChan chan struct{} // used as a signal to release the IP and stop the dhcp client daemon
|
||||||
|
releasedChan chan struct{} // indicate that the IP has been released
|
||||||
|
errorChan chan error // indicates there was an error on the IP request
|
||||||
|
ipChan chan string
|
||||||
|
ic *DHCPv6InternalClient
|
||||||
|
addr *dhcpv6.OptIAAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDHCPv6Client returns a new DHCP6 Client.
|
||||||
|
func NewDHCPv6Client(iface *net.Interface, parent netlink.Link, initRebootFlag bool, requestedIP string) (*DHCPv6Client, error) {
|
||||||
|
name := iface.Name
|
||||||
|
if parent != nil {
|
||||||
|
name = parent.Attrs().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := dhcpv6ClientManager.Add(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create DHCPv6 client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DHCPv6Client{
|
||||||
|
iface: iface,
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
releasedChan: make(chan struct{}),
|
||||||
|
errorChan: make(chan error),
|
||||||
|
initRebootFlag: initRebootFlag,
|
||||||
|
requestedIP: net.ParseIP(requestedIP),
|
||||||
|
ipChan: make(chan string),
|
||||||
|
ic: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv6Client) WithHostName(hostname string) DHCPClient {
|
||||||
|
c.ddnsHostName = hostname
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop state-transition process and close dhcp client
|
||||||
|
func (c *DHCPv6Client) Stop() {
|
||||||
|
close(c.ipChan)
|
||||||
|
close(c.stopChan)
|
||||||
|
<-c.releasedChan
|
||||||
|
dhcpv6ClientManager.Delete(c.iface.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the IPChannel for consumption
|
||||||
|
func (c *DHCPv6Client) IPChannel() chan string {
|
||||||
|
return c.ipChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the ErrorChannel for consumption
|
||||||
|
func (c *DHCPv6Client) ErrorChannel() chan error {
|
||||||
|
return c.errorChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv6Client) Start(ctx context.Context) error {
|
||||||
|
dhcpContext, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// REQUEST WITH BACKOFF ACTION
|
||||||
|
addr, err := c.requestWithBackoff(dhcpContext)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("DHCPv6 client failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.addr = addr
|
||||||
|
|
||||||
|
c.initRebootFlag = false
|
||||||
|
|
||||||
|
// Set up two ticker to renew/rebind regularly
|
||||||
|
t1Timeout := c.addr.PreferredLifetime / 2
|
||||||
|
t2Timeout := (c.addr.ValidLifetime / 8) * 7
|
||||||
|
log.Debug("[DHCPv6] timeouts", "timeout1", t1Timeout, "timeoute2", t2Timeout)
|
||||||
|
t1, t2 := time.NewTicker(t1Timeout), time.NewTicker(t2Timeout)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t1.C:
|
||||||
|
// renew is a unicast request of the IP renewal
|
||||||
|
// A point on renew is: the library does not return the right message (NAK)
|
||||||
|
// on renew error due to IP Change, but instead it returns a different error
|
||||||
|
// This way there's not much to do other than log and continue, as the renew error
|
||||||
|
// may be an offline server, or may be an incorrect package match
|
||||||
|
|
||||||
|
addr, err := c.renew(dhcpContext)
|
||||||
|
if err == nil {
|
||||||
|
c.addr = addr
|
||||||
|
log.Info("[DHCPv6] renew", "addr", addr.IPv6Addr.String())
|
||||||
|
t2.Reset(t2Timeout)
|
||||||
|
} else {
|
||||||
|
log.Error("[DHCPv6] renew failed", "err", err)
|
||||||
|
}
|
||||||
|
case <-t2.C:
|
||||||
|
// rebind is just like a request, but forcing to provide a new IP address
|
||||||
|
addr, err := c.request(dhcpContext, true)
|
||||||
|
if err == nil {
|
||||||
|
c.addr = addr
|
||||||
|
log.Info("[DHCPv6] rebind", "lease", addr)
|
||||||
|
} else {
|
||||||
|
log.Warn("[DHCPv6] ip may have changed", "ip", addr.IPv6Addr.String(), "err", err)
|
||||||
|
c.initRebootFlag = false
|
||||||
|
c.addr, err = c.requestWithBackoff(dhcpContext)
|
||||||
|
log.Error("[DHCPv6] rebind failed", "err", err)
|
||||||
|
}
|
||||||
|
t1.Reset(t1Timeout)
|
||||||
|
t2.Reset(t2Timeout)
|
||||||
|
|
||||||
|
case <-c.stopChan:
|
||||||
|
dhcpStopContext, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
// IP address release.
|
||||||
|
var err error
|
||||||
|
if err = c.release(dhcpStopContext); err != nil {
|
||||||
|
log.Error("[DHCPv6] release failed", "err", err)
|
||||||
|
} else {
|
||||||
|
log.Info("[DHCPv6] released", "address", c.addr.String())
|
||||||
|
}
|
||||||
|
t1.Stop()
|
||||||
|
t2.Stop()
|
||||||
|
|
||||||
|
close(c.releasedChan)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv6Client) requestWithBackoff(ctx context.Context) (*dhcpv6.OptIAAddress, error) {
|
||||||
|
backoff := backoff.Backoff{
|
||||||
|
Factor: 2,
|
||||||
|
Jitter: true,
|
||||||
|
Min: 10 * time.Second,
|
||||||
|
Max: 1 * time.Minute,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var addr *dhcpv6.OptIAAddress
|
||||||
|
|
||||||
|
for {
|
||||||
|
log.Debug("[DHCPv6] trying to get a new IP", "attempt", backoff.Attempt())
|
||||||
|
|
||||||
|
addr, err = c.request(ctx, false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
dur := backoff.Duration()
|
||||||
|
if backoff.Attempt() > maxBackoffAttempts-1 {
|
||||||
|
errMsg := fmt.Errorf("failed to get an IP address after %d attempts, error %s, giving up", maxBackoffAttempts, err.Error())
|
||||||
|
log.Error(errMsg.Error())
|
||||||
|
c.errorChan <- errMsg
|
||||||
|
c.Stop()
|
||||||
|
return nil, fmt.Errorf("failed to get IPv6 address: %w", err)
|
||||||
|
}
|
||||||
|
log.Error("[DHCPv6] request failed", "err", err.Error(), "waiting", dur)
|
||||||
|
time.Sleep(dur)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
backoff.Reset()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ipChan != nil {
|
||||||
|
log.Debug("[DHCPv6] using channel")
|
||||||
|
c.ipChan <- addr.IPv6Addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv6Client) request(ctx context.Context, rebind bool) (*dhcpv6.OptIAAddress, error) {
|
||||||
|
modifiers := []dhcpv6.Modifier{}
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithClientID(&dhcpv6.DUIDEN{EnterpriseNumber: 1, EnterpriseIdentifier: []byte(c.ddnsHostName)}))
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithFQDN(4, c.ddnsHostName))
|
||||||
|
|
||||||
|
// if initRebootFlag is set, this means we have an IP already set on c.requestedIP that should be used
|
||||||
|
if c.initRebootFlag {
|
||||||
|
log.Debug("[DHCPv6] init-reboot", "ip", c.requestedIP)
|
||||||
|
addr := dhcpv6.OptIAAddress{
|
||||||
|
IPv6Addr: c.requestedIP,
|
||||||
|
}
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithIANA(addr))
|
||||||
|
} else if rebind {
|
||||||
|
if c.addr == nil {
|
||||||
|
return nil, fmt.Errorf("unable to rebind - current IP unknown")
|
||||||
|
}
|
||||||
|
log.Debug("[DHCPv6] rebinding", "ip", c.addr.IPv6Addr)
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithIANA(*c.addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply *dhcpv6.Message
|
||||||
|
if rebind || c.initRebootFlag {
|
||||||
|
request, err := dhcpv6.NewMessage(modifiers...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create rebind message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.MessageType = dhcpv6.MessageTypeRebind
|
||||||
|
|
||||||
|
reply, err = c.ic.client.SendAndRead(ctx, c.ic.client.RemoteAddr(), request, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("rebind error: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
adv, err := c.ic.client.Solicit(ctx, modifiers...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("solicit error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := dhcpv6.NewRequestFromAdvertise(adv, modifiers...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create request message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.MessageType = dhcpv6.MessageTypeAdvertise
|
||||||
|
|
||||||
|
reply, err = c.ic.client.Request(ctx, request, modifiers...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply == nil {
|
||||||
|
return nil, fmt.Errorf("invalid request")
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAddress(reply.Options.IANA())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv6Client) renew(ctx context.Context) (*dhcpv6.OptIAAddress, error) {
|
||||||
|
modifiers := []dhcpv6.Modifier{}
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithClientID(&dhcpv6.DUIDEN{EnterpriseNumber: 1, EnterpriseIdentifier: []byte(c.ddnsHostName)}))
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithFQDN(4, c.ddnsHostName))
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithOption(&dhcpv6.OptionGeneric{OptionCode: dhcpv6.OptionUnicast}))
|
||||||
|
|
||||||
|
adv, err := c.ic.client.Solicit(ctx, modifiers...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("solicit error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := dhcpv6.NewRequestFromAdvertise(adv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.MessageType = dhcpv6.MessageTypeRenew
|
||||||
|
|
||||||
|
reply, err := c.ic.client.SendAndRead(ctx, c.ic.client.RemoteAddr(), request, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to send renew: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAddress(reply.Options.IANA())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DHCPv6Client) release(ctx context.Context) error {
|
||||||
|
modifiers := []dhcpv6.Modifier{}
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithClientID(&dhcpv6.DUIDEN{EnterpriseNumber: 1, EnterpriseIdentifier: []byte(c.ddnsHostName)}))
|
||||||
|
modifiers = append(modifiers, dhcpv6.WithFQDN(4, c.ddnsHostName))
|
||||||
|
|
||||||
|
adv, err := c.ic.client.Solicit(ctx, modifiers...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("solicit error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := dhcpv6.NewRequestFromAdvertise(adv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create release message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.MessageType = dhcpv6.MessageTypeRelease
|
||||||
|
|
||||||
|
reply, err := c.ic.client.SendAndRead(ctx, c.ic.client.RemoteAddr(), request, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to send release: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply.Options.Status().StatusCode != iana.StatusSuccess {
|
||||||
|
return fmt.Errorf("release failed with code %d: %s", reply.Options.Status().StatusCode, reply.Options.Status().StatusMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAddress(iana []*dhcpv6.OptIANA) (*dhcpv6.OptIAAddress, error) {
|
||||||
|
if len(iana) < 1 {
|
||||||
|
return nil, fmt.Errorf("failed to get IANA")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(iana) < 1 {
|
||||||
|
return nil, fmt.Errorf("failed to get addresses data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return iana[0].Options.Addresses()[0], nil
|
||||||
|
}
|
||||||
@@ -39,20 +39,20 @@ func (d *ipUpdater) Run(ctx context.Context) {
|
|||||||
mode = "ipv6"
|
mode = "ipv6"
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := utils.LookupHost(d.vip.DNSName(), mode)
|
ip, err := utils.LookupHost(d.vip.DNSName(), mode, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("cannot lookup", "name", d.vip.DNSName(), "err", err)
|
log.Warn("cannot lookup", "name", d.vip.DNSName(), "err", err)
|
||||||
// fallback to renewing the existing IP
|
// fallback to renewing the existing IP
|
||||||
ip = []string{d.vip.IP()}
|
ip = []string{d.vip.IP()}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("setting IP", "address", ip)
|
log.Debug("(ipUpdater) setting IP", "address", ip)
|
||||||
if err := d.vip.SetIP(ip[0]); err != nil {
|
if err := d.vip.SetIP(ip[0]); err != nil {
|
||||||
log.Error("setting IP", "address", ip, "err", err)
|
log.Error("setting IP", "address", ip, "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal VIP addition for DNS, use skipDAD=false for normal DAD process
|
// Normal VIP addition for DNS, use skipDAD=false for normal DAD process
|
||||||
if _, err := d.vip.AddIP(false, false); err != nil {
|
if _, err := d.vip.AddIP(true, false); err != nil {
|
||||||
log.Error("error adding virtual IP", "err", err)
|
log.Error("error adding virtual IP", "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ type NdpResponder struct {
|
|||||||
func NewNDPResponder(ifaceName string) (*NdpResponder, error) {
|
func NewNDPResponder(ifaceName string) (*NdpResponder, error) {
|
||||||
iface, err := net.InterfaceByName(ifaceName)
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get interface %q: %v", ifaceName, err)
|
return nil, fmt.Errorf("failed to get interface %q: %w", ifaceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use link-local address as the source IPv6 address for NDP communications.
|
// Use link-local address as the source IPv6 address for NDP communications.
|
||||||
conn, _, err := ndp.Listen(iface, ndp.LinkLocal)
|
conn, _, err := ndp.Listen(iface, ndp.LinkLocal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating NDP responder for %q: %s", iface.Name, err)
|
return nil, fmt.Errorf("creating NDP responder for %s: %w", iface.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := &NdpResponder{
|
ret := &NdpResponder{
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
log "log/slog"
|
log "log/slog"
|
||||||
|
|
||||||
"github.com/kube-vip/kube-vip/pkg/utils"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
@@ -144,22 +142,3 @@ func GetNonLinkLocalIP(iface *netlink.Link, family int) (string, error) {
|
|||||||
|
|
||||||
return "", fmt.Errorf("failed to find non-local IP on interface: %s", (*iface).Attrs().Name)
|
return "", fmt.Errorf("failed to find non-local IP on interface: %s", (*iface).Attrs().Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectSubnet(address string, subnets []string) string {
|
|
||||||
subnet := ""
|
|
||||||
if utils.IsIPv4(address) {
|
|
||||||
if subnets[0] != "" {
|
|
||||||
subnet = subnets[0]
|
|
||||||
} else {
|
|
||||||
subnet = strconv.Itoa(defaultMaskIPv4)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(subnets) > 1 && subnets[1] != "" {
|
|
||||||
subnet = subnets[1]
|
|
||||||
} else {
|
|
||||||
subnet = strconv.Itoa(defaultMaskIPv6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return subnet
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func GetLogs(ctx context.Context, client kubernetes.Interface, tempDirPath strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
intCtx, cancel := context.WithTimeout(ctx, time.Second*10)
|
intCtx, cancel := context.WithTimeout(ctx, time.Second*20)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
path := filepath.Join(tempDirPath, "pods.json")
|
path := filepath.Join(tempDirPath, "pods.json")
|
||||||
@@ -93,7 +93,8 @@ func GetLogs(ctx context.Context, client kubernetes.Interface, tempDirPath strin
|
|||||||
LabelSelector: "app=kube-vip",
|
LabelSelector: "app=kube-vip",
|
||||||
}
|
}
|
||||||
|
|
||||||
kvpods, err := client.CoreV1().Pods("").List(intCtx, listOptions)
|
var kvpods *corev1.PodList
|
||||||
|
kvpods, err = client.CoreV1().Pods("").List(intCtx, listOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to list pods: %w", err)
|
return fmt.Errorf("failed to list pods: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user