First version CNI plugin
This commit is contained in:
		
							
								
								
									
										10
									
								
								10-mycelium.conflist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								10-mycelium.conflist
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | { | ||||||
|  |   "cniVersion": "1.0.0", | ||||||
|  |   "name": "mycelium-network", | ||||||
|  |   "plugins": [ | ||||||
|  |     { | ||||||
|  |       "type": "mycelium-cni", | ||||||
|  |       "myceliumInterface": "mycelium" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ./... | ||||||
							
								
								
									
										63
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										9
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | ) | ||||||
							
								
								
									
										249
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user