diff --git a/container.go b/container.go index 9db1e297..7c9a3308 100644 --- a/container.go +++ b/container.go @@ -6,16 +6,9 @@ package libcontainer import ( "os" - "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/configs" - "github.com/docker/libcontainer/network" ) -type Stats struct { - NetworkStats *network.NetworkStats `json:"network_stats,omitempty"` - CgroupStats *cgroups.Stats `json:"cgroup_stats,omitempty"` -} - // A libcontainer container object. // // Each container is thread-safe within the same process. Since a container can diff --git a/linux_container.go b/linux_container.go index 8f733d47..06f90b3c 100644 --- a/linux_container.go +++ b/linux_container.go @@ -10,14 +10,9 @@ import ( "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/configs" - "github.com/docker/libcontainer/network" "github.com/golang/glog" ) -type pid struct { - Pid int `json:"Pid"` -} - type linuxContainer struct { id string root string @@ -73,13 +68,14 @@ func (c *linuxContainer) Stats() (*Stats, error) { if stats.CgroupStats, err = c.cgroupManager.GetStats(); err != nil { return stats, newGenericError(err, SystemError) } - // TODO: handle stats for multiple veth interfaces for _, iface := range c.config.Networks { - if iface.Type == "veth" { - if stats.NetworkStats, err = network.GetStats(iface.VethHost); err != nil { + switch iface.Type { + case "veth": + istats, err := getNetworkInterfaceStats(iface.VethHost) + if err != nil { return stats, newGenericError(err, SystemError) } - break + stats.Interfaces = append(stats.Interfaces, istats) } } return stats, nil diff --git a/linux_init.go b/linux_init.go index 9b28af63..663bfa6d 100644 --- a/linux_init.go +++ b/linux_init.go @@ -11,7 +11,6 @@ import ( "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/netlink" - "github.com/docker/libcontainer/network" "github.com/docker/libcontainer/security/capabilities" "github.com/docker/libcontainer/system" "github.com/docker/libcontainer/user" @@ -27,13 +26,17 @@ const ( initUsernsSetup initType = "userns_setup" ) +type pid struct { + Pid int `json:"pid"` +} + // Process is used for transferring parameters from Exec() to Init() type initConfig struct { - Args []string `json:"args,omitempty"` - Env []string `json:"env,omitempty"` - Cwd string `json:"cwd,omitempty"` - User string `json:"user,omitempty"` - Config *configs.Config `json:"config,omitempty"` + Args []string `json:"args"` + Env []string `json:"env"` + Cwd string `json:"cwd"` + User string `json:"user"` + Config *configs.Config `json:"config"` } type initer interface { @@ -183,7 +186,7 @@ func setupUser(config *initConfig) error { // setting the MTU and IP address along with the default gateway func setupNetwork(config *configs.Config) error { for _, config := range config.Networks { - strategy, err := network.GetStrategy(config.Type) + strategy, err := getStrategy(config.Type) if err != nil { return err } diff --git a/linux_network.go b/linux_network.go new file mode 100644 index 00000000..cc199be4 --- /dev/null +++ b/linux_network.go @@ -0,0 +1,201 @@ +// +build linux + +package libcontainer + +import ( + "errors" + "fmt" + "io/ioutil" + "net" + "path/filepath" + "strconv" + "strings" + + "github.com/docker/libcontainer/configs" + "github.com/docker/libcontainer/netlink" +) + +const defaultVethInterfaceName = "eth0" + +var ( + ErrNotValidStrategyType = errors.New("not a valid network strategy type") +) + +var strategies = map[string]networkStrategy{ + "veth": &veth{}, + "loopback": &loopback{}, +} + +// networkStrategy represents a specific network configuration for +// a container's networking stack +type networkStrategy interface { + Create(*configs.Network, int) error + Initialize(*configs.Network) error +} + +// getStrategy returns the specific network strategy for the +// provided type. If no strategy is registered for the type an +// ErrNotValidStrategyType is returned. +func getStrategy(tpe string) (networkStrategy, error) { + s, exists := strategies[tpe] + if !exists { + return nil, ErrNotValidStrategyType + } + return s, nil +} + +// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo. +func getNetworkInterfaceStats(interfaceName string) (*NetworkInterface, error) { + out := &NetworkInterface{Name: interfaceName} + // This can happen if the network runtime information is missing - possible if the + // container was created by an old version of libcontainer. + if interfaceName == "" { + return out, nil + } + type netStatsPair struct { + // Where to write the output. + Out *uint64 + // The network stats file to read. + File string + } + // Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container. + netStats := []netStatsPair{ + {Out: &out.RxBytes, File: "tx_bytes"}, + {Out: &out.RxPackets, File: "tx_packets"}, + {Out: &out.RxErrors, File: "tx_errors"}, + {Out: &out.RxDropped, File: "tx_dropped"}, + + {Out: &out.TxBytes, File: "rx_bytes"}, + {Out: &out.TxPackets, File: "rx_packets"}, + {Out: &out.TxErrors, File: "rx_errors"}, + {Out: &out.TxDropped, File: "rx_dropped"}, + } + for _, netStat := range netStats { + data, err := readSysfsNetworkStats(interfaceName, netStat.File) + if err != nil { + return nil, err + } + *(netStat.Out) = data + } + return out, nil +} + +// Reads the specified statistics available under /sys/class/net//statistics +func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) { + data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile)) + if err != nil { + return 0, err + } + return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) +} + +// loopback is a network strategy that provides a basic loopback device +type loopback struct { +} + +func (l *loopback) Create(n *configs.Network, nspid int) error { + return nil +} + +func (l *loopback) Initialize(config *configs.Network) error { + iface, err := net.InterfaceByName("lo") + if err != nil { + return err + } + return netlink.NetworkLinkUp(iface) +} + +// veth is a network strategy that uses a bridge and creates +// a veth pair, one that stays outside on the host and the other +// is placed inside the container's namespace +type veth struct { +} + +func (v *veth) Create(n *configs.Network, nspid int) error { + if n.Bridge == "" { + return fmt.Errorf("bridge is not specified") + } + bridge, err := net.InterfaceByName(n.Bridge) + if err != nil { + return err + } + if err := netlink.NetworkCreateVethPair(n.VethHost, n.VethChild, n.TxQueueLen); err != nil { + return err + } + host, err := net.InterfaceByName(n.VethHost) + if err != nil { + return err + } + if err := netlink.AddToBridge(host, bridge); err != nil { + return err + } + if err := netlink.NetworkSetMTU(host, n.Mtu); err != nil { + return err + } + if err := netlink.NetworkLinkUp(host); err != nil { + return err + } + child, err := net.InterfaceByName(n.VethChild) + if err != nil { + return err + } + return netlink.NetworkSetNsPid(child, nspid) +} + +func (v *veth) Initialize(config *configs.Network) error { + vethChild := config.VethChild + if vethChild == "" { + return fmt.Errorf("vethChild is not specified") + } + child, err := net.InterfaceByName(vethChild) + if err != nil { + return err + } + if err := netlink.NetworkLinkDown(child); err != nil { + return err + } + if err := netlink.NetworkChangeName(child, defaultVethInterfaceName); err != nil { + return err + } + // get the interface again after we changed the name as the index also changes. + if child, err = net.InterfaceByName(defaultVethInterfaceName); err != nil { + return err + } + if config.MacAddress != "" { + if err := netlink.NetworkSetMacAddress(child, config.MacAddress); err != nil { + return err + } + } + ip, ipNet, err := net.ParseCIDR(config.Address) + if err != nil { + return err + } + if err := netlink.NetworkLinkAddIp(child, ip, ipNet); err != nil { + return err + } + if config.IPv6Address != "" { + if ip, ipNet, err = net.ParseCIDR(config.IPv6Address); err != nil { + return err + } + if err := netlink.NetworkLinkAddIp(child, ip, ipNet); err != nil { + return err + } + } + if err := netlink.NetworkSetMTU(child, config.Mtu); err != nil { + return err + } + if err := netlink.NetworkLinkUp(child); err != nil { + return err + } + if config.Gateway != "" { + if err := netlink.AddDefaultGw(config.Gateway, defaultVethInterfaceName); err != nil { + return err + } + } + if config.IPv6Gateway != "" { + if err := netlink.AddDefaultGw(config.IPv6Gateway, defaultVethInterfaceName); err != nil { + return err + } + } + return nil +} diff --git a/linux_process.go b/linux_process.go index 0de894ab..04c0e5d6 100644 --- a/linux_process.go +++ b/linux_process.go @@ -12,7 +12,6 @@ import ( "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/configs" - "github.com/docker/libcontainer/network" "github.com/docker/libcontainer/system" "github.com/golang/glog" ) @@ -236,7 +235,7 @@ func (p *initProcess) sendConfig() error { func (p *initProcess) createNetworkInterfaces() error { for _, config := range p.config.Config.Networks { - strategy, err := network.GetStrategy(config.Type) + strategy, err := getStrategy(config.Type) if err != nil { return err } diff --git a/network/loopback.go b/network/loopback.go deleted file mode 100644 index 11db8885..00000000 --- a/network/loopback.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build linux - -package network - -import ( - "fmt" - - "github.com/docker/libcontainer/configs" -) - -// Loopback is a network strategy that provides a basic loopback device -type Loopback struct { -} - -func (l *Loopback) Create(n *configs.Network, nspid int) error { - return nil -} - -func (l *Loopback) Initialize(config *configs.Network) error { - // Do not set the MTU on the loopback interface - use the default. - if err := InterfaceUp("lo"); err != nil { - return fmt.Errorf("lo up %s", err) - } - return nil -} diff --git a/network/network.go b/network/network.go deleted file mode 100644 index 40b25b13..00000000 --- a/network/network.go +++ /dev/null @@ -1,117 +0,0 @@ -// +build linux - -package network - -import ( - "net" - - "github.com/docker/libcontainer/netlink" -) - -func InterfaceUp(name string) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - return netlink.NetworkLinkUp(iface) -} - -func InterfaceDown(name string) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - return netlink.NetworkLinkDown(iface) -} - -func ChangeInterfaceName(old, newName string) error { - iface, err := net.InterfaceByName(old) - if err != nil { - return err - } - return netlink.NetworkChangeName(iface, newName) -} - -func CreateVethPair(name1, name2 string, txQueueLen int) error { - return netlink.NetworkCreateVethPair(name1, name2, txQueueLen) -} - -func SetInterfaceInNamespacePid(name string, nsPid int) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - return netlink.NetworkSetNsPid(iface, nsPid) -} - -func SetInterfaceInNamespaceFd(name string, fd uintptr) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - return netlink.NetworkSetNsFd(iface, int(fd)) -} - -func SetInterfaceMaster(name, master string) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - masterIface, err := net.InterfaceByName(master) - if err != nil { - return err - } - return netlink.AddToBridge(iface, masterIface) -} - -func SetDefaultGateway(ip, ifaceName string) error { - return netlink.AddDefaultGw(ip, ifaceName) -} - -func SetInterfaceMac(name string, macaddr string) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - return netlink.NetworkSetMacAddress(iface, macaddr) -} - -func SetInterfaceIp(name string, rawIp string) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - ip, ipNet, err := net.ParseCIDR(rawIp) - if err != nil { - return err - } - return netlink.NetworkLinkAddIp(iface, ip, ipNet) -} - -func DeleteInterfaceIp(name string, rawIp string) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - ip, ipNet, err := net.ParseCIDR(rawIp) - if err != nil { - return err - } - return netlink.NetworkLinkDelIp(iface, ip, ipNet) -} - -func SetMtu(name string, mtu int) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - return netlink.NetworkSetMTU(iface, mtu) -} - -func SetHairpinMode(name string, enabled bool) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - return netlink.SetHairpinMode(iface, enabled) -} diff --git a/network/stats.go b/network/stats.go deleted file mode 100644 index 1d7cfe77..00000000 --- a/network/stats.go +++ /dev/null @@ -1,70 +0,0 @@ -package network - -import ( - "io/ioutil" - "path/filepath" - "strconv" - "strings" -) - -type NetworkStats struct { - RxBytes uint64 `json:"rx_bytes"` - RxPackets uint64 `json:"rx_packets"` - RxErrors uint64 `json:"rx_errors"` - RxDropped uint64 `json:"rx_dropped"` - TxBytes uint64 `json:"tx_bytes"` - TxPackets uint64 `json:"tx_packets"` - TxErrors uint64 `json:"tx_errors"` - TxDropped uint64 `json:"tx_dropped"` -} - -// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo. -func GetStats(vethHostInterface string) (*NetworkStats, error) { - // This can happen if the network runtime information is missing - possible if the container was created by an old version of libcontainer. - if vethHostInterface == "" { - return &NetworkStats{}, nil - } - out := &NetworkStats{} - type netStatsPair struct { - // Where to write the output. - Out *uint64 - - // The network stats file to read. - File string - } - // Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container. - netStats := []netStatsPair{ - {Out: &out.RxBytes, File: "tx_bytes"}, - {Out: &out.RxPackets, File: "tx_packets"}, - {Out: &out.RxErrors, File: "tx_errors"}, - {Out: &out.RxDropped, File: "tx_dropped"}, - - {Out: &out.TxBytes, File: "rx_bytes"}, - {Out: &out.TxPackets, File: "rx_packets"}, - {Out: &out.TxErrors, File: "rx_errors"}, - {Out: &out.TxDropped, File: "rx_dropped"}, - } - for _, netStat := range netStats { - data, err := readSysfsNetworkStats(vethHostInterface, netStat.File) - if err != nil { - return nil, err - } - *(netStat.Out) = data - } - return out, nil -} - -// Reads the specified statistics available under /sys/class/net//statistics -func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) { - fullPath := filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile) - data, err := ioutil.ReadFile(fullPath) - if err != nil { - return 0, err - } - value, err := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) - if err != nil { - return 0, err - } - - return value, err -} diff --git a/network/strategy.go b/network/strategy.go deleted file mode 100644 index 8631c11d..00000000 --- a/network/strategy.go +++ /dev/null @@ -1,36 +0,0 @@ -// +build linux - -package network - -import ( - "errors" - - "github.com/docker/libcontainer/configs" -) - -var ( - ErrNotValidStrategyType = errors.New("not a valid network strategy type") -) - -var strategies = map[string]NetworkStrategy{ - "veth": &Veth{}, - "loopback": &Loopback{}, -} - -// NetworkStrategy represents a specific network configuration for -// a container's networking stack -type NetworkStrategy interface { - Create(*configs.Network, int) error - Initialize(*configs.Network) error -} - -// GetStrategy returns the specific network strategy for the -// provided type. If no strategy is registered for the type an -// ErrNotValidStrategyType is returned. -func GetStrategy(tpe string) (NetworkStrategy, error) { - s, exists := strategies[tpe] - if !exists { - return nil, ErrNotValidStrategyType - } - return s, nil -} diff --git a/network/veth.go b/network/veth.go deleted file mode 100644 index 5d554e8b..00000000 --- a/network/veth.go +++ /dev/null @@ -1,85 +0,0 @@ -// +build linux - -package network - -import ( - "fmt" - - "github.com/docker/libcontainer/configs" -) - -// Veth is a network strategy that uses a bridge and creates -// a veth pair, one that stays outside on the host and the other -// is placed inside the container's namespace -type Veth struct { -} - -const defaultDevice = "eth0" - -func (v *Veth) Create(n *configs.Network, nspid int) error { - var ( - bridge = n.Bridge - txQueueLen = n.TxQueueLen - ) - if bridge == "" { - return fmt.Errorf("bridge is not specified") - } - if err := CreateVethPair(n.VethHost, n.VethChild, txQueueLen); err != nil { - return err - } - if err := SetInterfaceMaster(n.VethHost, bridge); err != nil { - return err - } - if err := SetMtu(n.VethHost, n.Mtu); err != nil { - return err - } - if err := InterfaceUp(n.VethHost); err != nil { - return err - } - return SetInterfaceInNamespacePid(n.VethChild, nspid) - return nil -} - -func (v *Veth) Initialize(config *configs.Network) error { - vethChild := config.VethChild - if vethChild == "" { - return fmt.Errorf("vethChild is not specified") - } - if err := InterfaceDown(vethChild); err != nil { - return fmt.Errorf("interface down %s %s", vethChild, err) - } - if err := ChangeInterfaceName(vethChild, defaultDevice); err != nil { - return fmt.Errorf("change %s to %s %s", vethChild, defaultDevice, err) - } - if config.MacAddress != "" { - if err := SetInterfaceMac(defaultDevice, config.MacAddress); err != nil { - return fmt.Errorf("set %s mac %s", defaultDevice, err) - } - } - if err := SetInterfaceIp(defaultDevice, config.Address); err != nil { - return fmt.Errorf("set %s ip %s", defaultDevice, err) - } - if config.IPv6Address != "" { - if err := SetInterfaceIp(defaultDevice, config.IPv6Address); err != nil { - return fmt.Errorf("set %s ipv6 %s", defaultDevice, err) - } - } - - if err := SetMtu(defaultDevice, config.Mtu); err != nil { - return fmt.Errorf("set %s mtu to %d %s", defaultDevice, config.Mtu, err) - } - if err := InterfaceUp(defaultDevice); err != nil { - return fmt.Errorf("%s up %s", defaultDevice, err) - } - if config.Gateway != "" { - if err := SetDefaultGateway(config.Gateway, defaultDevice); err != nil { - return fmt.Errorf("set gateway to %s on device %s failed with %s", config.Gateway, defaultDevice, err) - } - } - if config.IPv6Gateway != "" { - if err := SetDefaultGateway(config.IPv6Gateway, defaultDevice); err != nil { - return fmt.Errorf("set gateway for ipv6 to %s on device %s failed with %s", config.IPv6Gateway, defaultDevice, err) - } - } - return nil -} diff --git a/stats.go b/stats.go new file mode 100644 index 00000000..198a8bf5 --- /dev/null +++ b/stats.go @@ -0,0 +1,22 @@ +package libcontainer + +import "github.com/docker/libcontainer/cgroups" + +type NetworkInterface struct { + // Name is the name of the network interface. + Name string + + RxBytes uint64 + RxPackets uint64 + RxErrors uint64 + RxDropped uint64 + TxBytes uint64 + TxPackets uint64 + TxErrors uint64 + TxDropped uint64 +} + +type Stats struct { + Interfaces []*NetworkInterface + CgroupStats *cgroups.Stats +}