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