package netlink import ( "antoine-roux.tk/projects/go/firecracker-netns/internal/sysctl" "crypto/rand" "fmt" "github.com/coreos/go-iptables/iptables" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" "net" "os/user" "strconv" ) // inspired by https://github.com/firecracker-microvm/firecracker-go-sdk/blob/main/cni/internal/netlink.go#L197 type Tap struct { Link netlink.Link Ip net.IPNet externalLinkName string } // CreateTap set up a tap device with random number // this tap is linked with routing to given externalLinkName func CreateTap(tapNetwork net.IPNet, externalLinkName string) (*Tap, error) { tapName, err := randomTapName() if err != nil { return nil, err } // Get current user information current, err := user.Current() if err != nil { return nil, err } gid, err := strconv.Atoi(current.Uid) if err != nil { return nil, err } uid, err := strconv.Atoi(current.Gid) if err != nil { return nil, err } tapLinkAttrs := netlink.NewLinkAttrs() tapLinkAttrs.Name = tapName tapLink := &netlink.Tuntap{ LinkAttrs: tapLinkAttrs, // We want a tap device (L2) as opposed to a tun (L3) Mode: netlink.TUNTAP_MODE_TAP, // Firecracker does not support multiqueue tap devices at this time: // https://github.com/firecracker-microvm/firecracker/issues/750 Queues: 1, // single queue tap device // parse vnet headers added by the vm's virtio_net implementation Flags: unix.IFF_TAP | netlink.TUNTAP_ONE_QUEUE | netlink.TUNTAP_VNET_HDR, Owner: uint32(uid), Group: uint32(gid), } err = netlink.LinkAdd(tapLink) if err != nil { return nil, err } for _, tapFd := range tapLink.Fds { err := tapFd.Close() if err != nil { return nil, err } } // Re-fetch the container link to get its creation-time parameters, e.g. index and mac fmt.Println(tapName) tap, err := netlink.LinkByName(tapName) if err != nil { netlink.LinkDel(tapLink) return nil, err } // Ip tapAddr add dev tap0 err = netlink.AddrAdd(tap, &netlink.Addr{ IPNet: &tapNetwork, }) if err != nil { return nil, err } err = netlink.LinkSetUp(tap) if err != nil { return nil, err } err = sysctl.Set(fmt.Sprintf("net.ipv4.conf.%s.proxy_arp", tap.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 vethee574a50 -j MASQUERADE --source 192.168.0.3/24 err = ipt.InsertUnique("nat", "POSTROUTING", 1, "-o", externalLinkName, "-j", "MASQUERADE", "--source", tapNetwork.String()) if err != nil { return nil, err } // sudo iptables -I FORWARD 1 -o tap0 -i vethee574a50 -j ACCEPT err = ipt.InsertUnique("filter", "FORWARD", 1, "-o", externalLinkName, "-i", tap.Attrs().Name, "-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 &Tap{tap, tapNetwork, externalLinkName}, nil } // randomTapName returns string "tap" with random prefix (hashed from entropy) func randomTapName() (string, error) { entropy := make([]byte, 4) _, err := rand.Read(entropy) if err != nil { return "", fmt.Errorf("failed to generate random tap name: %v", err) } // NetworkManager (recent versions) will ignore veth devices that start with "veth" return fmt.Sprintf("tap%x", entropy), nil } // DeleteTap delete tap device and related routing func (tap *Tap) DeleteTap() error { link, err := netlink.LinkByName(tap.Link.Attrs().Name) if err != nil { return err } if err := netlink.LinkDel(link); err != nil { return fmt.Errorf("failed to delete %q: %v", link.Attrs().Name, err) } ipt, err := iptables.New() if err != nil { return err } // sudo iptables -t nat -I POSTROUTING 1 -o vethee574a50 -j MASQUERADE --source 192.168.0.3/24 err = ipt.DeleteIfExists("nat", "POSTROUTING", "-o", tap.externalLinkName, "-j", "MASQUERADE", "--source", tap.Ip.String()) if err != nil { return err } // sudo iptables -I FORWARD 1 -o tap0 -i vethee574a50 -j ACCEPT err = ipt.DeleteIfExists("filter", "FORWARD", "-o", tap.Link.Attrs().Name, "-i", tap.externalLinkName, "-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 }