package netlink import ( "antoine-roux.tk/projects/go/firecracker-netns/internal/netns" "antoine-roux.tk/projects/go/firecracker-netns/internal/sysctl" "crypto/rand" "fmt" "github.com/coreos/go-iptables/iptables" "github.com/vishvananda/netlink" "net" ) // inspired by https://github.com/containernetworking/plugins/blob/main/pkg/ip/link_linux.go#L57 type Peer struct { link netlink.Link netNs netns.NsHandle ip net.IPNet } type PairLink struct { host Peer guest Peer wanLinkName string } func NewVirtualPairing(ns netns.NsHandle, wanLinkName string) (pair *PairLink, error error) { origins, err := netns.Get() if err != nil { return nil, err } err = netns.Set(ns) if err != nil { return nil, err } name, err := randomVethName() if err != nil { return nil, err } peerName, err := randomVethName() if err != nil { return nil, err } attrs := netlink.NewLinkAttrs() attrs.Name = name veth := &netlink.Veth{ LinkAttrs: attrs, PeerName: peerName, PeerNamespace: netlink.NsFd(origins.Fd), } if err := netlink.LinkAdd(veth); err != nil { return nil, err } // Re-fetch the container link to get its creation-time parameters, e.g. index and mac fmt.Println(name) veth2, err := netlink.LinkByName(name) if err != nil { netlink.LinkDel(veth) return nil, err } err = netlink.LinkSetUp(veth2) if err != nil { return nil, err } veth2IP := &netlink.Addr{ IPNet: &net.IPNet{ IP: net.IPv4(192, 168, 0, 3), Mask: net.CIDRMask(24, 32), }, } // ip addr add 192.168.0.3/24 dev veth527f8d3e err = netlink.AddrAdd(veth2, veth2IP) if err != nil { return nil, err } // ip route add default via veth527f8d3e _, defaultDst, _ := net.ParseCIDR("0.0.0.0/0") defaultRoute := &netlink.Route{ LinkIndex: veth2.Attrs().Index, Dst: defaultDst, } err = netlink.RouteAdd(defaultRoute) if err != nil { return nil, err } err = netns.Set(origins) if err != nil { return nil, err } fmt.Println(peerName) veth1, err := netlink.LinkByName(peerName) if err != nil { netlink.LinkDel(veth) return nil, err } err = netlink.LinkSetUp(veth1) if err != nil { return nil, err } veth1IP := &netlink.Addr{ IPNet: &net.IPNet{ IP: net.IPv4(192, 168, 0, 4), Mask: net.CIDRMask(24, 32), }, } // ip addr add 192.168.0.4/24 dev vethda43d466 err = netlink.AddrAdd(veth1, veth1IP) if err != nil { return nil, err } // echo 1 > /proc/sys/net/ipv4/conf/veth4bbc9fef/proxy_arp err = sysctl.Set(fmt.Sprintf("net.ipv4.conf.%s.proxy_arp", veth1.Attrs().Name), "1") if err != nil { return nil, err } // echo 1 > /proc/sys/net/ipv4/ip_forward err = sysctl.Set("net.ipv4.ip_forward", "1") if err != nil { return nil, err } ipt, err := iptables.New() if err != nil { return nil, err } // sudo iptables -t nat -I POSTROUTING 1 -o wlp3s0 -j MASQUERADE --source 192.168.0.3/24 err = ipt.InsertUnique("nat", "POSTROUTING", 1, "-o", wanLinkName, "-j", "MASQUERADE", "--source", veth2IP.IPNet.String()) if err != nil { return nil, err } // sudo iptables -I FORWARD 1 -o vethee574a50 -i wlp3s0 -j ACCEPT err = ipt.InsertUnique("filter", "FORWARD", 1, "-o", veth1.Attrs().Name, "-i", wanLinkName, "-j", "ACCEPT") if err != nil { return nil, err } // sudo iptables -I FORWARD 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT err = ipt.InsertUnique("filter", "FORWARD", 1, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT") if err != nil { return nil, err } return &PairLink{ host: Peer{veth1, origins, *veth1IP.IPNet}, guest: Peer{veth2, ns, *veth2IP.IPNet}, wanLinkName: wanLinkName, }, nil } // DeleteVirtualPairing removes an interface link. func (p *PairLink) DeleteVirtualPairing() error { err := netns.Set(p.host.netNs) if err != nil { return fmt.Errorf("set original Ns for deleting veth %q: %v", p.host.link.Attrs().Name, err) } if err := netlink.LinkDel(p.host.link); err != nil { return fmt.Errorf("failed to delete %q: %v", p.host.link.Attrs().Name, err) } ipt, err := iptables.New() if err != nil { return err } // sudo iptables -t nat -I POSTROUTING 1 -o wlp3s0 -j MASQUERADE --source 192.168.0.3/24 err = ipt.DeleteIfExists("nat", "POSTROUTING", "-o", p.wanLinkName, "-j", "MASQUERADE", "--source", p.guest.ip.String()) if err != nil { return err } // sudo iptables -I FORWARD 1 -o vethee574a50 -i wlp3s0 -j ACCEPT err = ipt.DeleteIfExists("filter", "FORWARD", "-o", p.host.link.Attrs().Name, "-i", p.wanLinkName, "-j", "ACCEPT") if err != nil { return err } // sudo iptables -I FORWARD 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT err = ipt.DeleteIfExists("filter", "FORWARD", "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT") if err != nil { return err } return nil } // randomVethName returns string "veth" with random prefix (hashed from entropy) func randomVethName() (string, error) { entropy := make([]byte, 4) _, err := rand.Read(entropy) if err != nil { return "", fmt.Errorf("failed to generate random veth name: %v", err) } // NetworkManager (recent versions) will ignore veth devices that start with "veth" return fmt.Sprintf("veth%x", entropy), nil }