mycelium-cni/main.go
2025-06-20 20:10:00 -07:00

311 lines
7.4 KiB
Go

package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
const (
PluginName = "mycelium-cni"
MyceliumInterface = "mycelium"
)
type NetConf struct {
types.NetConf
MyceliumInterface string `json:"myceliumInterface,omitempty"`
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "mycelium CNI plugin")
}
func cmdAdd(args *skel.CmdArgs) error {
conf, err := parseConfig(args.StdinData)
if err != nil {
return err
}
// Get container network namespace
containerNS, err := netns.GetFromPath(args.Netns)
if err != nil {
return fmt.Errorf("failed to open container netns %q: %v", args.Netns, err)
}
defer containerNS.Close()
// Get Mycelium interface and its IPv6 prefix
myceliumIP, err := getMyceliumIP(conf.MyceliumInterface)
if err != nil {
return fmt.Errorf("failed to get Mycelium IP: %v", err)
}
// Create veth pair with unique naming
hostVethName := generateVethName(args.ContainerID)
containerVethName := "eth0"
hostVeth, containerVeth, err := createVethPair(hostVethName, containerVethName)
if err != nil {
return fmt.Errorf("failed to create veth pair: %v", err)
}
// Move container veth to container namespace
if err := netlink.LinkSetNsFd(containerVeth, int(containerNS)); err != nil {
return fmt.Errorf("failed to move veth to container netns: %v", err)
}
// Configure container interface
containerIP := generateContainerIP(myceliumIP, args.ContainerID)
if err := configureContainerInterface(containerNS, containerVethName, containerIP, hostVethName); err != nil {
return fmt.Errorf("failed to configure container interface: %v", err)
}
// Configure host interface and routing
if err := configureHostInterface(hostVeth, containerIP); err != nil {
return fmt.Errorf("failed to configure host interface: %v", err)
}
// Build result
result := &current.Result{
CNIVersion: current.ImplementedSpecVersion,
Interfaces: []*current.Interface{
{
Name: hostVethName,
},
{
Name: containerVethName,
Sandbox: args.Netns,
},
},
IPs: []*current.IPConfig{
{
Interface: current.Int(1), // container interface
Address: net.IPNet{IP: containerIP, Mask: net.CIDRMask(64, 128)},
},
},
}
return types.PrintResult(result, conf.CNIVersion)
}
func cmdCheck(args *skel.CmdArgs) error {
// Basic connectivity check could be implemented here
return nil
}
func cmdDel(args *skel.CmdArgs) error {
// Clean up veth pair (host side will be automatically removed)
hostVethName := generateVethName(args.ContainerID)
link, err := netlink.LinkByName(hostVethName)
if err != nil {
// Interface might already be gone, which is fine
return nil
}
return netlink.LinkDel(link)
}
func parseConfig(stdin []byte) (*NetConf, error) {
conf := &NetConf{
MyceliumInterface: MyceliumInterface,
}
if err := json.Unmarshal(stdin, conf); err != nil {
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
}
return conf, nil
}
func getMyceliumIP(interfaceName string) (net.IP, error) {
link, err := netlink.LinkByName(interfaceName)
if err != nil {
return nil, fmt.Errorf("failed to find interface %s: %v", interfaceName, err)
}
addrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
if err != nil {
return nil, fmt.Errorf("failed to get addresses for %s: %v", interfaceName, err)
}
for _, addr := range addrs {
if addr.IP.IsGlobalUnicast() {
// Return the /64 prefix
return addr.IP.Mask(net.CIDRMask(64, 128)), nil
}
}
return nil, fmt.Errorf("no global IPv6 address found on %s", interfaceName)
}
func generateContainerIP(myceliumPrefix net.IP, containerID string) net.IP {
// Generate a unique container IP within the /64 prefix using container ID hash
hash := sha256.Sum256([]byte(containerID))
containerIP := make(net.IP, len(myceliumPrefix))
copy(containerIP, myceliumPrefix)
// Use first 8 bytes of hash for the host part (last 64 bits)
copy(containerIP[8:], hash[:8])
return containerIP
}
func generateVethName(containerID string) string {
// Generate unique but short veth name using hash of container ID
hash := sha256.Sum256([]byte(containerID))
shortHash := hex.EncodeToString(hash[:4]) // Use first 4 bytes for 8-char hex
return fmt.Sprintf("veth-%s", shortHash)
}
func createVethPair(hostName, containerName string) (netlink.Link, netlink.Link, error) {
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: hostName},
PeerName: containerName,
}
if err := netlink.LinkAdd(veth); err != nil {
return nil, nil, err
}
hostVeth, err := netlink.LinkByName(hostName)
if err != nil {
return nil, nil, err
}
containerVeth, err := netlink.LinkByName(containerName)
if err != nil {
return nil, nil, err
}
// Bring up host side
if err := netlink.LinkSetUp(hostVeth); err != nil {
return nil, nil, err
}
return hostVeth, containerVeth, nil
}
func configureContainerInterface(containerNS netns.NsHandle, ifName string, containerIP net.IP, hostVethName string) error {
// Switch to container namespace
originalNS, err := netns.Get()
if err != nil {
return err
}
defer originalNS.Close()
defer netns.Set(originalNS)
if err := netns.Set(containerNS); err != nil {
return err
}
// Get the interface
link, err := netlink.LinkByName(ifName)
if err != nil {
return err
}
// Bring interface up
if err := netlink.LinkSetUp(link); err != nil {
return err
}
// Add IP address
addr := &netlink.Addr{
IPNet: &net.IPNet{
IP: containerIP,
Mask: net.CIDRMask(64, 128),
},
}
if err := netlink.AddrAdd(link, addr); err != nil {
return err
}
// Switch to main namespace to get host veth address
if err := netns.Set(originalNS); err != nil {
return err
}
// Get host veth link-local address
hostVeth, err := netlink.LinkByName(hostVethName)
if err != nil {
return err
}
hostAddrs, err := netlink.AddrList(hostVeth, netlink.FAMILY_V6)
if err != nil {
return err
}
var hostLLAddr net.IP
for _, addr := range hostAddrs {
if addr.IP.IsLinkLocalUnicast() {
hostLLAddr = addr.IP
break
}
}
// Switch back to container namespace to add route
if err := netns.Set(containerNS); err != nil {
return err
}
if hostLLAddr != nil {
// First remove any existing route to our /64
myceliumPrefix := &net.IPNet{
IP: containerIP.Mask(net.CIDRMask(64, 128)),
Mask: net.CIDRMask(64, 128),
}
existingRoute := &netlink.Route{
Dst: myceliumPrefix,
}
netlink.RouteDel(existingRoute)
// Add route to our /64 via host veth
route := &netlink.Route{
Dst: myceliumPrefix,
Gw: hostLLAddr,
LinkIndex: link.Attrs().Index,
}
if err := netlink.RouteAdd(route); err != nil {
return err
}
// Add route to Mycelium network via host veth
route = &netlink.Route{
Dst: &net.IPNet{
IP: net.ParseIP("400::"),
Mask: net.CIDRMask(7, 128),
},
Gw: hostLLAddr,
LinkIndex: link.Attrs().Index,
}
if err := netlink.RouteAdd(route); err != nil {
return err
}
}
return nil
}
func configureHostInterface(hostVeth netlink.Link, containerIP net.IP) error {
// Add route to container IP via host veth
route := &netlink.Route{
Dst: &net.IPNet{
IP: containerIP,
Mask: net.CIDRMask(128, 128),
},
LinkIndex: hostVeth.Attrs().Index,
}
return netlink.RouteAdd(route)
}