diff --git a/10-mycelium.conflist b/10-mycelium.conflist new file mode 100644 index 0000000..fb110f2 --- /dev/null +++ b/10-mycelium.conflist @@ -0,0 +1,10 @@ +{ + "cniVersion": "1.0.0", + "name": "mycelium-network", + "plugins": [ + { + "type": "mycelium-cni", + "myceliumInterface": "mycelium" + } + ] +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c745877 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +PLUGIN_NAME = mycelium-cni +CNI_PLUGINS_DIR = /opt/cni/bin +CNI_CONFIG_DIR = /etc/cni/net.d + +.PHONY: build install clean test + +build: + go build -o $(PLUGIN_NAME) . + +install: build + sudo cp $(PLUGIN_NAME) $(CNI_PLUGINS_DIR)/ + sudo cp 10-mycelium.conflist $(CNI_CONFIG_DIR)/ + +clean: + rm -f $(PLUGIN_NAME) + sudo rm -f $(CNI_PLUGINS_DIR)/$(PLUGIN_NAME) + sudo rm -f $(CNI_CONFIG_DIR)/10-mycelium.conflist + +test: + go test ./... + +fmt: + go fmt ./... + +vet: + go vet ./... diff --git a/README.md b/README.md new file mode 100644 index 0000000..6907480 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Mycelium CNI Plugin + +A Container Network Interface (CNI) plugin that enables Kubernetes containers to connect to the Mycelium network. + +## Overview + +This CNI plugin integrates with the Mycelium overlay network to provide IPv6 connectivity for Kubernetes containers. It creates veth pairs and assigns IPv6 addresses from the host's Mycelium /64 block to containers. + +## Prerequisites + +- Mycelium daemon running on the host +- Go 1.21+ +- Root privileges for installation + +## Installation + +```bash +# Build the plugin +make build + +# Install plugin and configuration +make install +``` + +## Configuration + +The plugin uses a CNI configuration file (`10-mycelium.conflist`) that specifies the Mycelium interface name: + +```json +{ + "cniVersion": "1.0.0", + "name": "mycelium-network", + "plugins": [ + { + "type": "mycelium-cni", + "myceliumInterface": "mycelium" + } + ] +} +``` + +## How it Works + +1. **ADD Operation**: Creates a veth pair, moves one end to the container namespace, assigns an IPv6 address from the Mycelium prefix, and sets up routing. + +2. **DEL Operation**: Cleans up the host-side veth interface when containers are destroyed. + +## Testing + +Start Mycelium daemon first: +```bash +sudo mycelium --peers tcp://188.40.132.242:9651 tcp://136.243.47.186:9651 +``` + +Then use with Kubernetes or test directly with CNI tools. + +## Architecture + +Based on the docker-demo.sh script, this plugin: +- Uses IPv6 addressing from Mycelium's /64 block +- Creates veth pairs for container connectivity +- Sets up routing for Mycelium network (400::/7) +- Enables IPv6 forwarding on the host diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..83b3b6f --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module mycelium-cni + +go 1.21 + +require ( + github.com/containernetworking/cni v1.1.2 + github.com/vishvananda/netlink v1.1.0 + github.com/vishvananda/netns v0.0.4 +) diff --git a/main.go b/main.go new file mode 100644 index 0000000..43f0ccc --- /dev/null +++ b/main.go @@ -0,0 +1,249 @@ +package main + +import ( + "encoding/json" + "fmt" + "net" + "os" + "path/filepath" + + "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 + hostVethName := fmt.Sprintf("veth-%s", args.ContainerID[:8]) + 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 := ¤t.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 := fmt.Sprintf("veth-%s", args.ContainerID[:8]) + + 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 container IP within the /64 prefix + // Using simple approach: prefix + ::1 (could be made more sophisticated) + containerIP := make(net.IP, len(myceliumPrefix)) + copy(containerIP, myceliumPrefix) + containerIP[15] = 1 // Set last byte to 1 + return containerIP +} + +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 { + return netns.Set(containerNS, func(ns netns.NsHandle) error { + // 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 + } + + // Get host veth link-local address for routing + hostVeth, err := netlink.LinkByName(hostVethName) + if err == nil { + hostAddrs, err := netlink.AddrList(hostVeth, netlink.FAMILY_V6) + if err == nil { + for _, addr := range hostAddrs { + if addr.IP.IsLinkLocalUnicast() { + // Add route to Mycelium network via host veth + route := &netlink.Route{ + Dst: &net.IPNet{ + IP: net.ParseIP("400::"), + Mask: net.CIDRMask(7, 128), + }, + Gw: addr.IP, + LinkIndex: link.Attrs().Index, + } + netlink.RouteAdd(route) + break + } + } + } + } + + 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) +}