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 }