diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index ba217ca6..fe466ace 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -226,6 +226,10 @@ "Comment": "v1.0-71-g2152b45", "Rev": "2152b45fa28a361beba9aab0885972323a444e28" }, + { + "ImportPath": "github.com/safchain/ethtool", + "Rev": "7ff1ba29eca231991280817541cb3903f6be15d1" + }, { "ImportPath": "github.com/vishvananda/netlink", "Rev": "6e453822d85ef5721799774b654d4d02fed62afb" diff --git a/pkg/ip/link_linux.go b/pkg/ip/link_linux.go index 843ce8aa..94ef4127 100644 --- a/pkg/ip/link_linux.go +++ b/pkg/ip/link_linux.go @@ -23,6 +23,7 @@ import ( "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/utils/hwaddr" + "github.com/safchain/ethtool" "github.com/vishvananda/netlink" ) @@ -226,3 +227,43 @@ func SetHWAddrByIP(ifName string, ip4 net.IP, ip6 net.IP) error { return nil } + +// GetVethPeerIfindex returns the veth link object, the peer ifindex of the +// veth, or an error. This peer ifindex will only be valid in the peer's +// network namespace. +func GetVethPeerIfindex(ifName string) (netlink.Link, int, error) { + link, err := netlink.LinkByName(ifName) + if err != nil { + return nil, -1, fmt.Errorf("could not look up %q: %v", ifName, err) + } + if _, ok := link.(*netlink.Veth); !ok { + return nil, -1, fmt.Errorf("interface %q was not a veth interface", ifName) + } + + // veth supports IFLA_LINK (what vishvananda/netlink calls ParentIndex) + // on 4.1 and higher kernels + peerIndex := link.Attrs().ParentIndex + if peerIndex <= 0 { + // Fall back to ethtool for 4.0 and earlier kernels + e, err := ethtool.NewEthtool() + if err != nil { + return nil, -1, fmt.Errorf("failed to initialize ethtool: %v", err) + } + defer e.Close() + + stats, err := e.Stats(link.Attrs().Name) + if err != nil { + return nil, -1, fmt.Errorf("failed to request ethtool stats: %v", err) + } + n, ok := stats["peer_ifindex"] + if !ok { + return nil, -1, fmt.Errorf("failed to find 'peer_ifindex' in ethtool stats") + } + if n > 32767 || n == 0 { + return nil, -1, fmt.Errorf("invalid 'peer_ifindex' %d", n) + } + peerIndex = int(n) + } + + return link, peerIndex, nil +} diff --git a/pkg/ip/link_linux_test.go b/pkg/ip/link_linux_test.go index 1049c86e..8b79d4df 100644 --- a/pkg/ip/link_linux_test.go +++ b/pkg/ip/link_linux_test.go @@ -91,6 +91,46 @@ var _ = Describe("Link", func() { rand.Reader = originalRandReader }) + Describe("GetVethPeerIfindex", func() { + It("returns the link and peer index of the named interface", func() { + By("looking up the container veth index using the host veth name") + _ = hostNetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + gotHostLink, gotContainerIndex, err := ip.GetVethPeerIfindex(hostVethName) + Expect(err).NotTo(HaveOccurred()) + + By("checking we got back the host link") + attrs := gotHostLink.Attrs() + Expect(attrs.Index).To(Equal(hostVeth.Index)) + Expect(attrs.Name).To(Equal(hostVeth.Name)) + + By("checking we got back the container veth index") + Expect(gotContainerIndex).To(Equal(containerVeth.Index)) + + return nil + }) + + By("looking up the host veth index using the container veth name") + _ = containerNetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + gotContainerLink, gotHostIndex, err := ip.GetVethPeerIfindex(containerVethName) + Expect(err).NotTo(HaveOccurred()) + + By("checking we got back the container link") + attrs := gotContainerLink.Attrs() + Expect(attrs.Index).To(Equal(containerVeth.Index)) + Expect(attrs.Name).To(Equal(containerVeth.Name)) + + By("checking we got back the host veth index") + Expect(gotHostIndex).To(Equal(hostVeth.Index)) + + return nil + }) + }) + }) + It("SetupVeth must put the veth endpoints into the separate namespaces", func() { _ = containerNetNS.Do(func(ns.NetNS) error { defer GinkgoRecover() diff --git a/vendor/github.com/safchain/ethtool/ethtool.go b/vendor/github.com/safchain/ethtool/ethtool.go new file mode 100644 index 00000000..1f8828fa --- /dev/null +++ b/vendor/github.com/safchain/ethtool/ethtool.go @@ -0,0 +1,240 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +// Package ethtool aims to provide a library giving a simple access to the +// Linux SIOCETHTOOL ioctl operations. It can be used to retrieve informations +// from a network device like statistics, driver related informations or +// even the peer of a VETH interface. +package ethtool + +import ( + "bytes" + "fmt" + "syscall" + "unsafe" +) + +// Maximum size of an interface name +const ( + IFNAMSIZ = 16 +) + +// ioctl ethtool request +const ( + SIOCETHTOOL = 0x8946 +) + +// ethtool stats related constants. +const ( + ETH_GSTRING_LEN = 32 + ETH_SS_STATS = 1 + ETHTOOL_GDRVINFO = 0x00000003 + ETHTOOL_GSTRINGS = 0x0000001b + ETHTOOL_GSTATS = 0x0000001d + // other CMDs from ethtool-copy.h of ethtool-3.5 package + ETHTOOL_GSET = 0x00000001 /* Get settings. */ + ETHTOOL_SSET = 0x00000002 /* Set settings. */ + ETHTOOL_GMSGLVL = 0x00000007 /* Get driver message level */ + ETHTOOL_SMSGLVL = 0x00000008 /* Set driver msg level. */ +) + +// MAX_GSTRINGS maximum number of stats entries that ethtool can +// retrieve currently. +const ( + MAX_GSTRINGS = 1000 +) + +type ifreq struct { + ifr_name [IFNAMSIZ]byte + ifr_data uintptr +} + +type ethtoolDrvInfo struct { + cmd uint32 + driver [32]byte + version [32]byte + fw_version [32]byte + bus_info [32]byte + erom_version [32]byte + reserved2 [12]byte + n_priv_flags uint32 + n_stats uint32 + testinfo_len uint32 + eedump_len uint32 + regdump_len uint32 +} + +type ethtoolGStrings struct { + cmd uint32 + string_set uint32 + len uint32 + data [MAX_GSTRINGS * ETH_GSTRING_LEN]byte +} + +type ethtoolStats struct { + cmd uint32 + n_stats uint32 + data [MAX_GSTRINGS]uint64 +} + +type Ethtool struct { + fd int +} + +// DriverName returns the driver name of the given interface. +func (e *Ethtool) DriverName(intf string) (string, error) { + info, err := e.getDriverInfo(intf) + if err != nil { + return "", err + } + return string(bytes.Trim(info.driver[:], "\x00")), nil +} + +// BusInfo returns the bus info of the given interface. +func (e *Ethtool) BusInfo(intf string) (string, error) { + info, err := e.getDriverInfo(intf) + if err != nil { + return "", err + } + return string(bytes.Trim(info.bus_info[:], "\x00")), nil +} + +func (e *Ethtool) getDriverInfo(intf string) (ethtoolDrvInfo, error) { + drvinfo := ethtoolDrvInfo{ + cmd: ETHTOOL_GDRVINFO, + } + + var name [IFNAMSIZ]byte + copy(name[:], []byte(intf)) + + ifr := ifreq{ + ifr_name: name, + ifr_data: uintptr(unsafe.Pointer(&drvinfo)), + } + + _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr))) + if ep != 0 { + return ethtoolDrvInfo{}, syscall.Errno(ep) + } + + return drvinfo, nil +} + +// Stats retrieves stats of the given interface name. +func (e *Ethtool) Stats(intf string) (map[string]uint64, error) { + drvinfo := ethtoolDrvInfo{ + cmd: ETHTOOL_GDRVINFO, + } + + var name [IFNAMSIZ]byte + copy(name[:], []byte(intf)) + + ifr := ifreq{ + ifr_name: name, + ifr_data: uintptr(unsafe.Pointer(&drvinfo)), + } + + _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr))) + if ep != 0 { + return nil, syscall.Errno(ep) + } + + if drvinfo.n_stats*ETH_GSTRING_LEN > MAX_GSTRINGS*ETH_GSTRING_LEN { + return nil, fmt.Errorf("ethtool currently doesn't support more than %d entries, received %d", MAX_GSTRINGS, drvinfo.n_stats) + } + + gstrings := ethtoolGStrings{ + cmd: ETHTOOL_GSTRINGS, + string_set: ETH_SS_STATS, + len: drvinfo.n_stats, + data: [MAX_GSTRINGS * ETH_GSTRING_LEN]byte{}, + } + ifr.ifr_data = uintptr(unsafe.Pointer(&gstrings)) + + _, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr))) + if ep != 0 { + return nil, syscall.Errno(ep) + } + + stats := ethtoolStats{ + cmd: ETHTOOL_GSTATS, + n_stats: drvinfo.n_stats, + data: [MAX_GSTRINGS]uint64{}, + } + + ifr.ifr_data = uintptr(unsafe.Pointer(&stats)) + + _, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr))) + if ep != 0 { + return nil, syscall.Errno(ep) + } + + var result = make(map[string]uint64) + for i := 0; i != int(drvinfo.n_stats); i++ { + b := gstrings.data[i*ETH_GSTRING_LEN : i*ETH_GSTRING_LEN+ETH_GSTRING_LEN] + key := string(bytes.Trim(b, "\x00")) + result[key] = stats.data[i] + } + + return result, nil +} + +func (e *Ethtool) Close() { + syscall.Close(e.fd) +} + +func NewEthtool() (*Ethtool, error) { + fd, _, err := syscall.RawSyscall(syscall.SYS_SOCKET, syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP) + if err != 0 { + return nil, syscall.Errno(err) + } + + return &Ethtool{ + fd: int(fd), + }, nil +} + +func BusInfo(intf string) (string, error) { + e, err := NewEthtool() + if err != nil { + return "", err + } + defer e.Close() + return e.BusInfo(intf) +} + +func DriverName(intf string) (string, error) { + e, err := NewEthtool() + if err != nil { + return "", err + } + defer e.Close() + return e.DriverName(intf) +} + +func Stats(intf string) (map[string]uint64, error) { + e, err := NewEthtool() + if err != nil { + return nil, err + } + defer e.Close() + return e.Stats(intf) +}