From 320750e1f10b50e1234778eb112a47b56e61a4bd Mon Sep 17 00:00:00 2001 From: phob0s Date: Thu, 14 Dec 2017 11:29:47 +0100 Subject: [PATCH] Two new functions: LinkSetBondSlave and VethPeerIndex --- ioctl_linux.go | 98 ++++++++++++++++++++++++++++++++++++++ link_linux.go | 54 +++++++++++++++++++++ link_test.go | 127 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 ioctl_linux.go diff --git a/ioctl_linux.go b/ioctl_linux.go new file mode 100644 index 00000000..a8503126 --- /dev/null +++ b/ioctl_linux.go @@ -0,0 +1,98 @@ +package netlink + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +// ioctl for statistics. +const ( + // ETHTOOL_GSSET_INFO gets string set info + ETHTOOL_GSSET_INFO = 0x00000037 + // SIOCETHTOOL is Ethtool interface + SIOCETHTOOL = 0x8946 + // ETHTOOL_GSTRINGS gets specified string set + ETHTOOL_GSTRINGS = 0x0000001b + // ETHTOOL_GSTATS gets NIC-specific statistics + ETHTOOL_GSTATS = 0x0000001d +) + +// string set id. +const ( + // ETH_SS_TEST is self-test result names, for use with %ETHTOOL_TEST + ETH_SS_TEST = iota + // ETH_SS_STATS statistic names, for use with %ETHTOOL_GSTATS + ETH_SS_STATS + // ETH_SS_PRIV_FLAGS are driver private flag names + ETH_SS_PRIV_FLAGS + // _ETH_SS_NTUPLE_FILTERS is deprecated + _ETH_SS_NTUPLE_FILTERS + // ETH_SS_FEATURES are device feature names + ETH_SS_FEATURES + // ETH_SS_RSS_HASH_FUNCS is RSS hush function names + ETH_SS_RSS_HASH_FUNCS +) + +// IfreqSlave is a struct for ioctl bond manipulation syscalls. +// It is used to assign slave to bond interface with Name. +type IfreqSlave struct { + Name [unix.IFNAMSIZ]byte + Slave [unix.IFNAMSIZ]byte +} + +// Ifreq is a struct for ioctl ethernet manipulation syscalls. +type Ifreq struct { + Name [unix.IFNAMSIZ]byte + Data uintptr +} + +// ethtoolSset is a string set information +type ethtoolSset struct { + cmd uint32 + reserved uint32 + mask uint64 + data [1]uint32 +} + +// ethtoolGstrings is string set for data tagging +type ethtoolGstrings struct { + cmd uint32 + stringSet uint32 + length uint32 + data [32]byte +} + +type ethtoolStats struct { + cmd uint32 + nStats uint32 + data [1]uint64 +} + +// newIocltSlaveReq returns filled IfreqSlave with proper interface names +// It is used by ioctl to assign slave to bond master +func newIocltSlaveReq(slave, master string) *IfreqSlave { + ifreq := &IfreqSlave{} + copy(ifreq.Name[:unix.IFNAMSIZ-1], master) + copy(ifreq.Slave[:unix.IFNAMSIZ-1], slave) + return ifreq +} + +// newIocltStringSetReq creates request to get interface string set +func newIocltStringSetReq(linkName string) (*Ifreq, *ethtoolSset) { + e := ðtoolSset{ + cmd: ETHTOOL_GSSET_INFO, + mask: 1 << ETH_SS_STATS, + } + + ifreq := &Ifreq{Data: uintptr(unsafe.Pointer(e))} + copy(ifreq.Name[:unix.IFNAMSIZ-1], linkName) + return ifreq, e +} + +// getSocketUDP returns file descriptor to new UDP socket +// It is used for communication with ioctl interface. +func getSocketUDP() (int, error) { + return syscall.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) +} diff --git a/link_linux.go b/link_linux.go index a6ae1041..587e74f6 100644 --- a/link_linux.go +++ b/link_linux.go @@ -2256,3 +2256,57 @@ func parseGTPData(link Link, data []syscall.NetlinkRouteAttr) { } } } + +// LinkSetBondSlave add slave to bond link via ioctl interface. +func LinkSetBondSlave(link Link, master *Bond) error { + fd, err := getSocketUDP() + if err != nil { + return err + } + defer syscall.Close(fd) + + ifreq := newIocltSlaveReq(link.Attrs().Name, master.Attrs().Name) + + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), unix.SIOCBONDENSLAVE, uintptr(unsafe.Pointer(ifreq))) + if errno != 0 { + return fmt.Errorf("Failed to enslave %q to %q, errno=%v", link.Attrs().Name, master.Attrs().Name, errno) + } + return nil +} + +// VethPeerIndex get veth peer index. +func VethPeerIndex(link *Veth) (int, error) { + fd, err := getSocketUDP() + if err != nil { + return -1, err + } + defer syscall.Close(fd) + + ifreq, sSet := newIocltStringSetReq(link.Name) + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), SIOCETHTOOL, uintptr(unsafe.Pointer(ifreq))) + if errno != 0 { + return -1, fmt.Errorf("SIOCETHTOOL request for %q failed, errno=%v", link.Attrs().Name, errno) + } + + gstrings := ðtoolGstrings{ + cmd: ETHTOOL_GSTRINGS, + stringSet: ETH_SS_STATS, + length: sSet.data[0], + } + ifreq.Data = uintptr(unsafe.Pointer(gstrings)) + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), SIOCETHTOOL, uintptr(unsafe.Pointer(ifreq))) + if errno != 0 { + return -1, fmt.Errorf("SIOCETHTOOL request for %q failed, errno=%v", link.Attrs().Name, errno) + } + + stats := ðtoolStats{ + cmd: ETHTOOL_GSTATS, + nStats: gstrings.length, + } + ifreq.Data = uintptr(unsafe.Pointer(stats)) + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), SIOCETHTOOL, uintptr(unsafe.Pointer(ifreq))) + if errno != 0 { + return -1, fmt.Errorf("SIOCETHTOOL request for %q failed, errno=%v", link.Attrs().Name, errno) + } + return int(stats.data[0]), nil +} diff --git a/link_test.go b/link_test.go index 3bf4b8e2..e333ed2b 100644 --- a/link_test.go +++ b/link_test.go @@ -1544,3 +1544,130 @@ func TestLinkAddDelTuntapMq(t *testing.T) { Queues: 4, Flags: TUNTAP_MULTI_QUEUE_DEFAULTS | TUNTAP_VNET_HDR}) } + +func TestVethPeerIndex(t *testing.T) { + tearDown := setUpNetlinkTest(t) + defer tearDown() + + const ( + vethPeer1 = "vethOne" + vethPeer2 = "vethTwo" + ) + + link := &Veth{ + LinkAttrs: LinkAttrs{ + Name: vethPeer1, + MTU: 1500, + Flags: net.FlagUp, + }, + PeerName: vethPeer2, + } + + if err := LinkAdd(link); err != nil { + t.Fatal(err) + } + + linkOne, err := LinkByName("vethOne") + if err != nil { + t.Fatal(err) + } + + linkTwo, err := LinkByName("vethTwo") + if err != nil { + t.Fatal(err) + } + + peerIndexOne, err := VethPeerIndex(&Veth{LinkAttrs: *linkOne.Attrs()}) + if err != nil { + t.Fatal(err) + } + + peerIndexTwo, err := VethPeerIndex(&Veth{LinkAttrs: *linkTwo.Attrs()}) + if err != nil { + t.Fatal(err) + } + + if peerIndexOne != linkTwo.Attrs().Index { + t.Errorf("VethPeerIndex(%s) mismatch %d != %d", linkOne.Attrs().Name, peerIndexOne, linkTwo.Attrs().Index) + } + + if peerIndexTwo != linkOne.Attrs().Index { + t.Errorf("VethPeerIndex(%s) mismatch %d != %d", linkTwo.Attrs().Name, peerIndexTwo, linkOne.Attrs().Index) + } +} + +func TestLinkSetBondSlave(t *testing.T) { + minKernelRequired(t, 3, 13) + + tearDown := setUpNetlinkTest(t) + defer tearDown() + + const ( + bondName = "foo" + slaveOneName = "fooFoo" + slaveTwoName = "fooBar" + ) + + bond := NewLinkBond(LinkAttrs{Name: bondName}) + bond.Mode = StringToBondModeMap["802.3ad"] + bond.AdSelect = BondAdSelect(BOND_AD_SELECT_BANDWIDTH) + bond.AdActorSysPrio = 1 + bond.AdUserPortKey = 1 + bond.AdActorSystem, _ = net.ParseMAC("06:aa:bb:cc:dd:ee") + + if err := LinkAdd(bond); err != nil { + t.Fatal(err) + } + + bondLink, err := LinkByName(bondName) + if err != nil { + t.Fatal(err) + } + defer LinkDel(bondLink) + + if err := LinkAdd(&Dummy{LinkAttrs{Name: slaveOneName}}); err != nil { + t.Fatal(err) + } + + slaveOneLink, err := LinkByName(slaveOneName) + if err != nil { + t.Fatal(err) + } + defer LinkDel(slaveOneLink) + + if err := LinkAdd(&Dummy{LinkAttrs{Name: slaveTwoName}}); err != nil { + t.Fatal(err) + } + slaveTwoLink, err := LinkByName(slaveTwoName) + if err != nil { + t.Fatal(err) + } + defer LinkDel(slaveTwoLink) + + if err := LinkSetBondSlave(slaveOneLink, &Bond{LinkAttrs: *bondLink.Attrs()}); err != nil { + t.Fatal(err) + } + + if err := LinkSetBondSlave(slaveTwoLink, &Bond{LinkAttrs: *bondLink.Attrs()}); err != nil { + t.Fatal(err) + } + + // Update info about interfaces + slaveOneLink, err = LinkByName(slaveOneName) + if err != nil { + t.Fatal(err) + } + + slaveTwoLink, err = LinkByName(slaveTwoName) + if err != nil { + t.Fatal(err) + } + + if slaveOneLink.Attrs().MasterIndex != bondLink.Attrs().Index { + t.Errorf("For %s expected %s to be master", slaveOneLink.Attrs().Name, bondLink.Attrs().Name) + } + + if slaveTwoLink.Attrs().MasterIndex != bondLink.Attrs().Index { + t.Errorf("For %s expected %s to be master", slaveTwoLink.Attrs().Name, bondLink.Attrs().Name) + } +}