firecracker-netns/internal/netlink/tap.go

183 lines
4.6 KiB
Go

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 addr add <tap Network> 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
}