Go-based network automation with YANG models, gRPC, Ansible, Terraform, and Kubernetes integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
126 lines
3.3 KiB
Go
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()
|
|
}
|