// +build linux package libcontainer import ( "fmt" "io/ioutil" "net" "path/filepath" "strconv" "strings" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/netlink" "github.com/opencontainers/runc/libcontainer/utils" ) 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(*network, int) error initialize(*network) error detach(*configs.Network) error attach(*configs.Network) error } // getStrategy returns the specific network strategy for the // provided type. func getStrategy(tpe string) (networkStrategy, error) { s, exists := strategies[tpe] if !exists { return nil, fmt.Errorf("unknown strategy type %q", tpe) } 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 *network, nspid int) error { return nil } func (l *loopback) initialize(config *network) error { iface, err := net.InterfaceByName("lo") if err != nil { return err } return netlink.NetworkLinkUp(iface) } func (l *loopback) attach(n *configs.Network) (err error) { return nil } func (l *loopback) detach(n *configs.Network) (err error) { return nil } // veth is a network strategy that uses a bridge and creates // a veth pair, one that is attached to the bridge on the host and the other // is placed inside the container's namespace type veth struct { } func (v *veth) detach(n *configs.Network) (err error) { bridge, err := net.InterfaceByName(n.Bridge) if err != nil { return err } host, err := net.InterfaceByName(n.HostInterfaceName) if err != nil { return err } if err := netlink.DelFromBridge(host, bridge); err != nil { return err } return nil } // attach a container network interface to an external network func (v *veth) attach(n *configs.Network) (err error) { bridge, err := net.InterfaceByName(n.Bridge) if err != nil { return err } host, err := net.InterfaceByName(n.HostInterfaceName) 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 n.HairpinMode { if err := netlink.SetHairpinMode(host, true); err != nil { return err } } if err := netlink.NetworkLinkUp(host); err != nil { return err } return nil } func (v *veth) create(n *network, nspid int) (err error) { tmpName, err := v.generateTempPeerName() if err != nil { return err } n.TempVethPeerName = tmpName defer func() { if err != nil { netlink.NetworkLinkDel(n.HostInterfaceName) netlink.NetworkLinkDel(n.TempVethPeerName) } }() if n.Bridge == "" { return fmt.Errorf("bridge is not specified") } if err := netlink.NetworkCreateVethPair(n.HostInterfaceName, n.TempVethPeerName, n.TxQueueLen); err != nil { return err } if err := v.attach(&n.Network); err != nil { return err } child, err := net.InterfaceByName(n.TempVethPeerName) if err != nil { return err } return netlink.NetworkSetNsPid(child, nspid) } func (v *veth) generateTempPeerName() (string, error) { return utils.GenerateRandomName("veth", 7) } func (v *veth) initialize(config *network) error { peer := config.TempVethPeerName if peer == "" { return fmt.Errorf("peer is not specified") } child, err := net.InterfaceByName(peer) if err != nil { return err } if err := netlink.NetworkLinkDown(child); err != nil { return err } if err := netlink.NetworkChangeName(child, config.Name); err != nil { return err } // get the interface again after we changed the name as the index also changes. if child, err = net.InterfaceByName(config.Name); 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, config.Name); err != nil { return err } } if config.IPv6Gateway != "" { if err := netlink.AddDefaultGw(config.IPv6Gateway, config.Name); err != nil { return err } } return nil }