ns: add interface, use it, and fix thread-related namespace switch issues
Add a namespace object interface for somewhat cleaner code when creating and switching between network namespaces. All created namespaces are now mounted in /var/run/netns to ensure they have persistent inodes and paths that can be passed around between plugin components without relying on the current namespace being correct. Also remove the thread-locking arguments from the ns package per https://github.com/appc/cni/issues/183 by doing all the namespace changes in a separate goroutine that locks/unlocks itself, instead of the caller having to track OS thread locking.
This commit is contained in:
@@ -19,7 +19,6 @@ import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -74,7 +73,7 @@ func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) {
|
||||
|
||||
l.wg.Add(1)
|
||||
go func() {
|
||||
errCh <- ns.WithNetNSPath(netns, true, func(_ *os.File) error {
|
||||
errCh <- ns.WithNetNSPath(netns, func(_ ns.NetNS) error {
|
||||
defer l.wg.Done()
|
||||
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
@@ -129,10 +128,10 @@ func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) {
|
||||
return br, nil
|
||||
}
|
||||
|
||||
func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) error {
|
||||
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) error {
|
||||
var hostVethName string
|
||||
|
||||
err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error {
|
||||
err := netns.Do(func(hostNS ns.NetNS) error {
|
||||
// create the veth pair in the container and move host end into host netns
|
||||
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
|
||||
if err != nil {
|
||||
@@ -191,7 +190,13 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = setupVeth(args.Netns, br, args.IfName, n.MTU, n.HairpinMode); err != nil {
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
if err = setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -209,7 +214,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)
|
||||
}
|
||||
|
||||
err = ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
||||
err = netns.Do(func(_ ns.NetNS) error {
|
||||
return ipam.ConfigureIface(args.IfName, result)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -254,7 +259,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
var ipn *net.IPNet
|
||||
err = ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
var err error
|
||||
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
||||
return err
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ip"
|
||||
@@ -65,7 +64,7 @@ func modeFromString(s string) (netlink.IPVlanMode, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func createIpvlan(conf *NetConf, ifName string, netns *os.File) error {
|
||||
func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) error {
|
||||
mode, err := modeFromString(conf.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -97,7 +96,7 @@ func createIpvlan(conf *NetConf, ifName string, netns *os.File) error {
|
||||
return fmt.Errorf("failed to create ipvlan: %v", err)
|
||||
}
|
||||
|
||||
return ns.WithNetNS(netns, false, func(_ *os.File) error {
|
||||
return netns.Do(func(_ ns.NetNS) error {
|
||||
err := renameLink(tmpName, ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to rename ipvlan to %q: %v", ifName, err)
|
||||
@@ -112,9 +111,9 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
netns, err := os.Open(args.Netns)
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", netns, err)
|
||||
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
@@ -131,7 +130,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return errors.New("IPAM plugin returned missing IPv4 config")
|
||||
}
|
||||
|
||||
err = ns.WithNetNS(netns, false, func(_ *os.File) error {
|
||||
err = netns.Do(func(_ ns.NetNS) error {
|
||||
return ipam.ConfigureIface(args.IfName, result)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -153,7 +152,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
||||
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
return ip.DelLinkByName(args.IfName)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ns"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@@ -25,7 +23,7 @@ import (
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
args.IfName = "lo" // ignore config, this only works for loopback
|
||||
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
||||
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
@@ -48,7 +46,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
args.IfName = "lo" // ignore config, this only works for loopback
|
||||
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
||||
err := ns.WithNetNSPath(args.Netns, func(ns.NetNS) error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
|
||||
@@ -17,12 +17,10 @@ package main_test
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ns"
|
||||
"github.com/containernetworking/cni/pkg/testhelpers"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gbytes"
|
||||
@@ -31,7 +29,7 @@ import (
|
||||
|
||||
var _ = Describe("Loopback", func() {
|
||||
var (
|
||||
networkNS string
|
||||
networkNS ns.NetNS
|
||||
containerID string
|
||||
command *exec.Cmd
|
||||
environ []string
|
||||
@@ -39,12 +37,14 @@ var _ = Describe("Loopback", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
command = exec.Command(pathToLoPlugin)
|
||||
containerID = "some-container-id"
|
||||
networkNS = testhelpers.MakeNetworkNS(containerID)
|
||||
|
||||
var err error
|
||||
networkNS, err = ns.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
environ = []string{
|
||||
fmt.Sprintf("CNI_CONTAINERID=%s", containerID),
|
||||
fmt.Sprintf("CNI_NETNS=%s", networkNS),
|
||||
fmt.Sprintf("CNI_NETNS=%s", networkNS.Path()),
|
||||
fmt.Sprintf("CNI_IFNAME=%s", "this is ignored"),
|
||||
fmt.Sprintf("CNI_ARGS=%s", "none"),
|
||||
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
|
||||
@@ -53,7 +53,7 @@ var _ = Describe("Loopback", func() {
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(testhelpers.RemoveNetworkNS(networkNS)).To(Succeed())
|
||||
Expect(networkNS.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
Context("when given a network namespace", func() {
|
||||
@@ -67,7 +67,7 @@ var _ = Describe("Loopback", func() {
|
||||
Eventually(session).Should(gexec.Exit(0))
|
||||
|
||||
var lo *net.Interface
|
||||
err = ns.WithNetNSPath(networkNS, true, func(hostNS *os.File) error {
|
||||
err = networkNS.Do(func(ns.NetNS) error {
|
||||
var err error
|
||||
lo, err = net.InterfaceByName("lo")
|
||||
return err
|
||||
@@ -87,7 +87,7 @@ var _ = Describe("Loopback", func() {
|
||||
Eventually(session).Should(gexec.Exit(0))
|
||||
|
||||
var lo *net.Interface
|
||||
err = ns.WithNetNSPath(networkNS, true, func(hostNS *os.File) error {
|
||||
err = networkNS.Do(func(ns.NetNS) error {
|
||||
var err error
|
||||
lo, err = net.InterfaceByName("lo")
|
||||
return err
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/ip"
|
||||
@@ -74,7 +73,7 @@ func modeFromString(s string) (netlink.MacvlanMode, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func createMacvlan(conf *NetConf, ifName string, netns *os.File) error {
|
||||
func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) error {
|
||||
mode, err := modeFromString(conf.Mode)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -106,7 +105,7 @@ func createMacvlan(conf *NetConf, ifName string, netns *os.File) error {
|
||||
return fmt.Errorf("failed to create macvlan: %v", err)
|
||||
}
|
||||
|
||||
return ns.WithNetNS(netns, false, func(_ *os.File) error {
|
||||
return netns.Do(func(_ ns.NetNS) error {
|
||||
// TODO: duplicate following lines for ipv6 support, when it will be added in other places
|
||||
ipv4SysctlValueName := fmt.Sprintf(IPv4InterfaceArpProxySysctlTemplate, tmpName)
|
||||
if _, err := sysctl.Sysctl(ipv4SysctlValueName, "1"); err != nil {
|
||||
@@ -130,7 +129,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
netns, err := os.Open(args.Netns)
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", netns, err)
|
||||
}
|
||||
@@ -149,7 +148,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return errors.New("IPAM plugin returned missing IPv4 config")
|
||||
}
|
||||
|
||||
err = ns.WithNetNS(netns, false, func(_ *os.File) error {
|
||||
err = netns.Do(func(_ ns.NetNS) error {
|
||||
return ipam.ConfigureIface(args.IfName, result)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -171,7 +170,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
||||
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
return ip.DelLinkByName(args.IfName)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (string
|
||||
// In other words we force all traffic to ARP via the gateway except for GW itself.
|
||||
|
||||
var hostVethName string
|
||||
err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error {
|
||||
err := ns.WithNetNSPath(netns, func(hostNS ns.NetNS) error {
|
||||
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -200,7 +200,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
var ipn *net.IPNet
|
||||
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
||||
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
var err error
|
||||
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
||||
return err
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -45,7 +44,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
// The directory /proc/sys/net is per network namespace. Enter in the
|
||||
// network namespace before writing on it.
|
||||
|
||||
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
|
||||
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
for key, value := range tuningConf.SysCtl {
|
||||
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
|
||||
fileName = filepath.Clean(fileName)
|
||||
|
||||
Reference in New Issue
Block a user