@@ -22,8 +22,29 @@ import (
2222 logicalswitchmanager "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/ovn/logical_switch_manager"
2323 ovntypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"
2424 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
25+ "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/ndp"
2526)
2627
28+ // DefaultGatewayReconciler is responsible for reconciling the default gateway
29+ // configuration of a virtual machine's network interface after a live migration.
30+ // It supports both IPv4 and IPv6 configurations.
31+ type DefaultGatewayReconciler struct {
32+ watchFactory * factory.WatchFactory
33+ netInfo util.NetInfo
34+ interfaceName string
35+ }
36+
37+ // NewDefaultGatewayReconciler creates a new instance of DefaultGatewayReconciler.
38+ // It takes a WatchFactory for managing resource watches, a NetInfo object for network information,
39+ // and the name of the network interface to send ARPs or RAs as parameters.
40+ func NewDefaultGatewayReconciler (watchFactory * factory.WatchFactory , netInfo util.NetInfo , interfaceName string ) * DefaultGatewayReconciler {
41+ return & DefaultGatewayReconciler {
42+ watchFactory : watchFactory ,
43+ netInfo : netInfo ,
44+ interfaceName : interfaceName ,
45+ }
46+ }
47+
2748// IsPodLiveMigratable will return true if the pod belongs
2849// to kubevirt and should use the live migration features
2950func IsPodLiveMigratable (pod * corev1.Pod ) bool {
@@ -482,17 +503,20 @@ func DiscoverLiveMigrationStatus(client *factory.WatchFactory, pod *corev1.Pod)
482503 return & status , nil
483504}
484505
485- func ReconcileIPv4DefaultGatewayAfterLiveMigration (watchFactory * factory.WatchFactory , netInfo util.NetInfo , liveMigrationStatus * LiveMigrationStatus , interfaceName string ) error {
506+ // ReconcileIPv4AfterLiveMigration will send a GARP after live migration
507+ // to update the default gw mac address to the node where the VM is running
508+ // now.
509+ func (r * DefaultGatewayReconciler ) ReconcileIPv4AfterLiveMigration (liveMigrationStatus * LiveMigrationStatus ) error {
486510 if liveMigrationStatus .State != LiveMigrationTargetDomainReady {
487511 return nil
488512 }
489513
490- targetNode , err := watchFactory .GetNode (liveMigrationStatus .TargetPod .Spec .NodeName )
514+ targetNode , err := r . watchFactory .GetNode (liveMigrationStatus .TargetPod .Spec .NodeName )
491515 if err != nil {
492516 return err
493517 }
494518
495- lrpJoinAddress , err := util .ParseNodeGatewayRouterJoinNetwork (targetNode , netInfo .GetNetworkName ())
519+ lrpJoinAddress , err := util .ParseNodeGatewayRouterJoinNetwork (targetNode , r . netInfo .GetNetworkName ())
496520 if err != nil {
497521 return err
498522 }
@@ -503,16 +527,104 @@ func ReconcileIPv4DefaultGatewayAfterLiveMigration(watchFactory *factory.WatchFa
503527 }
504528
505529 lrpMAC := util .IPAddrToHWAddr (lrpJoinIPv4 )
506- for _ , subnet := range netInfo .Subnets () {
530+ for _ , subnet := range r . netInfo .Subnets () {
507531 gwIP := util .GetNodeGatewayIfAddr (subnet .CIDR ).IP .To4 ()
508532 if gwIP == nil {
509533 continue
510534 }
511535 garp := util.GARP {IP : gwIP , MAC : & lrpMAC }
512- if err := util .BroadcastGARP (interfaceName , garp ); err != nil {
536+ if err := util .BroadcastGARP (r . interfaceName , garp ); err != nil {
513537 return err
514538 }
515539 }
516-
517540 return nil
518541}
542+
543+ // ReconcileIPv6AfterLiveMigration will do two things at VM's:
544+ // - Remove ipv6 default gw path from VM's node before live migration
545+ // - Add ipv6 default gw path from VM's node after live migration
546+ // This is done by sending a pair of unsolicited RA's one with lifetime=0
547+ // (to remove the gateway path) another with lifetime=max to add the new
548+ // default gateway path
549+ func (r * DefaultGatewayReconciler ) ReconcileIPv6AfterLiveMigration (liveMigration * LiveMigrationStatus ) error {
550+ if ! liveMigration .IsTargetDomainReady () {
551+ return nil
552+ }
553+ nodes , err := r .watchFactory .GetNodes ()
554+ if err != nil {
555+ return err
556+ }
557+
558+ targetPod := liveMigration .TargetPod
559+ if len (r .netInfo .GetNADs ()) != 1 {
560+ return fmt .Errorf ("expected only one nad for network %q, got %d" , r .netInfo .GetNetworkName (), len (r .netInfo .GetNADs ()))
561+ }
562+
563+ targetPodAnnotation , err := util .UnmarshalPodAnnotation (targetPod .Annotations , r .netInfo .GetNADs ()[0 ])
564+ if err != nil {
565+ return ovntypes .NewSuppressedError (fmt .Errorf ("failed parsing ovn pod annotation for pod '%s/%s' and network %q: %w" , targetPod .Namespace , targetPod .Name , r .netInfo .GetNetworkName (), err ))
566+ }
567+
568+ destinationIP , err := util .MatchFirstIPNetFamily (true /* ipv6 */ , targetPodAnnotation .IPs )
569+ if err != nil {
570+ return err
571+ }
572+ destinationMAC := targetPodAnnotation .MAC
573+
574+ ras := make ([]ndp.RouterAdvertisement , 0 , len (nodes ))
575+ for _ , node := range nodes {
576+ if node .Name == liveMigration .TargetPod .Spec .NodeName {
577+ // skip the target node since this is the proper gateway
578+ continue
579+ }
580+ nodeJoinAddrs , err := util .ParseNodeGatewayRouterJoinAddrs (node , r .netInfo .GetNetworkName ())
581+ if err != nil {
582+ return ovntypes .NewSuppressedError (fmt .Errorf ("failed parsing join addresss from node %q and network %q to reconcile ipv6 gateway: %w" , node .Name , r .netInfo .GetNetworkName (), err ))
583+ }
584+ // During upgrades, nftables blocks Router Advertisements (RAs) from other nodes.
585+ // However, Virtual Machines (VMs) may still retain old default gateway paths.
586+ // To address this, we create a new Router Advertisement with a lifetime of 0
587+ // to signal the removal of the old default gateway.
588+ // NOTE: This is a workaround for the issue and may not be needed in the future, after
589+ // upgrading to a version that supports the new behavior.
590+ ras = append (ras , newRouterAdvertisementFromJoinIPAndLifetime (nodeJoinAddrs [0 ].IP , destinationMAC , destinationIP .IP , 0 ))
591+ }
592+ targetNode , err := r .watchFactory .GetNode (liveMigration .TargetPod .Spec .NodeName )
593+ if err != nil {
594+ return fmt .Errorf ("failed fetching node %q to reconcile ipv6 gateway: %w" , liveMigration .TargetPod .Spec .NodeName , err )
595+ }
596+ targetNodeJoinAddrs , err := util .ParseNodeGatewayRouterJoinAddrs (targetNode , r .netInfo .GetNetworkName ())
597+ if err != nil {
598+ return ovntypes .NewSuppressedError (fmt .Errorf ("failed parsing join addresss from live migration target node %q and network %q to reconcile ipv6 gateway: %w" , targetNode .Name , r .netInfo .GetNetworkName (), err ))
599+ }
600+ ras = append (ras , newRouterAdvertisementFromJoinIPAndLifetime (targetNodeJoinAddrs [0 ].IP , destinationMAC , destinationIP .IP , 65535 ))
601+ return ndp .SendRouterAdvertisements (r .interfaceName , ras ... )
602+ }
603+
604+ // newRouterAdvertisementFromJoinIPAndLifetime creates a new Router Advertisement (RA) message
605+ // using the provided join IP address, destination MAC, destination IP, and lifetime.
606+ //
607+ // This function performs the following:
608+ // - Derives the source MAC address from the given IP using util.IPAddrToHWAddr.
609+ // - Calculates the link-local address (LLA) from the source MAC using util.HWAddrToIPv6LLA.
610+ // - Configures the destination IP and MAC address to use the provided values.
611+ // - Sets the RA message's lifetime to the specified value.
612+ //
613+ // Parameters:
614+ // - ip: The join IP address used to derive the source MAC and LLA.
615+ // - destinationMAC: The MAC address to which the RA message will be sent.
616+ // - destinationIP: The IP address to which the RA message will be sent.
617+ // - lifetime: The lifetime value for the RA message, in seconds.
618+ //
619+ // Returns:
620+ // - An ndp.RouterAdvertisement object configured with the calculated source MAC, LLA, and the provided destination MAC, IP, and lifetime.
621+ func newRouterAdvertisementFromJoinIPAndLifetime (ip net.IP , destinationMAC net.HardwareAddr , destinationIP net.IP , lifetime uint16 ) ndp.RouterAdvertisement {
622+ sourceMAC := util .IPAddrToHWAddr (ip )
623+ return ndp.RouterAdvertisement {
624+ SourceMAC : sourceMAC ,
625+ SourceIP : util .HWAddrToIPv6LLA (sourceMAC ),
626+ DestinationMAC : destinationMAC ,
627+ DestinationIP : destinationIP ,
628+ Lifetime : lifetime ,
629+ }
630+ }
0 commit comments