From 39ac3a2edf7c91a3d9848ef71ae2574a0c7d4381 Mon Sep 17 00:00:00 2001 From: Eugene Yakubovich Date: Thu, 8 Jan 2015 18:53:45 -0800 Subject: [PATCH] added proposed network plumbing --- build | 7 + network/ipam/ipam.go | 134 ++++++++++++++++++ network/net-plugin.go | 137 ++++++++++++++++++ network/net.go | 63 +++++++++ network/network.go | 260 +++++++++++++++++++++++++++++++++++ network/plugins/veth/veth.go | 130 ++++++++++++++++++ network/util/cidr.go | 15 ++ network/util/net.go | 43 ++++++ network/util/net_test.go | 80 +++++++++++ network/util/route.go | 22 +++ network/util/setns.go | 60 ++++++++ network/util/veth.go | 67 +++++++++ 12 files changed, 1018 insertions(+) create mode 100644 network/ipam/ipam.go create mode 100644 network/net-plugin.go create mode 100644 network/net.go create mode 100644 network/network.go create mode 100644 network/plugins/veth/veth.go create mode 100644 network/util/cidr.go create mode 100644 network/util/net.go create mode 100644 network/util/net_test.go create mode 100644 network/util/route.go create mode 100644 network/util/setns.go create mode 100644 network/util/veth.go diff --git a/build b/build index da8e4d9..b0f3724 100755 --- a/build +++ b/build @@ -35,3 +35,10 @@ go build -o $GOBIN/rkt ${REPO_PATH}/rkt echo "Building metadatasvc..." go build -o $GOBIN/metadatasvc ${REPO_PATH}/metadatasvc + +echo "Building network plugins" +for d in network/plugins/*; do + plugin=$(basename $d) + echo " " $plugin + go build -o $GOBIN/$plugin ${REPO_PATH}/$d +done diff --git a/network/ipam/ipam.go b/network/ipam/ipam.go new file mode 100644 index 0000000..5804f39 --- /dev/null +++ b/network/ipam/ipam.go @@ -0,0 +1,134 @@ +package ipam + +// ATTN: This is mostly throw away code. It'll be replaced +// by proper ip mgt plugins. + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "math/big" + "net" + "strings" + + "github.com/coreos/rocket/network/util" +) + +type options struct { + ipRange *net.IPNet + ip net.IP +} + +func ipAdd(ip net.IP, val uint) net.IP { + n := binary.BigEndian.Uint32(ip.To4()) + n += uint32(val) + + nip := make([]byte, 4) + binary.BigEndian.PutUint32(nip, n) + return net.IP(nip) +} + +func allocIP(ipn *net.IPNet) (*net.IPNet, error) { + ones, bits := ipn.Mask.Size() + zeros := bits - ones + rng := (1 << uint(zeros)) - 2 // (reduce for gw, bcast) + + n, err := rand.Int(rand.Reader, big.NewInt(int64(rng))) + if err != nil { + return nil, err + } + + offset := uint(n.Uint64() + 1) + + return &net.IPNet{ + IP: ipAdd(ipn.IP, offset), + Mask: ipn.Mask, + }, nil +} + +func deallocIP(ip net.IP) error { + return nil +} + +func splitArg(arg string) (k, v string) { + parts := strings.SplitN(arg, "=", 2) + + switch len(parts) { + case 1: + k = parts[0] + case 2: + k, v = parts[0], parts[1] + } + + return +} + +func parseArgs(args string) (*options, error) { + argv := strings.Split(args, ",") + + var err error + opts := &options{} + + for _, arg := range argv { + k, v := splitArg(arg) + switch k { + case "iprange": + opts.ipRange, err = util.ParseCIDR(v) + if err != nil { + return nil, fmt.Errorf("failed to parse iprange arg (%q): %v", v, err) + } + + case "ip": + opts.ip = net.ParseIP(v) + if opts.ip == nil { + return nil, fmt.Errorf("failed to parse ip arg (%q)", v) + } + } + } + + return opts, nil +} + +func AllocIP(contID, netConf, ifName, args string) (*net.IPNet, net.IP, error) { + opts, err := parseArgs(args) + if err != nil { + return nil, nil, err + } + + if opts.ipRange != nil { + ipn, err := allocIP(opts.ipRange) + if err != nil { + return nil, nil, fmt.Errorf("error allocating IP in %v: %v", ipn, err) + } + return ipn, nil, nil + } + + n := util.Net{} + if err := util.LoadNet(netConf, &n); err != nil { + return nil, nil, err + } + + switch n.IPAlloc.Type { + case "static": + _, ipn, err := net.ParseCIDR(n.IPAlloc.Subnet) + if err != nil { + // TODO: cleanup + return nil, nil, fmt.Errorf("error parsing %q conf: ipAlloc.Subnet: %v") + } + + ipn, err = allocIP(ipn) + if err != nil { + // TODO: cleanup + return nil, nil, fmt.Errorf("error allocating IP in %v: %v", ipn, err) + } + + return ipn, nil, nil + + default: + return nil, nil, fmt.Errorf("unsupported IP allocation type") + } +} + +func DeallocIP(contID, netConf, ifName string, ipn *net.IPNet) error { + return nil +} diff --git a/network/net-plugin.go b/network/net-plugin.go new file mode 100644 index 0000000..e8f0482 --- /dev/null +++ b/network/net-plugin.go @@ -0,0 +1,137 @@ +package network + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/coreos/rocket/network/util" +) + +type NetPlugin struct { + Name string `json:"name,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + Command struct { + Add []string `json:"add,omitempty"` + Del []string `json:"del,omitempty"` + } +} + +const RktNetPluginsPath = "/etc/rkt-net-plugins.conf.d" + +func LoadNetPlugin(path string) (*NetPlugin, error) { + c, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + np := &NetPlugin{} + if err = json.Unmarshal(c, np); err != nil { + return nil, err + } + + return np, nil +} + +func LoadNetPlugins() (map[string]*NetPlugin, error) { + plugins := make(map[string]*NetPlugin) + + dirents, err := ioutil.ReadDir(RktNetPluginsPath) + switch { + case err == nil: + case os.IsNotExist(err): + return plugins, nil + default: + return nil, err + } + + for _, dent := range dirents { + if dent.IsDir() { + continue + } + + npPath := filepath.Join(RktNetPluginsPath, dent.Name()) + np, err := LoadNetPlugin(npPath) + if err != nil { + log.Printf("Loading %v: %v", npPath, err) + continue + } + + plugins[np.Name] = np + } + + return plugins, nil +} + +func (np *NetPlugin) Add(n *Net, contID, netns, args, ifName string) (*net.IPNet, error) { + switch { + case np.Endpoint != "": + return nil, execHTTP(np.Endpoint, "add", n.Name, contID, netns, n.Filename, args, ifName) + + default: + if len(np.Command.Add) == 0 { + return nil, fmt.Errorf("plugin does not define command.add") + } + + output, err := execCmd(np.Command.Add, n.Name, contID, netns, n.Filename, args, ifName) + if err != nil { + return nil, err + } + + fmt.Printf("plugin's output %q\n", output) + + return util.ParseCIDR(output) + } +} + +func (np *NetPlugin) Del(n *Net, contID, netns, args, ifName string) error { + switch { + case np.Endpoint != "": + return execHTTP(np.Endpoint, "del", n.Name, contID, netns, n.Filename, args, ifName) + + default: + if len(np.Command.Del) == 0 { + return fmt.Errorf("plugin does not define command.del") + } + + _, err := execCmd(np.Command.Del, n.Name, contID, netns, n.Filename, args, ifName) + return err + } +} + +func execHTTP(ep, cmd, netName, contID, netns, confFile, args, ifName string) error { + return fmt.Errorf("not implemented") +} + +func replaceAll(xs []string, what, with string) { + for i, x := range xs { + xs[i] = strings.Replace(x, what, with, -1) + } +} + +func execCmd(cmd []string, netName, contID, netns, confFile, args, ifName string) (string, error) { + replaceAll(cmd, "{net-name}", netName) + replaceAll(cmd, "{cont-id}", contID) + replaceAll(cmd, "{netns}", netns) + replaceAll(cmd, "{conf-file}", confFile) + replaceAll(cmd, "{args}", args) + replaceAll(cmd, "{if-name}", ifName) + + stdout := &bytes.Buffer{} + + c := exec.Command(cmd[0], cmd[1:]...) + c.Stdout = stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + return "", err + } + + return stdout.String(), nil +} diff --git a/network/net.go b/network/net.go new file mode 100644 index 0000000..c402d60 --- /dev/null +++ b/network/net.go @@ -0,0 +1,63 @@ +package network + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path" + + "github.com/coreos/rocket/network/util" +) + +const RktNetPath = "/etc/rkt-net.conf.d" +const DefaultIPNet = "172.16.28.0/24" + +type Net struct { + util.Net + args string +} + +var defaultNet Net + +func init() { + defaultNet = Net{ + Net: util.Net{ + Name: "default", + Type: "veth", + }, + args: fmt.Sprintf("default,iprange=%v", DefaultIPNet), + } +} + +func LoadNets() ([]Net, error) { + dirents, err := ioutil.ReadDir(RktNetPath) + switch { + case err == nil: + case os.IsNotExist(err): + return nil, nil + default: + return nil, err + } + + var nets []Net + + for _, dent := range dirents { + if dent.IsDir() { + continue + } + + nf := path.Join(RktNetPath, dent.Name()) + n := Net{} + if err := util.LoadNet(nf, &n); err != nil { + log.Printf("Error loading %v: %v", nf, err) + continue + } + + nets = append(nets, n) + } + + nets = append(nets, defaultNet) + + return nets, nil +} diff --git a/network/network.go b/network/network.go new file mode 100644 index 0000000..23d674a --- /dev/null +++ b/network/network.go @@ -0,0 +1,260 @@ +package network + +import ( + "fmt" + "log" + "net" + "os" + "path/filepath" + "syscall" + + "github.com/appc/spec/schema/types" + "github.com/coreos/rocket/Godeps/_workspace/src/github.com/vishvananda/netlink" + + "github.com/coreos/rocket/network/util" +) + +const ( + ifnamePattern = "eth%d" + selfNetNS = "/proc/self/ns/net" +) + +type activeNet struct { + Net + ifName string + ipn *net.IPNet +} + +type Network struct { + MetadataIP net.IP + + contID types.UUID + hostNS *os.File + contNS *os.File + contNSPath string + nets []activeNet + plugins map[string]*NetPlugin +} + +func Setup(contID types.UUID) (*Network, error) { + var err error + n := Network{contID: contID} + + defer func() { + // cleanup on error + if err != nil { + n.Teardown() + } + }() + + if n.hostNS, n.contNS, err = basicNetNS(); err != nil { + return nil, err + } + // we're in contNS! + + contNSPath := filepath.Join("/var/lib/rkt/containers", contID.String(), "ns") + if err = bindMountFile(selfNetNS, contNSPath, "net"); err != nil { + return nil, err + } + n.contNSPath = filepath.Join(contNSPath, "net") + + n.plugins, err = LoadNetPlugins() + if err != nil { + return nil, fmt.Errorf("error loading plugin definitions: %v", err) + } + + nets, err := LoadNets() + if err != nil { + return nil, fmt.Errorf("error loading network definitions: %v", err) + } + + err = withNetNS(n.contNS, n.hostNS, func() error { + n.nets, err = setupNets(contID, n.contNSPath, n.plugins, nets) + return err + }) + if err != nil { + return nil, err + } + + // last net is the default + n.MetadataIP = n.nets[len(n.nets)-1].ipn.IP + + return &n, nil +} + +func (n *Network) Teardown() { + // teardown everything in reverse order of setup. + // this is called during error case as well so not + // everything maybe setup. + // N.B. better to keep going in case of errors to get as much + // cleaned up as possible + + if n.contNS == nil || n.hostNS == nil { + return + } + + if err := n.EnterHostNS(); err != nil { + log.Print(err) + return + } + + teardownNets(n.contID, n.contNSPath, n.plugins, n.nets) + + if n.contNSPath == "" { + return + } + + if err := syscall.Unmount(n.contNSPath, 0); err != nil { + log.Print("Error unmounting %q: %v", n.contNSPath, err) + } +} + +// sets up new netns with just lo +func basicNetNS() (hostNS, contNS *os.File, err error) { + hostNS, contNS, err = newNetNS() + if err != nil { + err = fmt.Errorf("failed to create new netns: %v", err) + return + } + // we're in contNS!! + + if err = loUp(); err != nil { + hostNS.Close() + contNS.Close() + return nil, nil, err + } + + return +} + + +func (n *Network) EnterHostNS() error { + return util.SetNS(n.hostNS, syscall.CLONE_NEWNET) +} + +func (n *Network) EnterContNS() error { + return util.SetNS(n.contNS, syscall.CLONE_NEWNET) +} + +func setupNets(contID types.UUID, netns string, plugins map[string]*NetPlugin, nets []Net) ([]activeNet, error) { + var err error + + active := []activeNet{} + + for i, nt := range nets { + plugin, ok := plugins[nt.Type] + if !ok { + err = fmt.Errorf("could not find network plugin %q\n", nt.Type) + break + } + + an := activeNet{ + Net: nt, + ifName: fmt.Sprintf(ifnamePattern, i), + } + + log.Printf("Executing net-plugin %v", nt.Type) + + an.ipn, err = plugin.Add(&nt, contID.String(), netns, nt.args, an.ifName) + if err != nil { + err = fmt.Errorf("error adding network %q: %v\n", nt.Name, err) + break + } + + active = append(active, an) + } + + log.Print("Done executing net plugins") + + if err != nil { + teardownNets(contID, netns, plugins, active) + return nil, err + } + + return active, nil +} + +func teardownNets(contID types.UUID, netns string, plugins map[string]*NetPlugin, nets []activeNet) { + for i := len(nets) - 1; i >= 0; i-- { + nt := nets[i] + plugin := plugins[nt.Type] + + err := plugin.Del(&nt.Net, contID.String(), netns, nt.args, nt.ifName) + if err != nil { + log.Printf("Error deleting %q: %v", nt.Name, err) + } + } +} + +func newNetNS() (hostNS, childNS *os.File, err error) { + defer func() { + if err != nil { + if hostNS != nil { + hostNS.Close() + } + if childNS != nil { + childNS.Close() + } + } + }() + + hostNS, err = os.Open(selfNetNS) + if err != nil { + return + } + + if err = syscall.Unshare(syscall.CLONE_NEWNET); err != nil { + return + } + + childNS, err = os.Open(selfNetNS) + if err != nil { + util.SetNS(hostNS, syscall.CLONE_NEWNET) + return + } + + return +} + +// execute f() in tgtNS +func withNetNS(curNS, tgtNS *os.File, f func() error) error { + if err := util.SetNS(tgtNS, syscall.CLONE_NEWNET); err != nil { + return err + } + + if err := f(); err != nil { + return err + } + + return util.SetNS(curNS, syscall.CLONE_NEWNET) +} + +func loUp() error { + lo, err := netlink.LinkByName("lo") + if err != nil { + return fmt.Errorf("failed to lookup lo: %v", err) + } + + if err := netlink.LinkSetUp(lo); err != nil { + return fmt.Errorf("failed to set lo up: %v", err) + } + + return nil +} + +func bindMountFile(src, dstDir, dstFile string) error { + if err := os.MkdirAll(dstDir, 0755); err != nil { + return err + } + + dst := filepath.Join(dstDir, dstFile) + + // mount point has to be an existing file + f, err := os.Create(dst) + if err != nil { + return err + } + f.Close() + + return syscall.Mount(src, dst, "none", syscall.MS_BIND, "") +} diff --git a/network/plugins/veth/veth.go b/network/plugins/veth/veth.go new file mode 100644 index 0000000..0e5a7c2 --- /dev/null +++ b/network/plugins/veth/veth.go @@ -0,0 +1,130 @@ +package main + +import ( + "fmt" + "log" + "net" + "os" + "runtime" + "strings" + "syscall" + + "github.com/coreos/rocket/Godeps/_workspace/src/github.com/vishvananda/netlink" + + "github.com/coreos/rocket/network/ipam" + "github.com/coreos/rocket/network/util" +) + +func init() { + // this ensures that main runs only on main thread (thread group leader). + // since namespace ops (unshare, setns) are done for a single thread, we + // must ensure that the goroutine does not jump from OS thread to thread + runtime.LockOSThread() +} + +func argsHasDefault(args string) bool { + argv := strings.Split(args, ",") + for _, a := range argv { + if a == "default" { + return true + } + } + return false +} + +func cmdAdd(contID, netns, netConf, ifName, args string) error { + var hostVethName string + + ipn, gw, err := ipam.AllocIP(contID, netConf, ifName, args) + if err != nil { + return err + } + + err = util.WithNetNSPath(netns, func(hostNS *os.File) error { + entropy := contID + ifName + + hostVeth, contVeth, err := util.SetupVeth(entropy, ifName, ipn, hostNS) + if err != nil { + return err + } + + if argsHasDefault(args) { + if err = util.AddDefaultRoute(gw, contVeth); err != nil { + return fmt.Errorf("failed to add default route: %v", err) + } + } + + hostVethName = hostVeth.Attrs().Name + return err + }) + + // hostVeth moved namespaces and will have a new ifindex + hostVeth, err := netlink.LinkByName(hostVethName) + if err != nil { + return fmt.Errorf("failed to lookup %q: %v", hostVeth.Attrs().Name, err) + } + + + // On the host we route traffic for the allocated IP to the container + ipn.Mask = net.CIDRMask(32, 32) + + if err = util.AddRoute(ipn, nil, hostVeth); err != nil { + return fmt.Errorf("failed to add route on host: %v", err) + } + + os.Stdout.Write([]byte(ipn.String())) + + return nil +} + +func cmdDel(contID, netns, netConf, ifName, args string) error { + // switch to the container namespace + contNS, err := os.Open(netns) + if err != nil { + return fmt.Errorf("Failed to open %v: %v", netns, err) + } + + if err = util.SetNS(contNS, syscall.CLONE_NEWNET); err != nil { + return fmt.Errorf("Error switching to ns %v: %v", netns, err) + } + + iface, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("Failed to lookup %q: %v", ifName, err) + } + + if err = netlink.LinkDel(iface); err != nil { + return fmt.Errorf("Failed to delete %q: %v", ifName, err) + } + + return nil +} + +func usage() int { + fmt.Fprintln(os.Stderr, "USAGE: add|del CONTAINER-ID NETNS NET-CONF IF-NAME ARGS") + return 1 +} + +func main() { + if len(os.Args) != 7 { + os.Exit(usage()) + } + + var err error + + switch os.Args[1] { + case "add": + err = cmdAdd(os.Args[2], os.Args[3], os.Args[4], os.Args[5], os.Args[6]) + + case "del": + err = cmdDel(os.Args[2], os.Args[3], os.Args[4], os.Args[5], os.Args[6]) + + default: + os.Exit(usage()) + } + + if err != nil { + log.Print(err) + os.Exit(1) + } +} diff --git a/network/util/cidr.go b/network/util/cidr.go new file mode 100644 index 0000000..3dd38a8 --- /dev/null +++ b/network/util/cidr.go @@ -0,0 +1,15 @@ +package util + +import ( + "net" +) + +func ParseCIDR(s string) (*net.IPNet, error) { + ip, ipn, err := net.ParseCIDR(s) + if err != nil { + return nil, err + } + + ipn.IP = ip + return ipn, nil +} diff --git a/network/util/net.go b/network/util/net.go new file mode 100644 index 0000000..bdadf39 --- /dev/null +++ b/network/util/net.go @@ -0,0 +1,43 @@ +package util + +import ( + "encoding/json" + "io/ioutil" + "reflect" +) + +type Net struct { + Filename string + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + IPAlloc struct { + Type string `json:"type,omitempty"` + Subnet string `json:"subnet,omitempty"` + } +} + +func LoadNet(path string, n interface{}) error { + c, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + if err = json.Unmarshal(c, n); err != nil { + return err + } + + // populate n.Filename if exists + v := reflect.ValueOf(n) + if v.Kind() == reflect.Ptr { + v = v.Elem() + if v.Kind() == reflect.Struct { + if fn := v.FieldByName("Filename"); fn.IsValid() { + if fn.Type().Kind() == reflect.String && fn.CanSet() { + fn.SetString(path) + } + } + } + } + + return nil +} diff --git a/network/util/net_test.go b/network/util/net_test.go new file mode 100644 index 0000000..f351c83 --- /dev/null +++ b/network/util/net_test.go @@ -0,0 +1,80 @@ +package util + +import ( + "encoding/json" + "io/ioutil" + "reflect" + "testing" +) + +func saveToTemp(v interface{}) (string, error) { + f, err := ioutil.TempFile("", "net") + if err != nil { + return "", err + } + defer f.Close() + + return f.Name(), json.NewEncoder(f).Encode(v) +} + +func TestNet(t *testing.T) { + expected := Net{ + Name: "mynet", + Type: "veth", + } + expected.IPAlloc.Type = "static" + expected.IPAlloc.Subnet = "10.1.2.0/24" + + fn, err := saveToTemp(expected) + if err != nil { + t.Fatal(err) + } + expected.Filename = fn + + actual := Net{} + if err = LoadNet(fn, &actual); err != nil { + t.Fatal(err) + } + + if expected.Filename != actual.Filename { + t.Errorf("Filename mismatch: expected=%q; actual=%q", expected.Filename, actual.Filename) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Mismatch: expected=%#v; actual=%#v", expected, actual) + } +} + +type MyNet struct { + Net +} + +func TestNetEmbedded(t *testing.T) { + expected := MyNet{ + Net: Net{ + Name: "mynet", + Type: "veth", + }, + } + expected.IPAlloc.Type = "static" + expected.IPAlloc.Subnet = "10.1.2.0/24" + + fn, err := saveToTemp(expected) + if err != nil { + t.Fatal(err) + } + expected.Filename = fn + + actual := MyNet{} + if err = LoadNet(fn, &actual); err != nil { + t.Fatal(err) + } + + if expected.Filename != actual.Filename { + t.Errorf("Filename mismatch: expected=%q; actual=%q", expected.Filename, actual.Filename) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Mismatch: expected=%#v; actual=%#v", expected, actual) + } +} diff --git a/network/util/route.go b/network/util/route.go new file mode 100644 index 0000000..bc0ddb1 --- /dev/null +++ b/network/util/route.go @@ -0,0 +1,22 @@ +package util + +import ( + "net" + + "github.com/coreos/rocket/Godeps/_workspace/src/github.com/vishvananda/netlink" +) + +func AddDefaultRoute(gw net.IP, dev netlink.Link) error { + _, defNet, _ := net.ParseCIDR("0.0.0.0/0") + return AddRoute(defNet, gw, dev) +} + +func AddRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error { + return netlink.RouteAdd(&netlink.Route{ + LinkIndex: dev.Attrs().Index, + Scope: netlink.SCOPE_UNIVERSE, + Dst: ipn, + Gw: gw, + }) +} + diff --git a/network/util/setns.go b/network/util/setns.go new file mode 100644 index 0000000..624fdf5 --- /dev/null +++ b/network/util/setns.go @@ -0,0 +1,60 @@ +package util + +import ( + "fmt" + "os" + "runtime" + "syscall" +) + +var setNsMap = map[string]uintptr{ + "386": 346, + "amd64": 308, + "arm": 374, +} + +func SetNS(f *os.File, flags uintptr) error { + if runtime.GOOS != "linux" { + return fmt.Errorf("unsupported OS: %s", runtime.GOOS) + } + + trap, ok := setNsMap[runtime.GOARCH] + if !ok { + return fmt.Errorf("unsupported arch: %s", runtime.GOARCH) + } + _, _, err := syscall.RawSyscall(trap, f.Fd(), flags, 0) + if err != 0 { + return err + } + + return nil +} + +func WithNetNSPath(nspath string, f func(*os.File) error) error { + // save a handle to current (host) network namespace + thisNS, err := os.Open("/proc/self/ns/net") + if err != nil { + return fmt.Errorf("Failed to open /proc/self/ns/net: %v", err) + } + + // switch to the container namespace + ns, err := os.Open(nspath) + if err != nil { + return fmt.Errorf("Failed to open %v: %v", nspath, err) + } + + if err = SetNS(ns, syscall.CLONE_NEWNET); err != nil { + return fmt.Errorf("Error switching to ns %v: %v", nspath, err) + } + + if err = f(thisNS); err != nil { + return err + } + + // switch back + if err = SetNS(thisNS, syscall.CLONE_NEWNET); err != nil { + return err + } + + return nil +} diff --git a/network/util/veth.go b/network/util/veth.go new file mode 100644 index 0000000..fa30ad3 --- /dev/null +++ b/network/util/veth.go @@ -0,0 +1,67 @@ +package util + +import ( + "crypto/sha512" + "fmt" + "net" + "os" + + "github.com/coreos/rocket/Godeps/_workspace/src/github.com/vishvananda/netlink" +) + +func makeVeth(name, peer string) (netlink.Link, error) { + veth := &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{ + Name: name, + Flags: net.FlagUp, + }, + PeerName: peer, + } + if err := netlink.LinkAdd(veth); err != nil { + return nil, err + } + + return veth, nil +} + +func hash(s string) string { + h := sha512.New() + h.Write([]byte(s)) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// Should be in container netns +func SetupVeth(entropy, contVethName string, ipn *net.IPNet, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) { + hostVethName := "rk" + hash(entropy)[:6] + hostVeth, err = makeVeth(hostVethName, contVethName) + if err != nil { + err = fmt.Errorf("failed to make veth pair: %v", err) + return + } + + if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil { + err = fmt.Errorf("failed to move veth to root netns: %v", err) + return + } + + contVeth, err = netlink.LinkByName(contVethName) + if err != nil { + err = fmt.Errorf("failed to lookup %q: %v", contVethName, err) + return + } + + if err = netlink.LinkSetUp(contVeth); err != nil { + err = fmt.Errorf("failed to set eth0 up: %v", err) + return + } + + if ipn != nil { + addr := &netlink.Addr{ipn, ""} + if err = netlink.AddrAdd(contVeth, addr); err != nil { + err = fmt.Errorf("failed to add IP addr to veth: %v", err) + return + } + } + + return +}