github.com/webmeshproj/webmesh-cni@v0.0.27/internal/controllers/remotenetwork_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  	storageprovider "github.com/webmeshproj/storage-provider-k8s/provider"
    28  	meshlogging "github.com/webmeshproj/webmesh/pkg/logging"
    29  	meshnet "github.com/webmeshproj/webmesh/pkg/meshnet"
    30  	mesheps "github.com/webmeshproj/webmesh/pkg/meshnet/endpoints"
    31  	netutil "github.com/webmeshproj/webmesh/pkg/meshnet/netutil"
    32  	meshsys "github.com/webmeshproj/webmesh/pkg/meshnet/system"
    33  	meshtransport "github.com/webmeshproj/webmesh/pkg/meshnet/transport"
    34  	meshnode "github.com/webmeshproj/webmesh/pkg/meshnode"
    35  	meshplugins "github.com/webmeshproj/webmesh/pkg/plugins"
    36  	meshdns "github.com/webmeshproj/webmesh/pkg/services/meshdns"
    37  	meshtypes "github.com/webmeshproj/webmesh/pkg/storage/types"
    38  	corev1 "k8s.io/api/core/v1"
    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  	"github.com/webmeshproj/webmesh-cni/internal/ipam"
    48  	"github.com/webmeshproj/webmesh-cni/internal/metadata"
    49  	"github.com/webmeshproj/webmesh-cni/internal/types"
    50  )
    51  
    52  //+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
    53  //+kubebuilder:rbac:groups=cni.webmesh.io,resources=remotenetworks,verbs=get;list;watch;create;update;patch;delete
    54  //+kubebuilder:rbac:groups=cni.webmesh.io,resources=remotenetworks/status,verbs=get;update;patch
    55  //+kubebuilder:rbac:groups=cni.webmesh.io,resources=remotenetworks/finalizers,verbs=update
    56  
    57  // RemoteNetworkReconciler ensures bridge connections to other clusters.
    58  type RemoteNetworkReconciler struct {
    59  	client.Client
    60  	config.Config
    61  	Provider *storageprovider.Provider
    62  	HostNode host.Node
    63  	bridges  map[client.ObjectKey]meshnode.Node
    64  	dnssrv   *meshdns.Server
    65  	mu       sync.Mutex
    66  }
    67  
    68  // SetupWithManager sets up the controller with the Manager.
    69  func (r *RemoteNetworkReconciler) SetupWithManager(mgr ctrl.Manager) (err error) {
    70  	return ctrl.NewControllerManagedBy(mgr).
    71  		For(&cniv1.RemoteNetwork{}).
    72  		Complete(r)
    73  }
    74  
    75  // SetDNSServer sets the DNS server for the controller.
    76  func (r *RemoteNetworkReconciler) SetDNSServer(srv *meshdns.Server) {
    77  	r.mu.Lock()
    78  	defer r.mu.Unlock()
    79  	r.dnssrv = srv
    80  }
    81  
    82  func (r *RemoteNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    83  	r.mu.Lock()
    84  	defer r.mu.Unlock()
    85  	log := log.FromContext(ctx)
    86  	if !r.HostNode.Started() {
    87  		// Request a requeue until the host is started.
    88  		log.Info("Host node not ready yet, requeuing")
    89  		return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 2}, nil
    90  	}
    91  	if r.bridges == nil {
    92  		r.bridges = make(map[client.ObjectKey]meshnode.Node)
    93  	}
    94  	if r.Manager.ReconcileTimeout > 0 {
    95  		log.V(1).Info("Setting reconcile timeout", "timeout", r.Manager.ReconcileTimeout)
    96  		var cancel context.CancelFunc
    97  		ctx, cancel = context.WithTimeout(ctx, r.Manager.ReconcileTimeout)
    98  		defer cancel()
    99  	}
   100  	var nw cniv1.RemoteNetwork
   101  	if err := r.Get(ctx, req.NamespacedName, &nw); err != nil {
   102  		if client.IgnoreNotFound(err) != nil {
   103  			log.Error(err, "Failed to lookup remote network")
   104  			return ctrl.Result{}, err
   105  		}
   106  		return ctrl.Result{}, nil
   107  	}
   108  	// Always ensure the type meta is set
   109  	nw.TypeMeta = cniv1.RemoteNetworkTypeMeta
   110  	if nw.GetDeletionTimestamp() != nil {
   111  		// Stop the mesh node for this container.
   112  		log.Info("Tearing down remote network bridge")
   113  		return ctrl.Result{}, r.reconcileRemove(ctx, req.NamespacedName, &nw)
   114  	}
   115  	log.Info("Reconciling remote network bridge")
   116  	err := r.reconcileNetwork(ctx, req.NamespacedName, &nw)
   117  	if err != nil {
   118  		log.Error(err, "Failed to reconcile remote network bridge")
   119  		return ctrl.Result{}, err
   120  	}
   121  	if nw.Spec.AuthMethod != cniv1.RemoteAuthMethodKubernetes {
   122  		// Request a requeue in a minute to ensure the bridge is still running
   123  		// and all node edges are up to date.
   124  		var requeueAfter time.Duration
   125  		if nw.Spec.CheckInterval != nil {
   126  			requeueAfter = nw.Spec.CheckInterval.Duration
   127  		} else {
   128  			requeueAfter = time.Minute
   129  		}
   130  		return ctrl.Result{Requeue: true, RequeueAfter: requeueAfter}, nil
   131  	}
   132  	return ctrl.Result{}, nil
   133  }
   134  
   135  func (r *RemoteNetworkReconciler) reconcileNetwork(ctx context.Context, key client.ObjectKey, nw *cniv1.RemoteNetwork) error {
   136  	log := log.FromContext(ctx)
   137  
   138  	// Ensure the finalizer on the network.
   139  	if !controllerutil.ContainsFinalizer(nw, cniv1.RemoteNetworkFinalizer) {
   140  		updated := controllerutil.AddFinalizer(nw, cniv1.RemoteNetworkFinalizer)
   141  		if updated {
   142  			log.Info("Adding finalizer to remote network")
   143  			if err := r.Update(ctx, nw); err != nil {
   144  				return fmt.Errorf("failed to add finalizer: %w", err)
   145  			}
   146  			return nil
   147  		}
   148  	}
   149  
   150  	// Fetch any credentials if provided.
   151  	var creds map[string][]byte
   152  	if nw.Spec.Credentials != nil {
   153  		var secret corev1.Secret
   154  		if err := r.Get(ctx, client.ObjectKey{
   155  			Name: nw.Spec.Credentials.Name,
   156  			Namespace: func() string {
   157  				if nw.Spec.Credentials.Namespace != "" {
   158  					return nw.Spec.Credentials.Namespace
   159  				}
   160  				return key.Namespace
   161  			}(),
   162  		}, &secret); err != nil {
   163  			if client.IgnoreNotFound(err) != nil {
   164  				return fmt.Errorf("failed to fetch credentials: %w", err)
   165  			}
   166  		}
   167  		creds = secret.Data
   168  	}
   169  
   170  	// Ensure the bridge exists.
   171  	bridge, ok := r.bridges[key]
   172  	if !ok {
   173  		// Create the bridge node with the configured attributes.
   174  		log.Info("Webmesh node for bridge not found, we must need to create it")
   175  		nodeID := string(r.HostNode.ID())
   176  		privkey := r.HostNode.Node().Key()
   177  		if nw.Spec.AuthMethod == cniv1.RemoteAuthMethodNative && len(creds) == 0 {
   178  			// If there are no credentials configured, we assume we are using ID auth.
   179  			// So our node ID needs to match the key.
   180  			nodeID = privkey.ID()
   181  		}
   182  		node := NewNode(meshlogging.NewLogger(r.Host.LogLevel, "json"), meshnode.Config{
   183  			Key:             privkey,
   184  			NodeID:          nodeID,
   185  			ZoneAwarenessID: r.Host.NodeID,
   186  			DisableIPv4:     nw.Spec.Network.DisableIPv4,
   187  			DisableIPv6:     nw.Spec.Network.DisableIPv6,
   188  		})
   189  		r.bridges[key] = node
   190  		// Update the status to created.
   191  		log.Info("Updating bridge interface status to created")
   192  		nw.Status.BridgeStatus = cniv1.BridgeStatusCreated
   193  		if err := r.updateBridgeStatus(ctx, nw); err != nil {
   194  			return fmt.Errorf("failed to update bridge status: %w", err)
   195  		}
   196  		return nil
   197  	}
   198  
   199  	// Ensure the bridge is running.
   200  	if !bridge.Started() {
   201  		log.Info("Starting webmesh node for remote network bridge")
   202  		var err error
   203  		switch nw.Spec.AuthMethod {
   204  		case cniv1.RemoteAuthMethodNone, cniv1.RemoteAuthMethodNative:
   205  			err = r.connectWithWebmeshAPI(ctx, nw, creds, bridge)
   206  		case cniv1.RemoteAuthMethodKubernetes:
   207  			kubeconfig, ok := creds[cniv1.KubeconfigKey]
   208  			if !ok {
   209  				err = fmt.Errorf("kubeconfig not provided in credentials")
   210  				break
   211  			}
   212  			err = r.connectWithKubeconfig(ctx, nw, kubeconfig, bridge)
   213  		default:
   214  			err = fmt.Errorf("unknown auth method: %s", nw.Spec.AuthMethod)
   215  		}
   216  		if err != nil {
   217  			log.Error(err, "Failed to connect bridge node to remote network")
   218  			r.setFailedStatus(ctx, nw, err)
   219  			// Create a new node on the next reconcile.
   220  			delete(r.bridges, key)
   221  			return fmt.Errorf("failed to connect bridge node: %w", err)
   222  		}
   223  		// Update the status to starting.
   224  		log.Info("Updating bridge interface status to starting")
   225  		nw.Status.BridgeStatus = cniv1.BridgeStatusStarting
   226  		if err := r.updateBridgeStatus(ctx, nw); err != nil {
   227  			return fmt.Errorf("failed to update bridge status: %w", err)
   228  		}
   229  		return nil
   230  	}
   231  
   232  	// Make sure the bridge is ready
   233  	log.Info("Ensuring the bridge node is ready")
   234  	select {
   235  	case <-bridge.Ready():
   236  		hwaddr, _ := bridge.Network().WireGuard().HardwareAddr()
   237  		log.Info("Webmesh node for remote network bridge is running",
   238  			"interfaceName", bridge.Network().WireGuard().Name(),
   239  			"macAddress", hwaddr.String(),
   240  			"ipv4Address", validOrNone(bridge.Network().WireGuard().AddressV4()),
   241  			"ipv4Address", validOrNone(bridge.Network().WireGuard().AddressV6()),
   242  			"networkV4", validOrNone(bridge.Network().NetworkV4()),
   243  			"networkV6", validOrNone(bridge.Network().NetworkV6()),
   244  		)
   245  		err := r.ensureBridgeReadyStatus(ctx, nw, bridge)
   246  		if err != nil {
   247  			log.Error(err, "Failed to update bridge status")
   248  			return fmt.Errorf("failed to update bridge status: %w", err)
   249  		}
   250  	case <-ctx.Done():
   251  		// Update the status to failed.
   252  		log.Error(ctx.Err(), "Timed out waiting for bridge node to start")
   253  		// Don't delete the node or set it to failed yet, maybe it'll be ready on the next reconcile.
   254  		return ctx.Err()
   255  	}
   256  
   257  	// Register the remote network with our dns server if enabled.
   258  	if nw.Spec.Network.ForwardDNS {
   259  		// We shouldn't have gotten this far without first checking the DNS server is set.
   260  		err := r.dnssrv.RegisterDomain(meshdns.DomainOptions{
   261  			NodeID:              bridge.ID(),
   262  			MeshDomain:          bridge.Domain(),
   263  			MeshStorage:         bridge.Storage(),
   264  			IPv6Only:            nw.Spec.Network.DisableIPv4,
   265  			SubscribeForwarders: true,
   266  		})
   267  		if err != nil {
   268  			return fmt.Errorf("failed to register domain with dns server: %w", err)
   269  		}
   270  	}
   271  
   272  	// Lookup the current leader on the remote side and grab all the routes
   273  	// of the remote network that we care about.
   274  	leader, err := bridge.Storage().Consensus().GetLeader(ctx)
   275  	if err != nil {
   276  		return fmt.Errorf("failed to get remote consensus leader: %w", err)
   277  	}
   278  	routes, err := bridge.Storage().MeshDB().Networking().GetRoutesByNode(ctx, meshtypes.NodeID(leader.GetId()))
   279  	if err != nil {
   280  		return fmt.Errorf("failed to get remote routes: %w", err)
   281  	}
   282  	var destinationCIDRs []string
   283  	for _, rt := range routes {
   284  		for _, cidr := range rt.DestinationPrefixes() {
   285  			if cidr.Addr().IsUnspecified() || cidr.Addr().IsLinkLocalUnicast() || cidr.Addr().IsLinkLocalMulticast() {
   286  				continue
   287  			}
   288  			if !r.Host.Network.CIDRsContain(cidr) {
   289  				destinationCIDRs = append(destinationCIDRs, cidr.String())
   290  			}
   291  		}
   292  	}
   293  	err = r.Provider.MeshDB().Networking().PutRoute(ctx, meshtypes.Route{
   294  		Route: &v1.Route{
   295  			Name:             r.localRouteName(nw),
   296  			Node:             r.HostNode.ID().String(),
   297  			DestinationCIDRs: destinationCIDRs,
   298  		},
   299  	})
   300  	if err != nil {
   301  		log.Error(err, "Failed to add local routes to remote network")
   302  		return fmt.Errorf("failed to add local routes to remote network: %w", err)
   303  	}
   304  	return bridge.Network().Peers().Sync(ctx)
   305  }
   306  
   307  func (r *RemoteNetworkReconciler) connectWithWebmeshAPI(ctx context.Context, nw *cniv1.RemoteNetwork, creds map[string][]byte, bridge meshnode.Node) error {
   308  	return fmt.Errorf("not implemented")
   309  }
   310  
   311  func (r *RemoteNetworkReconciler) connectWithKubeconfig(ctx context.Context, nw *cniv1.RemoteNetwork, kubeconfig []byte, bridge meshnode.Node) error {
   312  	log := log.FromContext(ctx)
   313  	// Detect the current endpoints on the machine.
   314  	eps, err := mesheps.Detect(ctx, mesheps.DetectOpts{
   315  		DetectPrivate:        true, // TODO: Not necessarily required in this case.
   316  		DetectIPv6:           !nw.Spec.Network.DisableIPv6,
   317  		AllowRemoteDetection: r.Manager.RemoteEndpointDetection,
   318  		SkipInterfaces: func() []string {
   319  			out := []string{r.HostNode.Node().Network().WireGuard().Name()}
   320  			for _, n := range r.bridges {
   321  				if n.Started() {
   322  					out = append(out, n.Network().WireGuard().Name())
   323  				}
   324  			}
   325  			return out
   326  		}(),
   327  	})
   328  	if err != nil {
   329  		return fmt.Errorf("failed to detect endpoints: %w", err)
   330  	}
   331  	encodedPubkey, err := bridge.Key().PublicKey().Encode()
   332  	if err != nil {
   333  		return fmt.Errorf("failed to encode public key: %w", err)
   334  	}
   335  	var bridgeFeatures []*v1.FeaturePort
   336  	if nw.Spec.Network.ForwardDNS {
   337  		if r.dnssrv == nil {
   338  			// Requeue until the DNS server is set.
   339  			return fmt.Errorf("no dns server running yet")
   340  		}
   341  		bridgeFeatures = append(bridgeFeatures, &v1.FeaturePort{
   342  			Feature: v1.Feature_MESH_DNS,
   343  			Port:    int32(r.dnssrv.ListenPort()),
   344  		})
   345  		bridgeFeatures = append(bridgeFeatures, &v1.FeaturePort{
   346  			Feature: v1.Feature_FORWARD_MESH_DNS,
   347  			Port:    int32(r.dnssrv.ListenPort()),
   348  		})
   349  	}
   350  	// Create a connection to the remote cluster storage
   351  	cfg, err := types.NewRestConfigFromBytes(kubeconfig)
   352  	if err != nil {
   353  		return fmt.Errorf("failed to create client from kubeconfig: %w", err)
   354  	}
   355  	namespace := func() string {
   356  		if nw.Spec.RemoteNamespace != "" {
   357  			return nw.Spec.RemoteNamespace
   358  		}
   359  		return r.Host.Namespace
   360  	}()
   361  	db, err := storageprovider.NewObserverWithConfig(cfg, storageprovider.Options{
   362  		NodeID:    bridge.ID().String(),
   363  		Namespace: namespace,
   364  	})
   365  	if err != nil {
   366  		return fmt.Errorf("failed to create meshdb observer: %w", err)
   367  	}
   368  	err = db.StartManaged(context.Background())
   369  	if err != nil {
   370  		return fmt.Errorf("failed to start meshdb observer: %w", err)
   371  	}
   372  	cleanFuncs := make([]func(), 0)
   373  	cleanFuncs = append(cleanFuncs, func() {
   374  		if err := db.Close(); err != nil {
   375  			log.Error(err, "Failed to stop meshdb observer")
   376  		}
   377  	})
   378  	handleErr := func(cause error) error {
   379  		// Iterate the clean functions in reverse order.
   380  		for i := len(cleanFuncs) - 1; i >= 0; i-- {
   381  			cleanFuncs[i]()
   382  		}
   383  		return cause
   384  	}
   385  	ok := db.WaitForCacheSync(ctx)
   386  	if !ok {
   387  		return handleErr(fmt.Errorf("failed to sync remote meshdb cache"))
   388  	}
   389  	// Retrieve the state of the remote network.
   390  	remoteState, err := db.MeshDB().MeshState().GetMeshState(ctx)
   391  	if err != nil {
   392  		return handleErr(fmt.Errorf("failed to get remote mesh state: %w", err))
   393  	}
   394  	// Create a peer for ourselves on the remote network.
   395  	var ipv4addr string
   396  	if !nw.Spec.Network.DisableIPv4 {
   397  		// Make sure we get an IPv4 allocation on the remote network.
   398  		ipam, err := ipam.NewAllocator(cfg, ipam.Config{
   399  			IPAM: meshplugins.IPAMConfig{
   400  				Storage: db.MeshDB(),
   401  			},
   402  			Lock: ipam.LockConfig{
   403  				ID:                 r.Host.NodeID,
   404  				Namespace:          namespace,
   405  				LockDuration:       r.Host.LockDuration,
   406  				LockAcquireTimeout: r.Host.LockAcquireTimeout,
   407  			},
   408  			Network: remoteState.NetworkV4(),
   409  		})
   410  		if err != nil {
   411  			return handleErr(fmt.Errorf("failed to create IPAM allocator: %w", err))
   412  		}
   413  		err = ipam.Locker().Acquire(ctx)
   414  		if err != nil {
   415  			return handleErr(fmt.Errorf("failed to acquire IPAM lock: %w", err))
   416  		}
   417  		defer ipam.Locker().Release(ctx)
   418  		alloc, err := ipam.Allocate(ctx, bridge.ID())
   419  		if err != nil {
   420  			return handleErr(fmt.Errorf("failed to allocate IPv4 address: %w", err))
   421  		}
   422  		ipv4addr = alloc.String()
   423  	}
   424  	peer := meshtypes.MeshNode{
   425  		MeshNode: &v1.MeshNode{
   426  			Id:              bridge.ID().String(),
   427  			PublicKey:       encodedPubkey,
   428  			ZoneAwarenessID: r.Host.NodeID,
   429  			PrivateIPv4:     ipv4addr,
   430  			PrivateIPv6: func() string {
   431  				if nw.Spec.Network.DisableIPv6 {
   432  					return ""
   433  				}
   434  				return netutil.AssignToPrefix(remoteState.NetworkV6(), bridge.Key().PublicKey()).String()
   435  			}(),
   436  			Features: bridgeFeatures,
   437  		},
   438  	}
   439  	log.Info("Registering ourselves with the remote meshdb", "peer", peer.MeshNode)
   440  	if err := db.MeshDB().Peers().Put(ctx, peer); err != nil {
   441  		return handleErr(fmt.Errorf("failed to register peer: %w", err))
   442  	}
   443  	cleanFuncs = append(cleanFuncs, func() {
   444  		if err := db.MeshDB().Peers().Delete(ctx, bridge.ID()); err != nil {
   445  			log.Error(err, "Failed to remove peer from remote meshdb")
   446  		}
   447  	})
   448  	// Create routes on the remote network for our local CIDRs.
   449  	log.Info("Registering local routes with the remote meshdb")
   450  	err = db.MeshDB().Networking().PutRoute(ctx, meshtypes.Route{
   451  		Route: &v1.Route{
   452  			Name: r.remoteRouteName(nw, bridge),
   453  			Node: bridge.ID().String(),
   454  			DestinationCIDRs: func() []string {
   455  				var out []string
   456  				for _, ep := range append(eps, r.Host.Network.CIDRs()...) {
   457  					out = append(out, ep.String())
   458  				}
   459  				return out
   460  			}(),
   461  		},
   462  	})
   463  	if err != nil {
   464  		return handleErr(fmt.Errorf("failed to register route: %w", err))
   465  	}
   466  	cleanFuncs = append(cleanFuncs, func() {
   467  		if err := db.MeshDB().Networking().DeleteRoute(ctx, r.remoteRouteName(nw, bridge)); err != nil {
   468  			log.Error(err, "Failed to remove route from remote meshdb")
   469  		}
   470  	})
   471  	// Add ourselves as an observer to the remote consensus group.
   472  	// This should trigger the remote CNI to edge us to all other remote CNI nodes.
   473  	err = db.Consensus().AddObserver(ctx, meshtypes.StoragePeer{
   474  		StoragePeer: &v1.StoragePeer{
   475  			Id:        bridge.ID().String(),
   476  			PublicKey: encodedPubkey,
   477  		},
   478  	})
   479  	if err != nil {
   480  		return handleErr(fmt.Errorf("failed to register with remote as observer: %w", err))
   481  	}
   482  	cleanFuncs = append(cleanFuncs, func() {
   483  		err = db.Consensus().RemovePeer(ctx, meshtypes.StoragePeer{
   484  			StoragePeer: &v1.StoragePeer{
   485  				Id: bridge.ID().String(),
   486  			},
   487  		}, false)
   488  		if err != nil {
   489  			log.Error(err, "Failed to remove peer from remote consensus group")
   490  		}
   491  	})
   492  	// Setup a join transport using the client to the remote network.
   493  	joinRTT := meshtransport.JoinRoundTripperFunc(func(ctx context.Context, _ *v1.JoinRequest) (*v1.JoinResponse, error) {
   494  		// Retrieve the peer we created earlier
   495  		peer, err := db.MeshDB().Peers().Get(ctx, bridge.ID())
   496  		if err != nil {
   497  			return nil, fmt.Errorf("failed to get registered peer for container: %w", err)
   498  		}
   499  		// Compute the current topology for the container.
   500  		peers, err := meshnet.WireGuardPeersFor(ctx, db.MeshDB(), bridge.ID())
   501  		if err != nil {
   502  			return nil, fmt.Errorf("failed to get peers for container: %w", err)
   503  		}
   504  		return &v1.JoinResponse{
   505  			MeshDomain: remoteState.Domain(),
   506  			// We always return both networks regardless of IP preferences.
   507  			NetworkIPv4: remoteState.NetworkV4().String(),
   508  			NetworkIPv6: remoteState.NetworkV6().String(),
   509  			// Addresses as allocated above.
   510  			AddressIPv4: peer.PrivateIPv4,
   511  			AddressIPv6: peer.PrivateIPv6,
   512  			Peers:       peers,
   513  		}, nil
   514  	})
   515  	leaveRTT := meshtransport.LeaveRoundTripperFunc(func(ctx context.Context, req *v1.LeaveRequest) (*v1.LeaveResponse, error) {
   516  		// We remove ourself from the remote network.
   517  		if err := db.MeshDB().Networking().DeleteRoute(ctx, r.remoteRouteName(nw, bridge)); err != nil {
   518  			log.Error(err, "Failed to remove route from remote meshdb")
   519  		}
   520  		if err := db.MeshDB().Peers().Delete(ctx, bridge.ID()); err != nil {
   521  			log.Error(err, "Failed to remove peer from remote meshdb")
   522  		}
   523  		return &v1.LeaveResponse{}, db.Consensus().RemovePeer(ctx, meshtypes.StoragePeer{
   524  			StoragePeer: &v1.StoragePeer{
   525  				Id: bridge.ID().String(),
   526  			},
   527  		}, false)
   528  	})
   529  	log.Info("Connecting to remote network")
   530  	err = bridge.Connect(r.HostNode.NodeContext(ctx), meshnode.ConnectOptions{
   531  		StorageProvider:   db,
   532  		MaxJoinRetries:    10,
   533  		JoinRoundTripper:  joinRTT,
   534  		LeaveRoundTripper: leaveRTT,
   535  		NetworkOptions: meshnet.Options{
   536  			ZoneAwarenessID: r.Host.NodeID,
   537  			DisableIPv4:     nw.Spec.Network.DisableIPv4,
   538  			DisableIPv6:     nw.Spec.Network.DisableIPv6,
   539  			// We don't want to use the default gateway routes broadcasted by
   540  			// the remote cluster because they will likely collide with our own.
   541  			DisableFullTunnel: true,
   542  			// Ignore routes to any metadata servers. But this may need to be
   543  			// more configurable.
   544  			IgnoreRoutes: append(r.Host.Network.CIDRs(), netip.PrefixFrom(metadata.DefaultServerAddress.Addr(), 32)),
   545  			ListenPort:   nw.Spec.Network.WireGuardPort,
   546  			MTU: func() int {
   547  				if nw.Spec.Network.MTU > 0 {
   548  					return nw.Spec.Network.MTU
   549  				}
   550  				return meshsys.DefaultMTU
   551  			}(),
   552  			InterfaceName: func() string {
   553  				if nw.Spec.Network.InterfaceName != "" {
   554  					return nw.Spec.Network.InterfaceName
   555  				}
   556  				return types.IfNameFromID(nw.GetName())
   557  			}(),
   558  			ForceReplace: true,
   559  			// Maybe by configuration?
   560  			RecordMetrics:         false,
   561  			RecordMetricsInterval: 0,
   562  		},
   563  		PreferIPv6: !nw.Spec.Network.DisableIPv6,
   564  	})
   565  	if err != nil {
   566  		return handleErr(fmt.Errorf("failed to connect to remote network: %w", err))
   567  	}
   568  	cleanFuncs = append(cleanFuncs, func() {
   569  		if err := bridge.Close(ctx); err != nil {
   570  			log.Error(err, "Failed to disconnect from remote network")
   571  		}
   572  	})
   573  	log.Info("Bridge node is connected, registering endpoints with remote network")
   574  	wireguardPort, err := bridge.Network().WireGuard().ListenPort()
   575  	if err != nil {
   576  		return handleErr(fmt.Errorf("failed to get wireguard listen port: %w", err))
   577  	}
   578  	var wgeps []string
   579  	for _, ep := range eps.AddrPorts(uint16(wireguardPort)) {
   580  		wgeps = append(wgeps, ep.String())
   581  	}
   582  	// Patch the peer we created earlier with our wireguard endpoints
   583  	peer = meshtypes.MeshNode{
   584  		MeshNode: &v1.MeshNode{
   585  			Id:        bridge.ID().String(),
   586  			PublicKey: encodedPubkey,
   587  			PrimaryEndpoint: func() string {
   588  				if eps.FirstPublicAddr().IsValid() {
   589  					return eps.FirstPublicAddr().String()
   590  				}
   591  				return eps.PrivateAddrs()[0].String()
   592  			}(),
   593  			WireguardEndpoints: wgeps,
   594  			ZoneAwarenessID:    r.Host.NodeID,
   595  			PrivateIPv4:        ipv4addr,
   596  			PrivateIPv6: func() string {
   597  				if nw.Spec.Network.DisableIPv6 {
   598  					return ""
   599  				}
   600  				return netutil.AssignToPrefix(remoteState.NetworkV6(), bridge.Key().PublicKey()).String()
   601  			}(),
   602  			Features: bridgeFeatures,
   603  		},
   604  	}
   605  	log.Info("Updating peer with wireguard endpoints", "peer", peer.MeshNode)
   606  	if err := db.MeshDB().Peers().Put(ctx, peer); err != nil {
   607  		return handleErr(fmt.Errorf("failed to update peer with wireguard endpoints: %w", err))
   608  	}
   609  	return err
   610  }
   611  
   612  func (r *RemoteNetworkReconciler) reconcileRemove(ctx context.Context, key client.ObjectKey, nw *cniv1.RemoteNetwork) error {
   613  	log := log.FromContext(ctx)
   614  	// Make sure the bridge connection is shutdown
   615  	if bridge, ok := r.bridges[key]; ok {
   616  		// Make sure we deregister DNS if we have a server running
   617  		if nw.Spec.Network.ForwardDNS && r.dnssrv != nil {
   618  			log.Info("Deregistering domain from dns server")
   619  			r.dnssrv.DeregisterDomain(bridge.Domain())
   620  		}
   621  		log.Info("Closing bridge node")
   622  		err := bridge.Close(ctx)
   623  		if err != nil {
   624  			log.Error(err, "Failed to close bridge node")
   625  		}
   626  		delete(r.bridges, key)
   627  	}
   628  	// Make sure we've removed routes to the remote network.
   629  	log.Info("Removing local routes to remote network")
   630  	err := r.Provider.MeshDB().Networking().DeleteRoute(ctx, r.localRouteName(nw))
   631  	if err != nil {
   632  		log.Error(err, "Failed to remove local routes to remote network")
   633  		// Try again on the next reconcile.
   634  		return fmt.Errorf("failed to remove local routes to remote network: %w", err)
   635  	}
   636  	// Remove the finalizer
   637  	if controllerutil.ContainsFinalizer(nw, cniv1.RemoteNetworkFinalizer) {
   638  		updated := controllerutil.RemoveFinalizer(nw, cniv1.RemoteNetworkFinalizer)
   639  		if updated {
   640  			log.Info("Removing finalizer from remote network")
   641  			if err := r.Update(ctx, nw); err != nil {
   642  				return fmt.Errorf("failed to remove finalizer: %w", err)
   643  			}
   644  		}
   645  	}
   646  	return nil
   647  }
   648  
   649  func (r *RemoteNetworkReconciler) localRouteName(nw *cniv1.RemoteNetwork) string {
   650  	return fmt.Sprintf("%s-%s-bridge", r.HostNode.ID(), nw.GetName())
   651  }
   652  
   653  func (r *RemoteNetworkReconciler) remoteRouteName(nw *cniv1.RemoteNetwork, bridge meshnode.Node) string {
   654  	return fmt.Sprintf("%s-%s-bridge", bridge.ID(), nw.GetName())
   655  }
   656  
   657  func (r *RemoteNetworkReconciler) setFailedStatus(ctx context.Context, bridge *cniv1.RemoteNetwork, reason error) {
   658  	bridge.Status.BridgeStatus = cniv1.BridgeStatusFailed
   659  	bridge.Status.Error = reason.Error()
   660  	err := r.updateBridgeStatus(ctx, bridge)
   661  	if err != nil {
   662  		log.FromContext(ctx).Error(err, "Failed to update container status")
   663  	}
   664  }
   665  
   666  func (r *RemoteNetworkReconciler) updateBridgeStatus(ctx context.Context, bridge *cniv1.RemoteNetwork) error {
   667  	bridge.SetManagedFields(nil)
   668  	err := r.Status().Patch(ctx,
   669  		bridge,
   670  		client.Apply,
   671  		client.ForceOwnership,
   672  		client.FieldOwner(cniv1.FieldOwner),
   673  	)
   674  	if err != nil {
   675  		return fmt.Errorf("failed to update status: %w", err)
   676  	}
   677  	return nil
   678  }
   679  
   680  func (r *RemoteNetworkReconciler) ensureBridgeReadyStatus(ctx context.Context, nw *cniv1.RemoteNetwork, node meshnode.Node) (err error) {
   681  	log := log.FromContext(ctx)
   682  	// Update the status to running and sets its IP address.
   683  	var updateStatus bool
   684  	origStatus := nw.Status
   685  	addrV4 := validOrEmpty(node.Network().WireGuard().AddressV4())
   686  	addrV6 := validOrEmpty(node.Network().WireGuard().AddressV6())
   687  	netv4 := validOrEmpty(node.Network().NetworkV4())
   688  	netv6 := validOrEmpty(node.Network().NetworkV6())
   689  	if nw.Status.BridgeStatus != cniv1.BridgeStatusRunning {
   690  		// Update the status to running and sets its IP address.
   691  		nw.Status.BridgeStatus = cniv1.BridgeStatusRunning
   692  		updateStatus = true
   693  	}
   694  	hwaddr, _ := node.Network().WireGuard().HardwareAddr()
   695  	if nw.Status.MACAddress != hwaddr.String() {
   696  		nw.Status.MACAddress = hwaddr.String()
   697  		updateStatus = true
   698  	}
   699  	if nw.Status.IPv4Address != addrV4 {
   700  		nw.Status.IPv4Address = addrV4
   701  		updateStatus = true
   702  	}
   703  	if nw.Status.IPv6Address != addrV6 {
   704  		nw.Status.IPv6Address = addrV6
   705  		updateStatus = true
   706  	}
   707  	if nw.Status.NetworkV4 != netv4 {
   708  		nw.Status.NetworkV4 = netv4
   709  		updateStatus = true
   710  	}
   711  	if nw.Status.NetworkV6 != netv6 {
   712  		nw.Status.NetworkV6 = netv6
   713  		updateStatus = true
   714  	}
   715  	if nw.Status.InterfaceName != node.Network().WireGuard().Name() {
   716  		nw.Status.InterfaceName = node.Network().WireGuard().Name()
   717  		updateStatus = true
   718  	}
   719  	if nw.Status.Error != "" {
   720  		nw.Status.Error = ""
   721  		updateStatus = true
   722  	}
   723  	// TODO: Lookup our direct peers and populate the status
   724  	if updateStatus {
   725  		log.Info("Updating container interface status",
   726  			"newStatus", nw.Status,
   727  			"oldStatus", origStatus,
   728  		)
   729  		return r.updateBridgeStatus(ctx, nw)
   730  	}
   731  	return nil
   732  }