kedge/internal/cni/netns.go
Tyler King 6058e62348 Initial commit: Kedge network automation platform
Go-based network automation with YANG models, gRPC, Ansible,
Terraform, and Kubernetes integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:09:30 -05:00

126 lines
3.3 KiB
Go

package cni
import (
"fmt"
"net"
"runtime"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
// createVethPair creates a veth pair with one end in the host namespace
// and the other moved into the pod's network namespace.
// Returns (hostVethName, podVethName, error).
func createVethPair(podNetns string, ifName string) (string, string, error) {
hostVethName := "kedge_h_" + ifName[:min(5, len(ifName))]
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: hostVethName},
PeerName: ifName,
}
if err := netlink.LinkAdd(veth); err != nil {
return "", "", fmt.Errorf("failed to create veth pair: %w", err)
}
// Get a handle to the pod's network namespace.
podNS, err := netns.GetFromPath(podNetns)
if err != nil {
netlink.LinkDel(veth)
return "", "", fmt.Errorf("failed to get pod netns: %w", err)
}
defer podNS.Close()
// Move the peer end into the pod namespace.
peer, err := netlink.LinkByName(ifName)
if err != nil {
netlink.LinkDel(veth)
return "", "", fmt.Errorf("failed to find peer veth: %w", err)
}
if err := netlink.LinkSetNsFd(peer, int(podNS)); err != nil {
netlink.LinkDel(veth)
return "", "", fmt.Errorf("failed to move peer to pod netns: %w", err)
}
// Bring up the host-side veth.
hostVeth, err := netlink.LinkByName(hostVethName)
if err != nil {
return "", "", fmt.Errorf("failed to find host veth: %w", err)
}
if err := netlink.LinkSetUp(hostVeth); err != nil {
return "", "", fmt.Errorf("failed to bring up host veth: %w", err)
}
// Bring up the pod-side veth inside the namespace.
if err := inNamespace(podNS, func() error {
link, err := netlink.LinkByName(ifName)
if err != nil {
return err
}
return netlink.LinkSetUp(link)
}); err != nil {
return "", "", fmt.Errorf("failed to bring up pod veth: %w", err)
}
return hostVethName, ifName, nil
}
// teardownInterface removes the named interface from the pod's network namespace.
func teardownInterface(podNetns string, ifName string) error {
ns, err := netns.GetFromPath(podNetns)
if err != nil {
// Namespace may already be gone during cleanup.
return nil
}
defer ns.Close()
return inNamespace(ns, func() error {
link, err := netlink.LinkByName(ifName)
if err != nil {
return nil // Already gone.
}
return netlink.LinkDel(link)
})
}
// verifyInterface checks that the named interface exists in the pod's network namespace.
func verifyInterface(podNetns string, ifName string) error {
ns, err := netns.GetFromPath(podNetns)
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)
}
if link.Attrs().Flags&net.FlagUp == 0 {
return fmt.Errorf("interface %s is down", ifName)
}
return nil
})
}
// inNamespace executes fn inside the given network namespace, restoring
// the original namespace afterward.
func inNamespace(ns netns.NsHandle, fn func() error) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
origNS, err := netns.Get()
if err != nil {
return fmt.Errorf("failed to get current netns: %w", err)
}
defer origNS.Close()
if err := netns.Set(ns); err != nil {
return fmt.Errorf("failed to set netns: %w", err)
}
defer netns.Set(origNS) //nolint:errcheck
return fn()
}