Go-based network automation with YANG models, gRPC, Ansible, Terraform, and Kubernetes integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.1 KiB
Go
84 lines
2.1 KiB
Go
package cni
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netns"
|
|
|
|
"github.com/guildhouse-co/kedge/internal/topology"
|
|
)
|
|
|
|
// programPodRoutes programs routes inside the pod's network namespace for
|
|
// both overlay and underlay destinations.
|
|
func programPodRoutes(podNetnsPath string, ifName string, overlay, underlay []SubnetRoute) error {
|
|
ns, err := netns.GetFromPath(podNetnsPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get pod netns: %w", err)
|
|
}
|
|
defer ns.Close()
|
|
|
|
return inNamespace(ns, func() error {
|
|
link, err := netlink.LinkByName(ifName)
|
|
if err != nil {
|
|
return fmt.Errorf("interface %s not found: %w", ifName, err)
|
|
}
|
|
|
|
allRoutes := append(overlay, underlay...)
|
|
for _, sr := range allRoutes {
|
|
_, dst, err := net.ParseCIDR(sr.Dst)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid CIDR %s: %w", sr.Dst, err)
|
|
}
|
|
|
|
route := &netlink.Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: dst,
|
|
}
|
|
if err := netlink.RouteAdd(route); err != nil {
|
|
return fmt.Errorf("failed to add route %s: %w", sr.Dst, err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// verifyRoutes checks that expected routes exist in the pod's network namespace.
|
|
func verifyRoutes(podNetnsPath string, overlay, underlay []SubnetRoute) error {
|
|
ns, err := netns.GetFromPath(podNetnsPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get pod netns: %w", err)
|
|
}
|
|
defer ns.Close()
|
|
|
|
return inNamespace(ns, func() error {
|
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list routes: %w", err)
|
|
}
|
|
|
|
routeMap := make(map[string]bool)
|
|
for _, r := range routes {
|
|
if r.Dst != nil {
|
|
routeMap[r.Dst.String()] = true
|
|
}
|
|
}
|
|
|
|
allExpected := append(overlay, underlay...)
|
|
for _, sr := range allExpected {
|
|
if !routeMap[sr.Dst] {
|
|
return fmt.Errorf("missing route: %s", sr.Dst)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// loadMeshTopology reads the mesh topology file written by the DaemonSet.
|
|
func loadMeshTopology(meshConfigPath string) (*topology.MeshTopology, error) {
|
|
if meshConfigPath == "" {
|
|
meshConfigPath = "/etc/kedge/mesh.json"
|
|
}
|
|
return topology.LoadFromFile(meshConfigPath)
|
|
}
|