From 0d29ca540f206efd675a4180fa035e0214741e60 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Thu, 19 Feb 2015 17:21:42 -0800 Subject: [PATCH] Initial import Signed-off-by: Arnaud Porterie --- libnetwork/bridge/bridge.go | 37 +++++++++++ libnetwork/configure.go | 95 +++++++++++++++++++++++++++++ libnetwork/namespace.go | 30 +++++++++ libnetwork/network.go | 69 +++++++++++++++++++++ libnetwork/reexec.go | 45 ++++++++++++++ libnetwork/reexec_move_interface.go | 78 +++++++++++++++++++++++ libnetwork/reexec_netns_create.go | 52 ++++++++++++++++ libnetwork/strategies.go | 16 +++++ libnetwork/system.go | 34 +++++++++++ 9 files changed, 456 insertions(+) create mode 100644 libnetwork/bridge/bridge.go create mode 100644 libnetwork/configure.go create mode 100644 libnetwork/namespace.go create mode 100644 libnetwork/network.go create mode 100644 libnetwork/reexec.go create mode 100644 libnetwork/reexec_move_interface.go create mode 100644 libnetwork/reexec_netns_create.go create mode 100644 libnetwork/strategies.go create mode 100644 libnetwork/system.go diff --git a/libnetwork/bridge/bridge.go b/libnetwork/bridge/bridge.go new file mode 100644 index 0000000000..bd6a96090b --- /dev/null +++ b/libnetwork/bridge/bridge.go @@ -0,0 +1,37 @@ +package bridge + +import ( + "net" + + "github.com/docker/libnetwork" +) + +const networkType = "bridgednetwork" + +func init() { + libnetwork.RegisterNetworkType(networkType, Create) +} + +func Create(options libnetwork.strategyParams) libnetwork.Network { + return &bridgeNetwork{} +} + +type Configuration struct { + Subnet net.IPNet +} + +type bridgeNetwork struct { + Config Configuration +} + +func (b *bridgeNetwork) Name() string { + return b.Id +} + +func (b *bridgeNetwork) Type() string { + return networkType +} + +func (b *bridgeNetwork) Link(name string) ([]*libnetwork.Interface, error) { + return nil, nil +} diff --git a/libnetwork/configure.go b/libnetwork/configure.go new file mode 100644 index 0000000000..31a6571d86 --- /dev/null +++ b/libnetwork/configure.go @@ -0,0 +1,95 @@ +package libnetwork + +import ( + "fmt" + "net" + + "github.com/vishvananda/netlink" +) + +func configureInterface(iface netlink.Link, settings *Interface) error { + ifaceName := iface.Attrs().Name + ifaceConfigurators := []struct { + Fn func(netlink.Link, *Interface) error + ErrMessage string + }{ + {setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, settings.DstName)}, + {setInterfaceMAC, fmt.Sprintf("error setting interface %q MAC address to %q", ifaceName, settings.MacAddress)}, + {setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, settings.Address)}, + {setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, settings.AddressIPv6)}, + {setInterfaceMTU, fmt.Sprintf("error setting interface %q MTU to %q", ifaceName, settings.MTU)}, + {setInterfaceGateway, fmt.Sprintf("error setting interface %q gateway to %q", ifaceName, settings.Gateway)}, + {setInterfaceGatewayIPv6, fmt.Sprintf("error setting interface %q IPv6 gateway to %q", ifaceName, settings.GatewayIPv6)}, + } + + for _, config := range ifaceConfigurators { + if err := config.Fn(iface, settings); err != nil { + return fmt.Errorf("%s: %v", config.ErrMessage, err) + } + } + return nil +} + +func setGatewayIP(iface netlink.Link, ip net.IP) error { + return netlink.RouteAdd(&netlink.Route{ + LinkIndex: iface.Attrs().Index, + Scope: netlink.SCOPE_UNIVERSE, + Gw: ip, + }) +} + +func setInterfaceGateway(iface netlink.Link, settings *Interface) error { + ip := net.ParseIP(settings.Gateway) + if ip == nil { + return fmt.Errorf("bad address format %q", settings.Gateway) + } + return setGatewayIP(iface, ip) +} + +func setInterfaceGatewayIPv6(iface netlink.Link, settings *Interface) error { + if settings.GatewayIPv6 != "" { + return nil + } + + ip := net.ParseIP(settings.GatewayIPv6) + if ip == nil { + return fmt.Errorf("bad address format %q", settings.GatewayIPv6) + } + return setGatewayIP(iface, ip) +} + +func setInterfaceIP(iface netlink.Link, settings *Interface) (err error) { + var ipAddr *netlink.Addr + if ipAddr, err = netlink.ParseAddr(settings.Address); err == nil { + err = netlink.AddrAdd(iface, ipAddr) + } + return err +} + +func setInterfaceIPv6(iface netlink.Link, settings *Interface) (err error) { + if settings.AddressIPv6 != "" { + return nil + } + + var ipAddr *netlink.Addr + if ipAddr, err = netlink.ParseAddr(settings.AddressIPv6); err == nil { + err = netlink.AddrAdd(iface, ipAddr) + } + return err +} + +func setInterfaceMAC(iface netlink.Link, settings *Interface) (err error) { + var hwAddr net.HardwareAddr + if hwAddr, err = net.ParseMAC(settings.MacAddress); err == nil { + err = netlink.LinkSetHardwareAddr(iface, hwAddr) + } + return err +} + +func setInterfaceMTU(iface netlink.Link, settings *Interface) error { + return netlink.LinkSetMTU(iface, settings.MTU) +} + +func setInterfaceName(iface netlink.Link, settings *Interface) error { + return netlink.LinkSetName(iface, settings.DstName) +} diff --git a/libnetwork/namespace.go b/libnetwork/namespace.go new file mode 100644 index 0000000000..263a51d6ad --- /dev/null +++ b/libnetwork/namespace.go @@ -0,0 +1,30 @@ +package libnetwork + +type networkNamespace struct { + path string + interfaces []*Interface +} + +// Create a new network namespace mounted on the provided path. +func NewNamespace(path string) (Namespace, error) { + if err := Reexec(ReexecCreateNamespace, path); err != nil { + return nil, err + } + return &networkNamespace{path: path}, nil +} + +func (n *networkNamespace) AddInterface(i *Interface) error { + if err := Reexec(ReexecMoveInterface, i.SrcName, i.DstName); err != nil { + return err + } + n.interfaces = append(n.interfaces, i) + return nil +} + +func (n *networkNamespace) Interfaces() []*Interface { + return n.interfaces +} + +func (n *networkNamespace) Path() string { + return n.path +} diff --git a/libnetwork/network.go b/libnetwork/network.go new file mode 100644 index 0000000000..9cb64264df --- /dev/null +++ b/libnetwork/network.go @@ -0,0 +1,69 @@ +// Package libnetwork provides basic fonctionalities and extension points to +// create network namespaces and allocate interfaces for containers to use. +// +// // Create a network for containers to join. +// network, err := libnetwork.NewNetwork("simplebridge", &Options{}) +// if err != nil { +// return err +// } +// +// // For a new container: create network namespace (providing the path). +// networkPath := "/var/lib/docker/.../4d23e" +// networkNamespace, err := libnetwork.NewNamespace(networkPath) +// if err != nil { +// return err +// } +// +// // For each new container: allocate IP and interfaces. The returned network +// // settings will be used for container infos (inspect and such), as well as +// // iptables rules for port publishing. +// interfaces, err := network.CreateInterfaces(containerID) +// if err != nil { +// return err +// } +// +// // Add interfaces to the namespace. +// for _, interface := range interfaces { +// if err := networkNamespace.AddInterface(interface); err != nil { +// return err +// } +// } +package libnetwork + +import "fmt" + +type Network interface { + Name() string + Type() string + Link(name string) ([]*Interface, error) +} + +type Interface struct { + // The name of the interface in the origin network namespace. + SrcName string + + // The name that will be assigned to the interface once moves inside a + // network namespace. + DstName string + + MacAddress string + Address string + AddressIPv6 string + Gateway string + GatewayIPv6 string + MTU int +} + +type Namespace interface { + Path() string + Interfaces() []*Interface + AddInterface(*Interface) error +} + +// TODO Figure out the proper options type +func NewNetwork(networkType string, options strategyParams) (Network, error) { + if ctor, ok := strategies[networkType]; ok { + return ctor(options) + } + return nil, fmt.Errorf("Unknown network type %q", networkType) +} diff --git a/libnetwork/reexec.go b/libnetwork/reexec.go new file mode 100644 index 0000000000..b06d4303c1 --- /dev/null +++ b/libnetwork/reexec.go @@ -0,0 +1,45 @@ +package libnetwork + +import ( + "fmt" + "os" + "os/exec" + + "github.com/docker/docker/pkg/reexec" +) + +type ReexecCommand int + +const ( + ReexecCreateNamespace ReexecCommand = iota + ReexecMoveInterface +) + +var ReexecCommands = map[ReexecCommand]struct { + Key string + Entrypoint func() +}{ + ReexecCreateNamespace: {"netns-create", createNetworkNamespace}, + ReexecMoveInterface: {"netns-moveif", namespaceMoveInterface}, +} + +func init() { + for _, reexecCmd := range ReexecCommands { + reexec.Register(reexecCmd.Key, reexecCmd.Entrypoint) + } +} + +func Reexec(command ReexecCommand, params ...string) error { + reexecCommand, ok := ReexecCommands[command] + if !ok { + return fmt.Errorf("unknown reexec command %q", command) + } + + cmd := &exec.Cmd{ + Path: reexec.Self(), + Args: append([]string{reexecCommand.Key}, params...), + Stdout: os.Stdout, + Stderr: os.Stderr, + } + return cmd.Run() +} diff --git a/libnetwork/reexec_move_interface.go b/libnetwork/reexec_move_interface.go new file mode 100644 index 0000000000..10706033b2 --- /dev/null +++ b/libnetwork/reexec_move_interface.go @@ -0,0 +1,78 @@ +package libnetwork + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "runtime" + "syscall" + + "github.com/vishvananda/netlink" +) + +type setupError struct { + Message string +} + +func (s setupError) Error() string { + return s.Message +} + +func namespaceMoveInterface() { + runtime.LockOSThread() + + var ( + err error + pipe = os.NewFile(3, "child") + ) + + defer func() { + if err != nil { + ioutil.ReadAll(pipe) + if err := json.NewEncoder(pipe).Encode(setupError{Message: err.Error()}); err != nil { + panic(err) + } + } + pipe.Close() + }() + + n := &Interface{} + if err = json.NewDecoder(pipe).Decode(n); err == nil { + err = setupInNS(os.Args[1], n) + } +} + +func setupInNS(nsPath string, settings *Interface) error { + f, err := os.OpenFile(nsPath, os.O_RDONLY, 0) + if err != nil { + return fmt.Errorf("failed get network namespace %q: %v", nsPath, err) + } + + // Find the network inteerface identified by the SrcName attribute. + iface, err := netlink.LinkByName(settings.SrcName) + if err != nil { + return err + } + + // Move the network interface to the destination namespace. + nsFD := f.Fd() + if err := netlink.LinkSetNsFd(iface, int(nsFD)); err != nil { + return err + } + f.Close() + + // Move the executing code to the destination namespace so we can start + // configure the interface. + if err := Setns(nsFD, syscall.CLONE_NEWNET); err != nil { + return err + } + + // Configure the interface now this is moved in the proper namespace. + if err := configureInterface(iface, settings); err != nil { + return err + } + + // Up the interface. + return netlink.LinkSetUp(iface) +} diff --git a/libnetwork/reexec_netns_create.go b/libnetwork/reexec_netns_create.go new file mode 100644 index 0000000000..867061e897 --- /dev/null +++ b/libnetwork/reexec_netns_create.go @@ -0,0 +1,52 @@ +package libnetwork + +import ( + "log" + "os" + "runtime" + "syscall" + + "github.com/vishvananda/netlink" +) + +func createNetworkNamespace() { + runtime.LockOSThread() + + if len(os.Args) < 2 { + log.Fatalf("no namespace path provided") + } + + if err := createNamespaceFile(os.Args[1]); err != nil { + log.Fatal(err) + } + + if err := syscall.Unshare(syscall.CLONE_NEWNET); err != nil { + log.Fatal(err) + } + + if err := loopbackUp(); err != nil { + log.Fatal(err) + } + + if err := syscall.Mount("/proc/self/ns/net", os.Args[1], "bind", syscall.MS_BIND, ""); err != nil { + log.Fatal(err) + } + + os.Exit(0) +} + +func createNamespaceFile(path string) (err error) { + var f *os.File + if f, err = os.Create(path); err == nil { + f.Close() + } + return err +} + +func loopbackUp() error { + iface, err := netlink.LinkByName("lo") + if err != nil { + return err + } + return netlink.LinkSetUp(iface) +} diff --git a/libnetwork/strategies.go b/libnetwork/strategies.go new file mode 100644 index 0000000000..582c590dc9 --- /dev/null +++ b/libnetwork/strategies.go @@ -0,0 +1,16 @@ +package libnetwork + +import "fmt" + +type strategyParams map[string]interface{} +type strategyConstructor func(strategyParams) (Network, error) + +var strategies = map[string]strategyConstructor{} + +func RegisterNetworkType(name string, ctor strategyConstructor) error { + if _, ok := strategies[name]; ok { + return fmt.Errorf("network type %q is already registed", name) + } + strategies[name] = ctor + return nil +} diff --git a/libnetwork/system.go b/libnetwork/system.go new file mode 100644 index 0000000000..6be142066c --- /dev/null +++ b/libnetwork/system.go @@ -0,0 +1,34 @@ +package libnetwork + +import ( + "fmt" + "runtime" + "syscall" +) + +// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092 +// +// We need different setns values for the different platforms and arch +// We are declaring the macro here because the SETNS syscall does not exist in th stdlib +var setNsMap = map[string]uintptr{ + "linux/386": 346, + "linux/amd64": 308, + "linux/arm": 374, + "linux/ppc64": 350, + "linux/ppc64le": 350, + "linux/s390x": 339, +} + +func Setns(fd uintptr, flags uintptr) error { + ns, exists := setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)] + if !exists { + return fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) + } + + _, _, err := syscall.RawSyscall(ns, fd, flags, 0) + if err != 0 { + return err + } + + return nil +}