From fdb4db7dba1c8bde7d30e2c811171a329c07af0d Mon Sep 17 00:00:00 2001 From: RouxAntoine Date: Tue, 2 Jan 2024 18:39:09 +0100 Subject: [PATCH] feature: setup veth and allow routing to an external interface --- cmd/main.go | 4 +- go.mod | 5 +- go.sum | 2 + internal/netlink/netlink.go | 120 ++++++++++++++++++++++++++++++++++-- internal/sysctl/sysctl.go | 37 +++++++++++ 5 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 internal/sysctl/sysctl.go diff --git a/cmd/main.go b/cmd/main.go index 02cd9c7..b49ce72 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -33,14 +33,14 @@ func setupEnv() int { } }(newNs) - vethPair, err := netlink.NewVethPair(newNs) + vethPair, err := netlink.NewVirtualPairing(newNs, "wlp3s0") if err != nil { fmt.Println("new Veth error", err) return 1 } defer func(veth *netlink.PairLink) { - err = veth.DeleteLink() + err = veth.DeleteVirtualPairing() if err != nil { fmt.Println("delete vethPair error", err) } diff --git a/go.mod b/go.mod index 99556f0..251d315 100644 --- a/go.mod +++ b/go.mod @@ -9,4 +9,7 @@ require ( golang.org/x/sys v0.15.0 ) -require github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect +require ( + github.com/coreos/go-iptables v0.7.0 // indirect + github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect +) diff --git a/go.sum b/go.sum index 561652b..bdbed21 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= +github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= diff --git a/internal/netlink/netlink.go b/internal/netlink/netlink.go index a115f86..1578652 100644 --- a/internal/netlink/netlink.go +++ b/internal/netlink/netlink.go @@ -2,9 +2,12 @@ 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 @@ -12,14 +15,16 @@ import ( type Peer struct { link netlink.Link netNs netns.NsHandle + ip net.IPNet } type PairLink struct { - host Peer - guest Peer + host Peer + guest Peer + wanLinkName string } -func NewVethPair(ns netns.NsHandle) (pair *PairLink, error error) { +func NewVirtualPairing(ns netns.NsHandle, wanLinkName string) (pair *PairLink, error error) { origins, err := netns.Get() if err != nil { @@ -61,6 +66,35 @@ func NewVethPair(ns netns.NsHandle) (pair *PairLink, error error) { 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 @@ -73,11 +107,62 @@ func NewVethPair(ns netns.NsHandle) (pair *PairLink, error error) { return nil, err } - return &PairLink{host: Peer{veth1, origins}, guest: Peer{veth2, ns}}, nil + 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 + } + + // bash -c '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 + } + + 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 } -// DeleteLink removes an interface link. -func (p *PairLink) DeleteLink() error { +// 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) @@ -87,6 +172,29 @@ func (p *PairLink) DeleteLink() error { 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 } diff --git a/internal/sysctl/sysctl.go b/internal/sysctl/sysctl.go new file mode 100644 index 0000000..ba2b646 --- /dev/null +++ b/internal/sysctl/sysctl.go @@ -0,0 +1,37 @@ +package sysctl + +// inspired from https://github.com/lorenzosaino/go-sysctl/blob/main/sysctl.go + +import ( + "os" + "path/filepath" + "strings" +) + +const defaultPath = "/proc/sys/" + +// Get returns a sysctl from a given key. +func Get(key string) (string, error) { + return readFile(pathFromKey(key)) +} + +// Set updates the value of a sysctl. +func Set(key, value string) error { + return writeFile(pathFromKey(key), value) +} + +func pathFromKey(key string) string { + return filepath.Join(defaultPath, strings.Replace(key, ".", "/", -1)) +} + +func readFile(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + return strings.TrimSpace(string(data)), nil +} + +func writeFile(path, value string) error { + return os.WriteFile(path, []byte(value), 0o644) +}