github.com/webmeshproj/webmesh-cni@v0.0.27/internal/controllers/peercontainer_controller.go (about)

     1  /*
     2  Copyright 2023 Avi Zimmerman <avi.zimmerman@gmail.com>.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controllers
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/netip"
    23  	"sync"
    24  	"time"
    25  
    26  	v1 "github.com/webmeshproj/api/go/v1"
    27  	"github.com/webmeshproj/storage-provider-k8s/provider"
    28  	"github.com/webmeshproj/webmesh/pkg/crypto"
    29  	"github.com/webmeshproj/webmesh/pkg/logging"
    30  	"github.com/webmeshproj/webmesh/pkg/meshnet"
    31  	"github.com/webmeshproj/webmesh/pkg/meshnet/endpoints"
    32  	netutil "github.com/webmeshproj/webmesh/pkg/meshnet/netutil"
    33  	meshtransport "github.com/webmeshproj/webmesh/pkg/meshnet/transport"
    34  	meshnode "github.com/webmeshproj/webmesh/pkg/meshnode"
    35  	meshdns "github.com/webmeshproj/webmesh/pkg/services/meshdns"
    36  	meshstorage "github.com/webmeshproj/webmesh/pkg/storage"
    37  	mesherrors "github.com/webmeshproj/webmesh/pkg/storage/errors"
    38  	meshtypes "github.com/webmeshproj/webmesh/pkg/storage/types"
    39  	ctrl "sigs.k8s.io/controller-runtime"
    40  	"sigs.k8s.io/controller-runtime/pkg/client"
    41  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    42  	"sigs.k8s.io/controller-runtime/pkg/log"
    43  
    44  	cniv1 "github.com/webmeshproj/webmesh-cni/api/v1"
    45  	"github.com/webmeshproj/webmesh-cni/internal/config"
    46  	"github.com/webmeshproj/webmesh-cni/internal/host"
    47  )
    48  
    49  //+kubebuilder:rbac:groups=cni.webmesh.io,resources=peercontainers,verbs=get;list;watch;create;update;patch;delete
    50  //+kubebuilder:rbac:groups=cni.webmesh.io,resources=peercontainers/status,verbs=get;update;patch
    51  //+kubebuilder:rbac:groups=cni.webmesh.io,resources=peercontainers/finalizers,verbs=update
    52  
    53  // PeerContainerReconciler reconciles a PeerContainer object. Reconcile
    54  // attempts will fail until SetNetworkState is called.
    55  type PeerContainerReconciler struct {
    56  	client.Client
    57  	config.Config
    58  	Provider *provider.Provider
    59  	Host     host.Node
    60  
    61  	dns            *meshdns.Server
    62  	containerNodes map[client.ObjectKey]meshnode.Node
    63  	mu             sync.Mutex
    64  }
    65  
    66  // SetupWithManager sets up the controller with the Manager.
    67  func (r *PeerContainerReconciler) SetupWithManager(mgr ctrl.Manager) (err error) {
    68  	// Create clients for IPAM locking
    69  	r.containerNodes = make(map[client.ObjectKey]meshnode.Node)
    70  	return ctrl.NewControllerManagedBy(mgr).
    71  		For(&cniv1.PeerContainer{}).
    72  		Complete(r)
    73  }
    74  
    75  // SetDNSServer sets the DNS server for the controller.
    76  func (r *PeerContainerReconciler) SetDNSServer(dns *meshdns.Server) {
    77  	r.mu.Lock()
    78  	defer r.mu.Unlock()
    79  	r.dns = dns
    80  }
    81  
    82  // LookupPrivateKey looks up the private key for the given node ID.
    83  func (r *PeerContainerReconciler) LookupPrivateKey(nodeID meshtypes.NodeID) (crypto.PrivateKey, bool) {
    84  	r.mu.Lock()
    85  	defer r.mu.Unlock()
    86  	if nodeID == r.Host.ID() || string(nodeID) == r.Host.Node().Key().ID() {
    87  		return r.Host.Node().Key(), true
    88  	}
    89  	for _, node := range r.containerNodes {
    90  		if node.ID() == nodeID {
    91  			return node.Key(), true
    92  		}
    93  	}
    94  	return nil, false
    95  }
    96  
    97  // Shutdown shuts down the controller and all running mesh nodes.
    98  func (r *PeerContainerReconciler) Shutdown(ctx context.Context) {
    99  	r.mu.Lock()
   100  	defer r.mu.Unlock()
   101  	for id, node := range r.containerNodes {
   102  		log.FromContext(ctx).V(1).Info("Stopping mesh node for container", "container", id)
   103  		if err := node.Close(ctx); err != nil {
   104  			log.FromContext(ctx).Error(err, "Failed to stop mesh node for container")
   105  		}
   106  		delete(r.containerNodes, id)
   107  	}
   108  }
   109  
   110  // Reconcile reconciles a PeerContainer.
   111  func (r *PeerContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
   112  	r.mu.Lock()
   113  	defer r.mu.Unlock()
   114  	log := log.FromContext(ctx)
   115  	if !r.Host.Started() {
   116  		log.Info("Controller is not ready yet, requeing reconcile request")
   117  		return ctrl.Result{
   118  			Requeue:      true,
   119  			RequeueAfter: 1 * time.Second,
   120  		}, nil
   121  	}
   122  	if r.Manager.ReconcileTimeout > 0 {
   123  		log.V(1).Info("Setting reconcile timeout", "timeout", r.Manager.ReconcileTimeout)
   124  		var cancel context.CancelFunc
   125  		ctx, cancel = context.WithTimeout(ctx, r.Manager.ReconcileTimeout)
   126  		defer cancel()
   127  	}
   128  	var container cniv1.PeerContainer
   129  	if err := r.Get(ctx, req.NamespacedName, &container); err != nil {
   130  		if client.IgnoreNotFound(err) == nil {
   131  			return ctrl.Result{}, nil
   132  		}
   133  		log.Error(err, "Failed to get container")
   134  		return ctrl.Result{}, err
   135  	}
   136  	if container.Spec.NodeName != r.Host.ID().String() {
   137  		// This container is not for this node, so we don't care about it.
   138  		log.V(1).Info("Ignoring container for another node")
   139  		return ctrl.Result{}, nil
   140  	}
   141  	// Always ensure the type meta is set
   142  	container.TypeMeta = cniv1.PeerContainerTypeMeta
   143  	if container.GetDeletionTimestamp() != nil {
   144  		// Stop the mesh node for this container.
   145  		log.Info("Tearing down mesh node for container")
   146  		return ctrl.Result{}, r.teardownPeerContainer(ctx, req, &container)
   147  	}
   148  	// Reconcile the mesh node for this container.
   149  	log.Info("Reconciling mesh node for container")
   150  	return ctrl.Result{}, r.reconcilePeerContainer(ctx, req, &container)
   151  }
   152  
   153  // NoOpStorageCloser wraps the storage provider with a no-op closer so
   154  // that mesh nodes will not close the storage provider.
   155  type NoOpStorageCloser struct {
   156  	meshstorage.Provider
   157  }
   158  
   159  // Close is a no-op.
   160  func (n *NoOpStorageCloser) Close() error {
   161  	return nil
   162  }
   163  
   164  // reconcilePeerContainer reconciles the given PeerContainer.
   165  func (r *PeerContainerReconciler) reconcilePeerContainer(ctx context.Context, req ctrl.Request, container *cniv1.PeerContainer) error {
   166  	log := log.FromContext(ctx)
   167  
   168  	// Make sure the finalizer is present first.
   169  	if !controllerutil.ContainsFinalizer(container, cniv1.PeerContainerFinalizer) {
   170  		updated := controllerutil.AddFinalizer(container, cniv1.PeerContainerFinalizer)
   171  		if updated {
   172  			log.V(1).Info("Adding finalizer to container")
   173  			if err := r.Update(ctx, container); err != nil {
   174  				return fmt.Errorf("failed to add finalizer: %w", err)
   175  			}
   176  			return nil
   177  		}
   178  	}
   179  
   180  	// Check if we have registered the node yet
   181  	node, ok := r.containerNodes[req.NamespacedName]
   182  	if !ok {
   183  		// We need to create the node.
   184  		nodeID := container.Spec.NodeID
   185  		log.Info("Webmesh node for container not found, we must need to create it")
   186  		log.V(1).Info("Creating new webmesh node with container spec", "spec", container.Spec)
   187  		key, err := crypto.GenerateKey()
   188  		if err != nil {
   189  			return fmt.Errorf("failed to generate key: %w", err)
   190  		}
   191  		var ipv4addr string
   192  		if !container.Spec.DisableIPv4 && container.Status.IPv4Address == "" {
   193  			// If the container does not have an IPv4 address and we are not disabling
   194  			// IPv4, use the default plugin to allocate one.
   195  			err = r.Host.IPAM().Locker().Acquire(ctx)
   196  			if err != nil {
   197  				return fmt.Errorf("failed to acquire IPAM lock: %w", err)
   198  			}
   199  			defer r.Host.IPAM().Locker().Release(ctx)
   200  			alloc, err := r.Host.IPAM().Allocate(ctx, meshtypes.NodeID(nodeID))
   201  			if err != nil {
   202  				return fmt.Errorf("failed to allocate IPv4 address: %w", err)
   203  			}
   204  			ipv4addr = alloc.String()
   205  		}
   206  		// Go ahead and register a Peer for this node.
   207  		encoded, err := key.PublicKey().Encode()
   208  		if err != nil {
   209  			return fmt.Errorf("failed to encode public key: %w", err)
   210  		}
   211  		peer := meshtypes.MeshNode{
   212  			MeshNode: &v1.MeshNode{
   213  				Id:              nodeID,
   214  				PublicKey:       encoded,
   215  				ZoneAwarenessID: container.Spec.NodeName,
   216  				PrivateIPv4:     ipv4addr,
   217  				PrivateIPv6: func() string {
   218  					if container.Spec.DisableIPv6 {
   219  						return ""
   220  					}
   221  					return netutil.AssignToPrefix(r.Host.Node().Network().NetworkV6(), key.PublicKey()).String()
   222  				}(),
   223  			},
   224  		}
   225  		log.Info("Registering peer with meshdb", "peer", peer.MeshNode)
   226  		if err := r.Provider.MeshDB().Peers().Put(ctx, peer); err != nil {
   227  			return fmt.Errorf("failed to register peer: %w", err)
   228  		}
   229  		// Create the mesh node.
   230  		r.containerNodes[req.NamespacedName] = NewNode(logging.NewLogger(container.Spec.LogLevel, "json"), meshnode.Config{
   231  			Key:             key,
   232  			NodeID:          nodeID,
   233  			ZoneAwarenessID: container.Spec.NodeName,
   234  			DisableIPv4:     container.Spec.DisableIPv4,
   235  			DisableIPv6:     container.Spec.DisableIPv6,
   236  		})
   237  		// Update the status to created.
   238  		log.Info("Updating container interface status to created")
   239  		container.Status.InterfaceStatus = cniv1.InterfaceStatusCreated
   240  		if err := r.updateContainerStatus(ctx, container); err != nil {
   241  			return fmt.Errorf("failed to update status: %w", err)
   242  		}
   243  		return nil
   244  	}
   245  
   246  	log = log.WithValues("nodeID", node.ID())
   247  
   248  	// If the node is not started, start it.
   249  	if !node.Started() {
   250  		log.Info("Starting webmesh node for container")
   251  		rtt := meshtransport.JoinRoundTripperFunc(func(ctx context.Context, _ *v1.JoinRequest) (*v1.JoinResponse, error) {
   252  			// Retrieve the peer we created earlier
   253  			peer, err := r.Provider.MeshDB().Peers().Get(ctx, node.ID())
   254  			if err != nil {
   255  				return nil, fmt.Errorf("failed to get registered peer for container: %w", err)
   256  			}
   257  			// Compute the current topology for the container.
   258  			peers, err := meshnet.WireGuardPeersFor(ctx, r.Provider.MeshDB(), node.ID())
   259  			if err != nil {
   260  				return nil, fmt.Errorf("failed to get peers for container: %w", err)
   261  			}
   262  			return &v1.JoinResponse{
   263  				MeshDomain: r.Host.Node().Domain(),
   264  				// We always return both networks regardless of IP preferences.
   265  				NetworkIPv4: r.Host.Node().Network().NetworkV4().String(),
   266  				NetworkIPv6: r.Host.Node().Network().NetworkV6().String(),
   267  				// Addresses as allocated above.
   268  				AddressIPv4: peer.PrivateIPv4,
   269  				AddressIPv6: peer.PrivateIPv6,
   270  				Peers:       peers,
   271  			}, nil
   272  		})
   273  		err := node.Connect(ctx, meshnode.ConnectOptions{
   274  			StorageProvider:  &NoOpStorageCloser{r.Provider},
   275  			MaxJoinRetries:   10,
   276  			JoinRoundTripper: rtt,
   277  			LeaveRoundTripper: meshtransport.LeaveRoundTripperFunc(func(ctx context.Context, req *v1.LeaveRequest) (*v1.LeaveResponse, error) {
   278  				// No-op, we clean up in the finalizers
   279  				return &v1.LeaveResponse{}, nil
   280  			}),
   281  			NetworkOptions: meshnet.Options{
   282  				NetNs:           container.Spec.Netns,
   283  				InterfaceName:   container.Spec.IfName,
   284  				ForceReplace:    true,
   285  				MTU:             container.Spec.MTU,
   286  				ZoneAwarenessID: container.Spec.NodeName,
   287  				DisableIPv4:     container.Spec.DisableIPv4,
   288  				DisableIPv6:     container.Spec.DisableIPv6,
   289  				// Maybe by configuration?
   290  				RecordMetrics:         false,
   291  				RecordMetricsInterval: 0,
   292  			},
   293  			DirectPeers: func() map[meshtypes.NodeID]v1.ConnectProtocol {
   294  				peers := make(map[meshtypes.NodeID]v1.ConnectProtocol)
   295  				for _, n := range r.containerNodes {
   296  					if n.ID() == node.ID() {
   297  						continue
   298  					}
   299  					peers[n.ID()] = v1.ConnectProtocol_CONNECT_NATIVE
   300  				}
   301  				return peers
   302  			}(),
   303  			PreferIPv6: !container.Spec.DisableIPv6,
   304  		})
   305  		if err != nil {
   306  			log.Error(err, "Failed to connect meshnode to network")
   307  			r.setFailedStatus(ctx, container, err)
   308  			// Create a new node on the next reconcile.
   309  			delete(r.containerNodes, req.NamespacedName)
   310  			return fmt.Errorf("failed to connect node: %w", err)
   311  		}
   312  		// Update the status to starting.
   313  		log.Info("Updating container interface status to starting")
   314  		container.Status.InterfaceStatus = cniv1.InterfaceStatusStarting
   315  		if err := r.updateContainerStatus(ctx, container); err != nil {
   316  			return fmt.Errorf("failed to update status: %w", err)
   317  		}
   318  		return nil
   319  	}
   320  
   321  	log.Info("Ensuring the container webmesh node is ready")
   322  	select {
   323  	case <-node.Ready():
   324  		hwaddr, _ := node.Network().WireGuard().HardwareAddr()
   325  		log.Info("Webmesh node for container is running",
   326  			"interfaceName", node.Network().WireGuard().Name(),
   327  			"macAddress", hwaddr.String(),
   328  			"ipv4Address", validOrNone(node.Network().WireGuard().AddressV4()),
   329  			"ipv4Address", validOrNone(node.Network().WireGuard().AddressV6()),
   330  			"networkV4", validOrNone(node.Network().NetworkV4()),
   331  			"networkV6", validOrNone(node.Network().NetworkV6()),
   332  		)
   333  		updated, err := r.ensureInterfaceReadyStatus(ctx, container, node)
   334  		if err != nil {
   335  			log.Error(err, "Failed to update container status")
   336  			return fmt.Errorf("failed to update container status: %w", err)
   337  		}
   338  		if updated {
   339  			// Return and continue on the next reconcile.
   340  			return nil
   341  		}
   342  	case <-ctx.Done():
   343  		// Update the status to failed.
   344  		log.Error(ctx.Err(), "Timed out waiting for mesh node to start")
   345  		// Don't delete the node or set it to failed yet, maybe it'll be ready on the next reconcile.
   346  		return ctx.Err()
   347  	}
   348  
   349  	// Register the node to the storage provider.
   350  	wireguardPort, err := node.Network().WireGuard().ListenPort()
   351  	if err != nil {
   352  		// Something went terribly wrong, we need to recreate the node.
   353  		defer func() {
   354  			if err := node.Close(ctx); err != nil {
   355  				log.Error(err, "Failed to stop mesh node for container")
   356  			}
   357  		}()
   358  		delete(r.containerNodes, req.NamespacedName)
   359  		r.setFailedStatus(ctx, container, err)
   360  		return fmt.Errorf("failed to get wireguard port: %w", err)
   361  	}
   362  	encoded, err := node.Key().PublicKey().Encode()
   363  	if err != nil {
   364  		// Something went terribly wrong, we need to recreate the node.
   365  		defer func() {
   366  			if err := node.Close(ctx); err != nil {
   367  				log.Error(err, "Failed to stop mesh node for container")
   368  			}
   369  		}()
   370  		delete(r.containerNodes, req.NamespacedName)
   371  		r.setFailedStatus(ctx, container, err)
   372  		return fmt.Errorf("failed to encode public key: %w", err)
   373  	}
   374  	// Detect the current endpoints on the machine.
   375  	eps, err := endpoints.Detect(ctx, endpoints.DetectOpts{
   376  		DetectPrivate:        true, // Required for finding endpoints for other containers on the local node.
   377  		DetectIPv6:           !container.Spec.DisableIPv6,
   378  		AllowRemoteDetection: r.Manager.RemoteEndpointDetection,
   379  		SkipInterfaces: func() []string {
   380  			var out []string
   381  			for _, n := range r.containerNodes {
   382  				if n.Started() {
   383  					out = append(out, n.Network().WireGuard().Name())
   384  				}
   385  			}
   386  			return out
   387  		}(),
   388  	})
   389  	if err != nil {
   390  		// Try again on the next reconcile.
   391  		return fmt.Errorf("failed to detect endpoints: %w", err)
   392  	}
   393  	var wgeps []string
   394  	for _, ep := range eps.AddrPorts(uint16(wireguardPort)) {
   395  		wgeps = append(wgeps, ep.String())
   396  	}
   397  	// Register the peer's endpoints.
   398  	log.Info("Registering peer endpoints",
   399  		"wireguardPort", wireguardPort,
   400  		"primaryEndpoint", eps.FirstPublicAddr().String(),
   401  		"wireguardEndpoints", wgeps,
   402  	)
   403  	err = r.Provider.MeshDB().Peers().Put(ctx, meshtypes.MeshNode{
   404  		MeshNode: &v1.MeshNode{
   405  			Id:                 node.ID().String(),
   406  			PublicKey:          encoded,
   407  			WireguardEndpoints: wgeps,
   408  			ZoneAwarenessID:    container.Spec.NodeName,
   409  			PrivateIPv4:        validOrEmpty(node.Network().WireGuard().AddressV4()),
   410  			PrivateIPv6:        validOrEmpty(node.Network().WireGuard().AddressV6()),
   411  		},
   412  	})
   413  	if err != nil {
   414  		// Try again on the next reconcile.
   415  		log.Error(err, "Failed to register peer")
   416  		return fmt.Errorf("failed to register peer: %w", err)
   417  	}
   418  	// Make sure all MeshEdges are up to date for this node.
   419  	log.Info("Forcing sync of peers and topology")
   420  	peers, err := r.Provider.MeshDB().Peers().List(
   421  		ctx,
   422  		meshstorage.FilterAgainstNode(node.ID()),
   423  		meshstorage.FilterByZoneID(container.Spec.NodeName),
   424  	)
   425  	if err != nil {
   426  		// Try again on the next reconcile.
   427  		log.Error(err, "Failed to list peers")
   428  		return fmt.Errorf("failed to list peers: %w", err)
   429  	}
   430  	for _, peer := range peers {
   431  		if err := r.Provider.MeshDB().Peers().PutEdge(ctx, meshtypes.MeshEdge{MeshEdge: &v1.MeshEdge{
   432  			Source: node.ID().String(),
   433  			Target: peer.NodeID().String(),
   434  			Weight: 50,
   435  		}}); err != nil {
   436  			// Try again on the next reconcile.
   437  			log.Error(err, "Failed to create edge", "targetNode", peer.NodeID())
   438  			return fmt.Errorf("failed to create edge: %w", err)
   439  		}
   440  	}
   441  	// Force a sync of the node.
   442  	err = node.Network().Peers().Sync(ctx)
   443  	if err != nil {
   444  		log.Error(err, "Failed to sync peers")
   445  		// We don't return an error because the peer will eventually sync on its own.
   446  	}
   447  	return nil
   448  }
   449  
   450  // teardownPeerContainer tears down the given PeerContainer.
   451  func (r *PeerContainerReconciler) teardownPeerContainer(ctx context.Context, req ctrl.Request, container *cniv1.PeerContainer) error {
   452  	log := log.FromContext(ctx)
   453  	node, ok := r.containerNodes[req.NamespacedName]
   454  	if !ok {
   455  		log.Info("Mesh node for container not found, we must have already deleted")
   456  	} else {
   457  		if err := node.Close(ctx); err != nil {
   458  			log.Error(err, "Failed to stop mesh node for container")
   459  		}
   460  		delete(r.containerNodes, req.NamespacedName)
   461  	}
   462  	// Make sure we've deleted the mesh peer from the database.
   463  	if err := r.Provider.MeshDB().Peers().Delete(ctx, meshtypes.NodeID(req.Name)); err != nil {
   464  		if !mesherrors.Is(err, mesherrors.ErrNodeNotFound) {
   465  			log.Error(err, "Failed to delete peer from meshdb")
   466  			return fmt.Errorf("failed to delete peer: %w", err)
   467  		}
   468  	}
   469  	if controllerutil.ContainsFinalizer(container, cniv1.PeerContainerFinalizer) {
   470  		updated := controllerutil.RemoveFinalizer(container, cniv1.PeerContainerFinalizer)
   471  		if updated {
   472  			log.Info("Removing finalizer from container")
   473  			if err := r.Update(ctx, container); err != nil {
   474  				return fmt.Errorf("failed to remove finalizer: %w", err)
   475  			}
   476  		}
   477  	}
   478  	return nil
   479  }
   480  
   481  func (r *PeerContainerReconciler) ensureInterfaceReadyStatus(ctx context.Context, container *cniv1.PeerContainer, node meshnode.Node) (updated bool, err error) {
   482  	log := log.FromContext(ctx)
   483  	// Update the status to running and sets its IP address.
   484  	var updateStatus bool
   485  	origStatus := container.Status
   486  	addrV4 := validOrEmpty(node.Network().WireGuard().AddressV4())
   487  	addrV6 := validOrEmpty(node.Network().WireGuard().AddressV6())
   488  	netv4 := validOrEmpty(node.Network().NetworkV4())
   489  	netv6 := validOrEmpty(node.Network().NetworkV6())
   490  	if container.Status.InterfaceStatus != cniv1.InterfaceStatusRunning {
   491  		// Update the status to running and sets its IP address.
   492  		container.Status.InterfaceStatus = cniv1.InterfaceStatusRunning
   493  		updateStatus = true
   494  	}
   495  	hwaddr, _ := node.Network().WireGuard().HardwareAddr()
   496  	if container.Status.MACAddress != hwaddr.String() {
   497  		container.Status.MACAddress = hwaddr.String()
   498  		updateStatus = true
   499  	}
   500  	if r.dns != nil {
   501  		// Add ourself as a DNS server for the container.
   502  		var addr netip.Addr
   503  		if container.Spec.DisableIPv4 && r.Host.Node().Network().WireGuard().AddressV6().IsValid() {
   504  			addr = r.Host.Node().Network().WireGuard().AddressV6().Addr()
   505  		} else if r.Host.Node().Network().WireGuard().AddressV4().IsValid() {
   506  			// Prefer IPv4 if it's available.
   507  			addr = r.Host.Node().Network().WireGuard().AddressV4().Addr()
   508  		}
   509  		if addr.IsValid() {
   510  			addrport := netip.AddrPortFrom(addr, uint16(r.dns.ListenPort()))
   511  			if len(container.Status.DNSServers) == 0 || container.Status.DNSServers[0] != addrport.String() {
   512  				container.Status.DNSServers = []string{addrport.String()}
   513  				updateStatus = true
   514  			}
   515  		}
   516  	}
   517  	if container.Status.IPv4Address != addrV4 {
   518  		container.Status.IPv4Address = addrV4
   519  		updateStatus = true
   520  	}
   521  	if container.Status.IPv6Address != addrV6 {
   522  		container.Status.IPv6Address = addrV6
   523  		updateStatus = true
   524  	}
   525  	if container.Status.NetworkV4 != netv4 {
   526  		container.Status.NetworkV4 = netv4
   527  		updateStatus = true
   528  	}
   529  	if container.Status.NetworkV6 != netv6 {
   530  		container.Status.NetworkV6 = netv6
   531  		updateStatus = true
   532  	}
   533  	if container.Status.InterfaceName != node.Network().WireGuard().Name() {
   534  		container.Status.InterfaceName = node.Network().WireGuard().Name()
   535  		updateStatus = true
   536  	}
   537  	if container.Status.Error != "" {
   538  		container.Status.Error = ""
   539  		updateStatus = true
   540  	}
   541  	if updateStatus {
   542  		log.Info("Updating container interface status",
   543  			"newStatus", container.Status,
   544  			"oldStatus", origStatus,
   545  		)
   546  		return true, r.updateContainerStatus(ctx, container)
   547  	}
   548  	return false, nil
   549  }
   550  
   551  func (r *PeerContainerReconciler) setFailedStatus(ctx context.Context, container *cniv1.PeerContainer, reason error) {
   552  	container.Status.InterfaceStatus = cniv1.InterfaceStatusFailed
   553  	container.Status.Error = reason.Error()
   554  	err := r.updateContainerStatus(ctx, container)
   555  	if err != nil {
   556  		log.FromContext(ctx).Error(err, "Failed to update container status")
   557  	}
   558  }
   559  
   560  func (r *PeerContainerReconciler) updateContainerStatus(ctx context.Context, container *cniv1.PeerContainer) error {
   561  	container.SetManagedFields(nil)
   562  	err := r.Status().Patch(ctx,
   563  		container,
   564  		client.Apply,
   565  		client.ForceOwnership,
   566  		client.FieldOwner(cniv1.FieldOwner),
   567  	)
   568  	if err != nil {
   569  		return fmt.Errorf("failed to update status: %w", err)
   570  	}
   571  	return nil
   572  }