Compare commits

..

12 Commits

Author SHA1 Message Date
fe60fcddb8 Merge pull request #326 from squeed/bump-release-go
Release: bump go to v1.12
2019-05-29 11:30:15 -05:00
e308211d34 Merge pull request #300 from zshi-redhat/host-device-pci-parameter
host-device: add pciBusID property
2019-05-29 11:06:00 -05:00
29a431f1fc Release: bump go to v1.12 2019-05-29 17:58:47 +02:00
7d75ab66a4 Merge pull request #321 from mars1024/modify/bandwidth
bandwidth: get bandwidth interface in host ns through container interface
2019-05-29 17:57:36 +02:00
d5c8b4b206 Merge pull request #325 from nickethier/b-bridge-ipmasq
bridge: fix ipMasq setup to use correct source address
2019-05-29 16:44:18 +01:00
5a4085f1fa Merge pull request #324 from moshe010/fix_386
fix compilation error on 386
2019-05-29 10:43:18 -05:00
37d2ee1d5d bridge: don't use canonical CIDR address when setting up IP masquerade 2019-05-25 20:55:47 -04:00
7f9b1844b8 bridge: add test for ipMasq rules 2019-05-24 23:14:31 -04:00
2753b9af8f fix compilation error on 386
Update github.com/safchain/ethtool to fix the compilation
error on 386. Also added 386 to the tarvis yaml.

Fixes #322

Signed-off-by: Moshe Levi <moshele@mellanox.com>
2019-05-24 23:43:37 +03:00
d35c96dda6 bandwidth: add testcases for func getHostInterface
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-05-21 00:28:04 +08:00
344d343431 bandwidth: get bandwidth interface in host ns through container interface
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-05-18 14:06:45 +08:00
79b1c402c4 host-device: add deviceID property
Fixes #253
2019-04-25 12:17:40 +08:00
16 changed files with 806 additions and 66 deletions

View File

@ -3,14 +3,15 @@ sudo: required
dist: trusty
go:
- 1.10.x
- 1.11.x
- 1.12.x
env:
global:
- PATH=$GOROOT/bin:$GOPATH/bin:$PATH
- CGO_ENABLED=0
matrix:
- TARGET=386
- TARGET=amd64
- TARGET=arm
- TARGET=arm64
@ -22,10 +23,10 @@ matrix:
include:
- os: windows
env: TARGET=amd64
go: 1.10.x
go: 1.11.x
- os: windows
env: TARGET=amd64
go: 1.11.x
go: 1.12.x
install:
- go get github.com/onsi/ginkgo/ginkgo

2
Godeps/Godeps.json generated
View File

@ -314,7 +314,7 @@
},
{
"ImportPath": "github.com/safchain/ethtool",
"Rev": "7ff1ba29eca231991280817541cb3903f6be15d1"
"Rev": "42ed695e3de80b9d695f280295fd7994639f209d"
},
{
"ImportPath": "github.com/sirupsen/logrus",

View File

@ -517,7 +517,7 @@ func cmdAdd(args *skel.CmdArgs) error {
chain := utils.FormatChainName(n.Name, args.ContainerID)
comment := utils.FormatComment(n.Name, args.ContainerID)
for _, ipc := range result.IPs {
if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil {
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
return err
}
}

View File

@ -17,12 +17,14 @@ package main
import (
"encoding/json"
"fmt"
"github.com/vishvananda/netlink/nl"
"io/ioutil"
"net"
"os"
"strings"
"github.com/coreos/go-iptables/iptables"
"github.com/vishvananda/netlink/nl"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
@ -70,6 +72,7 @@ type testCase struct {
isLayer2 bool
expGWCIDRs []string // Expected gateway addresses in CIDR form
vlan int
ipMasq bool
}
// Range definition for each entry in the ranges list
@ -105,8 +108,7 @@ const (
"vlan": %d`
netDefault = `,
"isDefaultGateway": true,
"ipMasq": false`
"isDefaultGateway": true`
ipamStartStr = `,
"ipam": {
@ -115,6 +117,9 @@ const (
ipamDataDirStr = `,
"dataDir": "%s"`
ipMasqConfStr = `,
"ipMasq": %t`
// Single subnet configuration (legacy)
subnetConfStr = `,
"subnet": "%s"`
@ -147,6 +152,9 @@ func (tc testCase) netConfJSON(dataDir string) string {
if tc.vlan != 0 {
conf += fmt.Sprintf(vlan, tc.vlan)
}
if tc.ipMasq {
conf += tc.ipMasqConfig()
}
if !tc.isLayer2 {
conf += netDefault
@ -178,6 +186,11 @@ func (tc testCase) subnetConfig() string {
return conf
}
func (tc testCase) ipMasqConfig() string {
conf := fmt.Sprintf(ipMasqConfStr, tc.ipMasq)
return conf
}
func (tc testCase) rangesConfig() string {
conf := rangesStartStr
for i, tcRange := range tc.ranges {
@ -1595,4 +1608,40 @@ var _ = Describe("bridge Operations", func() {
cmdAddDelCheckTest(originalNS, tc, dataDir)
}
})
It("configures a bridge and ipMasq rules for 0.4.0 config", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
ipMasq: true,
cniVersion: "0.4.0",
}
args := tc.createCmdArgs(originalNS, dataDir)
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).Should(HaveLen(1))
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
rules, err := ipt.List("nat", "POSTROUTING")
Expect(err).NotTo(HaveOccurred())
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})

View File

@ -1,17 +1,25 @@
# host-device
Move an already-existing device into a container.
This simple plugin will move the requested device from the host's network namespace
to the container's. Nothing else will be done - no IPAM, no addresses.
## Overview
The device can be specified with any one of three properties:
This simple plugin will move the requested device from the host's network namespace
to the container's. IPAM configuration can be used for this plugin.
## Network configuration reference
The device can be specified with any one of four properties:
* `device`: The device name, e.g. `eth0`, `can0`
* `hwaddr`: A MAC address
* `kernelpath`: The kernel device kobj, e.g. `/sys/devices/pci0000:00/0000:00:1f.6`
* `pciBusID`: A PCI address of network device, e.g `0000:00:1f.6`
For this plugin, `CNI_IFNAME` will be ignored. Upon DEL, the device will be moved back.
A sample configuration might look like:
## Example configuration
A sample configuration with `device` property looks like:
```json
{
@ -20,3 +28,13 @@ A sample configuration might look like:
"device": "enp0s1"
}
```
A sample configuration with `pciBusID` property looks like:
```json
{
"cniVersion": "0.3.1",
"type": "host-device",
"pciBusID": "0000:3d:00.1"
}
```

View File

@ -21,6 +21,7 @@ import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"runtime"
"strings"
@ -38,12 +39,17 @@ import (
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
const (
sysBusPCI = "/sys/bus/pci/devices"
)
//NetConf for host-device config, look the README to learn how to use those parameters
type NetConf struct {
types.NetConf
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
KernelPath string `json:"kernelpath"` // Kernelpath of the device
PCIAddr string `json:"pciBusID"` // PCI Address of target network device
}
func init() {
@ -58,8 +64,8 @@ func loadConf(bytes []byte) (*NetConf, error) {
if err := json.Unmarshal(bytes, n); err != nil {
return nil, fmt.Errorf("failed to load netconf: %v", err)
}
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" {
return nil, fmt.Errorf(`specify either "device", "hwaddr" or "kernelpath"`)
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
}
return n, nil
}
@ -75,7 +81,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
defer containerNs.Close()
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath)
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
if err != nil {
return fmt.Errorf("failed to find host device: %v", err)
}
@ -240,7 +246,7 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error
return types.PrintResult(&result, cniVersion)
}
func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
links, err := netlink.LinkList()
if err != nil {
return nil, fmt.Errorf("failed to list node links: %v", err)
@ -278,6 +284,19 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
}
}
}
} else if len(pciaddr) > 0 {
netDir := filepath.Join(sysBusPCI, pciaddr, "net")
if _, err := os.Lstat(netDir); err != nil {
return nil, fmt.Errorf("no net directory under pci device %s: %q", pciaddr, err)
}
fInfo, err := ioutil.ReadDir(netDir)
if err != nil {
return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
}
if len(fInfo) > 0 {
return netlink.LinkByName(fInfo[0].Name())
}
return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
}
return nil, fmt.Errorf("failed to find physical interface")

View File

@ -40,6 +40,7 @@ type Net struct {
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
KernelPath string `json:"kernelpath"` // Kernelpath of the device
PCIAddr string `json:"pciBusID"` // PCI Address of target network device
IPAM *IPAMConfig `json:"ipam,omitempty"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
@ -449,7 +450,7 @@ var _ = Describe("base functionality", func() {
StdinData: []byte(conf),
}
_, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
Expect(err).To(MatchError(`specify either "device", "hwaddr" or "kernelpath"`))
Expect(err).To(MatchError(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`))
})

View File

@ -621,6 +621,257 @@ var _ = Describe("bandwidth test", func() {
})
})
Describe("Getting the host interface which plugin should work on from veth peer of container interface", func() {
It("Should work with multiple host veth interfaces", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"ingressRate": 8,
"ingressBurst": 8,
"egressRate": 16,
"egressBurst": 8,
"prevResult": {
"interfaces": [
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": "%s"
}
],
"ips": [
{
"version": "4",
"address": "%s/24",
"gateway": "10.0.0.1",
"interface": 1
}
],
"routes": []
}
}`
// create veth peer in host ns
vethName, peerName := "host-veth-peer1", "host-veth-peer2"
createVethInOneNs(hostNs.Path(), vethName, peerName)
conf = fmt.Sprintf(conf, vethName, peerName, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: containerNs.Path(),
IfName: containerIfname,
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.Interfaces).To(HaveLen(5))
Expect(result.Interfaces[4].Name).To(Equal(ifbDeviceName))
Expect(result.Interfaces[4].Sandbox).To(Equal(""))
ifbLink, err := netlink.LinkByName(ifbDeviceName)
Expect(err).NotTo(HaveOccurred())
Expect(ifbLink.Attrs().MTU).To(Equal(hostIfaceMTU))
qdiscs, err := netlink.QdiscList(ifbLink)
Expect(err).NotTo(HaveOccurred())
Expect(qdiscs).To(HaveLen(1))
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(2)))
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1)))
hostVethLink, err := netlink.LinkByName(hostIfname)
Expect(err).NotTo(HaveOccurred())
qdiscFilters, err := netlink.FilterList(hostVethLink, netlink.MakeHandle(0xffff, 0))
Expect(err).NotTo(HaveOccurred())
Expect(qdiscFilters).To(HaveLen(1))
Expect(qdiscFilters[0].(*netlink.U32).Actions[0].(*netlink.MirredAction).Ifindex).To(Equal(ifbLink.Attrs().Index))
return nil
})).To(Succeed())
Expect(hostNs.Do(func(n ns.NetNS) error {
defer GinkgoRecover()
ifbLink, err := netlink.LinkByName(hostIfname)
Expect(err).NotTo(HaveOccurred())
qdiscs, err := netlink.QdiscList(ifbLink)
Expect(err).NotTo(HaveOccurred())
Expect(qdiscs).To(HaveLen(2))
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1)))
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1)))
return nil
})).To(Succeed())
})
It("Should fail when container interface has no veth peer", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"ingressRate": 8,
"ingressBurst": 8,
"egressRate": 16,
"egressBurst": 8,
"prevResult": {
"interfaces": [
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": "%s"
}
],
"ips": [
{
"version": "4",
"address": "%s/24",
"gateway": "10.0.0.1",
"interface": 1
}
],
"routes": []
}
}`
// create a macvlan device to be container interface
macvlanContainerIfname := "container-macv"
createMacvlan(containerNs.Path(), containerIfname, macvlanContainerIfname)
conf = fmt.Sprintf(conf, hostIfname, macvlanContainerIfname, containerNs.Path(), containerIP.String())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: containerNs.Path(),
IfName: macvlanContainerIfname,
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).To(HaveOccurred())
return nil
})).To(Succeed())
})
It("Should fail when preResult has no interfaces", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"ingressRate": 8,
"ingressBurst": 8,
"egressRate": 16,
"egressBurst": 8,
"prevResult": {
"interfaces": [],
"ips": [],
"routes": []
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: containerNs.Path(),
IfName: "eth0",
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).To(HaveOccurred())
return nil
})).To(Succeed())
})
It("Should fail when veth peer of container interface does not match any of host interfaces in preResult", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"ingressRate": 8,
"ingressBurst": 8,
"egressRate": 16,
"egressBurst": 8,
"prevResult": {
"interfaces": [
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": "%s"
}
],
"ips": [
{
"version": "4",
"address": "%s/24",
"gateway": "10.0.0.1",
"interface": 1
}
],
"routes": []
}
}`
// fake a non-exist host interface name
fakeHostIfname := fmt.Sprintf("%s-fake", hostIfname)
conf = fmt.Sprintf(conf, fakeHostIfname, containerIfname, containerNs.Path(), containerIP.String())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: containerNs.Path(),
IfName: containerIfname,
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).To(HaveOccurred())
return nil
})).To(Succeed())
})
})
Context("when chaining bandwidth plugin with PTP using 0.3.0 config", func() {
var ptpConf string
var rateInBits int

View File

@ -199,3 +199,61 @@ func createVeth(hostNamespace string, hostVethIfName string, containerNamespace
Expect(err).NotTo(HaveOccurred())
}
func createVethInOneNs(namespace, vethName, peerName string) {
vethDeviceRequest := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: vethName,
Flags: net.FlagUp,
},
PeerName: peerName,
}
netNS, err := ns.GetNS(namespace)
Expect(err).NotTo(HaveOccurred())
err = netNS.Do(func(_ ns.NetNS) error {
if err := netlink.LinkAdd(vethDeviceRequest); err != nil {
return fmt.Errorf("failed to create veth pair: %v", err)
}
_, err := netlink.LinkByName(peerName)
if err != nil {
return fmt.Errorf("failed to find newly-created veth device %q: %v", peerName, err)
}
return nil
})
Expect(err).NotTo(HaveOccurred())
}
func createMacvlan(namespace, master, macvlanName string) {
netNS, err := ns.GetNS(namespace)
Expect(err).NotTo(HaveOccurred())
err = netNS.Do(func(_ ns.NetNS) error {
m, err := netlink.LinkByName(master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", master, err)
}
macvlanDeviceRequest := &netlink.Macvlan{
LinkAttrs: netlink.LinkAttrs{
MTU: m.Attrs().MTU,
Name: macvlanName,
ParentIndex: m.Attrs().Index,
},
Mode: netlink.MACVLAN_MODE_BRIDGE,
}
if err = netlink.LinkAdd(macvlanDeviceRequest); err != nil {
return fmt.Errorf("failed to create macvlan device: %s", err)
}
_, err = netlink.LinkByName(macvlanName)
if err != nil {
return fmt.Errorf("failed to find newly-created macvlan device %q: %v", macvlanName, err)
}
return nil
})
Expect(err).NotTo(HaveOccurred())
}

View File

@ -17,7 +17,6 @@ package main
import (
"crypto/sha1"
"encoding/json"
"errors"
"fmt"
"github.com/vishvananda/netlink"
@ -28,6 +27,7 @@ import (
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
@ -130,21 +130,35 @@ func getMTU(deviceName string) (int, error) {
return link.Attrs().MTU, nil
}
func getHostInterface(interfaces []*current.Interface) (*current.Interface, error) {
// get the veth peer of container interface in host namespace
func getHostInterface(interfaces []*current.Interface, containerIfName string, netns ns.NetNS) (*current.Interface, error) {
if len(interfaces) == 0 {
return nil, errors.New("no interfaces provided")
return nil, fmt.Errorf("no interfaces provided")
}
// get veth peer index of container interface
var peerIndex int
var err error
_ = netns.Do(func(_ ns.NetNS) error {
_, peerIndex, err = ip.GetVethPeerIfindex(containerIfName)
return nil
})
if peerIndex <= 0 {
return nil, fmt.Errorf("container interface %s has no veth peer: %v", containerIfName, err)
}
// find host interface by index
link, err := netlink.LinkByIndex(peerIndex)
if err != nil {
return nil, fmt.Errorf("veth peer with index %d is not in host ns", peerIndex)
}
for _, iface := range interfaces {
if iface.Sandbox == "" { // host interface
_, _, err = ip.GetVethPeerIfindex(iface.Name)
if err == nil {
return iface, err
}
if iface.Sandbox == "" && iface.Name == link.Attrs().Name {
return iface, nil
}
}
return nil, errors.New(fmt.Sprintf("no host interface found. last error: %s", err))
return nil, fmt.Errorf("no veth peer of container interface found in host ns")
}
func cmdAdd(args *skel.CmdArgs) error {
@ -166,7 +180,14 @@ func cmdAdd(args *skel.CmdArgs) error {
if err != nil {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns)
if err != nil {
return err
}
@ -266,7 +287,13 @@ func cmdCheck(args *skel.CmdArgs) error {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns)
if err != nil {
return err
}

View File

@ -16,7 +16,7 @@ rm -Rf ${SRC_DIR}/${RELEASE_DIR}
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
mkdir -p ${OUTPUT_DIR}
$DOCKER run -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.10-alpine \
$DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.12-alpine \
/bin/sh -xe -c "\
apk --no-cache add bash tar;
cd /go/src/github.com/containernetworking/plugins; umask 0022;

View File

@ -22,3 +22,6 @@ _testmain.go
*.exe
*.test
*.prof
# Skip compiled example binary file
/example/example

4
vendor/github.com/safchain/ethtool/Makefile generated vendored Normal file
View File

@ -0,0 +1,4 @@
all: build
build:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

View File

@ -33,6 +33,7 @@ func main() {
if err != nil {
panic(err.Error())
}
defer ethHandle.Close()
// Retrieve tx from eth0
stats, err := ethHandle.Stats("eth0")

View File

@ -27,7 +27,9 @@ package ethtool
import (
"bytes"
"encoding/hex"
"fmt"
"strings"
"syscall"
"unsafe"
)
@ -46,20 +48,34 @@ const (
const (
ETH_GSTRING_LEN = 32
ETH_SS_STATS = 1
ETH_SS_FEATURES = 4
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. */
ETHTOOL_GSET = 0x00000001 /* Get settings. */
ETHTOOL_SSET = 0x00000002 /* Set settings. */
ETHTOOL_GMSGLVL = 0x00000007 /* Get driver message level */
ETHTOOL_SMSGLVL = 0x00000008 /* Set driver msg level. */
/* Get link status for host, i.e. whether the interface *and* the
* physical port (if there is one) are up (ethtool_value). */
ETHTOOL_GLINK = 0x0000000a
ETHTOOL_GMODULEINFO = 0x00000042 /* Get plug-in module information */
ETHTOOL_GMODULEEEPROM = 0x00000043 /* Get plug-in module eeprom */
ETHTOOL_GPERMADDR = 0x00000020
ETHTOOL_GFEATURES = 0x0000003a /* Get device offload settings */
ETHTOOL_SFEATURES = 0x0000003b /* Change device offload settings */
ETHTOOL_GFLAGS = 0x00000025 /* Get flags bitmap(ethtool_value) */
ETHTOOL_GSSET_INFO = 0x00000037 /* Get string set info */
)
// MAX_GSTRINGS maximum number of stats entries that ethtool can
// retrieve currently.
const (
MAX_GSTRINGS = 1000
MAX_GSTRINGS = 1000
MAX_FEATURE_BLOCKS = (MAX_GSTRINGS + 32 - 1) / 32
EEPROM_LEN = 640
PERMADDR_LEN = 32
)
type ifreq struct {
@ -67,6 +83,38 @@ type ifreq struct {
ifr_data uintptr
}
// following structures comes from uapi/linux/ethtool.h
type ethtoolSsetInfo struct {
cmd uint32
reserved uint32
sset_mask uint32
data uintptr
}
type ethtoolGetFeaturesBlock struct {
available uint32
requested uint32
active uint32
never_changed uint32
}
type ethtoolGfeatures struct {
cmd uint32
size uint32
blocks [MAX_FEATURE_BLOCKS]ethtoolGetFeaturesBlock
}
type ethtoolSetFeaturesBlock struct {
valid uint32
requested uint32
}
type ethtoolSfeatures struct {
cmd uint32
size uint32
blocks [MAX_FEATURE_BLOCKS]ethtoolSetFeaturesBlock
}
type ethtoolDrvInfo struct {
cmd uint32
driver [32]byte
@ -95,11 +143,37 @@ type ethtoolStats struct {
data [MAX_GSTRINGS]uint64
}
type ethtoolEeprom struct {
cmd uint32
magic uint32
offset uint32
len uint32
data [EEPROM_LEN]byte
}
type ethtoolModInfo struct {
cmd uint32
tpe uint32
eeprom_len uint32
reserved [8]uint32
}
type ethtoolLink struct {
cmd uint32
data uint32
}
type ethtoolPermAddr struct {
cmd uint32
size uint32
data [PERMADDR_LEN]byte
}
type Ethtool struct {
fd int
}
// DriverName returns the driver name of the given interface.
// DriverName returns the driver name of the given interface name.
func (e *Ethtool) DriverName(intf string) (string, error) {
info, err := e.getDriverInfo(intf)
if err != nil {
@ -108,7 +182,7 @@ func (e *Ethtool) DriverName(intf string) (string, error) {
return string(bytes.Trim(info.driver[:], "\x00")), nil
}
// BusInfo returns the bus info of the given interface.
// BusInfo returns the bus information of the given interface name.
func (e *Ethtool) BusInfo(intf string) (string, error) {
info, err := e.getDriverInfo(intf)
if err != nil {
@ -117,44 +191,259 @@ func (e *Ethtool) BusInfo(intf string) (string, error) {
return string(bytes.Trim(info.bus_info[:], "\x00")), nil
}
func (e *Ethtool) getDriverInfo(intf string) (ethtoolDrvInfo, error) {
drvinfo := ethtoolDrvInfo{
cmd: ETHTOOL_GDRVINFO,
// ModuleEeprom returns Eeprom information of the given interface name.
func (e *Ethtool) ModuleEeprom(intf string) ([]byte, error) {
eeprom, _, err := e.getModuleEeprom(intf)
if err != nil {
return nil, err
}
return eeprom.data[:eeprom.len], nil
}
// ModuleEeprom returns Eeprom information of the given interface name.
func (e *Ethtool) ModuleEepromHex(intf string) (string, error) {
eeprom, _, err := e.getModuleEeprom(intf)
if err != nil {
return "", err
}
return hex.EncodeToString(eeprom.data[:eeprom.len]), nil
}
// DriverInfo returns driver information of the given interface name.
func (e *Ethtool) DriverInfo(intf string) (ethtoolDrvInfo, error) {
drvInfo, err := e.getDriverInfo(intf)
if err != nil {
return ethtoolDrvInfo{}, err
}
return drvInfo, nil
}
// PermAddr returns permanent address of the given interface name.
func (e *Ethtool) PermAddr(intf string) (string, error) {
permAddr, err := e.getPermAddr(intf)
if err != nil {
return "", err
}
if permAddr.data[0] == 0 && permAddr.data[1] == 0 &&
permAddr.data[2] == 0 && permAddr.data[3] == 0 &&
permAddr.data[4] == 0 && permAddr.data[5] == 0 {
return "", nil
}
return fmt.Sprintf("%x:%x:%x:%x:%x:%x",
permAddr.data[0:1],
permAddr.data[1:2],
permAddr.data[2:3],
permAddr.data[3:4],
permAddr.data[4:5],
permAddr.data[5:6],
), nil
}
func (e *Ethtool) ioctl(intf string, data uintptr) error {
var name [IFNAMSIZ]byte
copy(name[:], []byte(intf))
ifr := ifreq{
ifr_name: name,
ifr_data: uintptr(unsafe.Pointer(&drvinfo)),
ifr_data: data,
}
_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
if ep != 0 {
return ethtoolDrvInfo{}, syscall.Errno(ep)
return syscall.Errno(ep)
}
return nil
}
func (e *Ethtool) getDriverInfo(intf string) (ethtoolDrvInfo, error) {
drvinfo := ethtoolDrvInfo{
cmd: ETHTOOL_GDRVINFO,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&drvinfo))); err != nil {
return ethtoolDrvInfo{}, err
}
return drvinfo, nil
}
func (e *Ethtool) getPermAddr(intf string) (ethtoolPermAddr, error) {
permAddr := ethtoolPermAddr{
cmd: ETHTOOL_GPERMADDR,
size: PERMADDR_LEN,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&permAddr))); err != nil {
return ethtoolPermAddr{}, err
}
return permAddr, nil
}
func (e *Ethtool) getModuleEeprom(intf string) (ethtoolEeprom, ethtoolModInfo, error) {
modInfo := ethtoolModInfo{
cmd: ETHTOOL_GMODULEINFO,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&modInfo))); err != nil {
return ethtoolEeprom{}, ethtoolModInfo{}, err
}
eeprom := ethtoolEeprom{
cmd: ETHTOOL_GMODULEEEPROM,
len: modInfo.eeprom_len,
offset: 0,
}
if modInfo.eeprom_len > EEPROM_LEN {
return ethtoolEeprom{}, ethtoolModInfo{}, fmt.Errorf("eeprom size: %d is larger than buffer size: %d", modInfo.eeprom_len, EEPROM_LEN)
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&eeprom))); err != nil {
return ethtoolEeprom{}, ethtoolModInfo{}, err
}
return eeprom, modInfo, nil
}
func isFeatureBitSet(blocks [MAX_FEATURE_BLOCKS]ethtoolGetFeaturesBlock, index uint) bool {
return (blocks)[index/32].active&(1<<(index%32)) != 0
}
func setFeatureBit(blocks *[MAX_FEATURE_BLOCKS]ethtoolSetFeaturesBlock, index uint, value bool) {
blockIndex, bitIndex := index/32, index%32
blocks[blockIndex].valid |= 1 << bitIndex
if value {
blocks[blockIndex].requested |= 1 << bitIndex
} else {
blocks[blockIndex].requested &= ^(1 << bitIndex)
}
}
// FeatureNames shows supported features by their name.
func (e *Ethtool) FeatureNames(intf string) (map[string]uint, error) {
ssetInfo := ethtoolSsetInfo{
cmd: ETHTOOL_GSSET_INFO,
sset_mask: 1 << ETH_SS_FEATURES,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&ssetInfo))); err != nil {
return nil, err
}
length := uint32(ssetInfo.data)
if length == 0 {
return map[string]uint{}, nil
} else if length > MAX_GSTRINGS {
return nil, fmt.Errorf("ethtool currently doesn't support more than %d entries, received %d", MAX_GSTRINGS, length)
}
gstrings := ethtoolGStrings{
cmd: ETHTOOL_GSTRINGS,
string_set: ETH_SS_FEATURES,
len: length,
data: [MAX_GSTRINGS * ETH_GSTRING_LEN]byte{},
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&gstrings))); err != nil {
return nil, err
}
var result = make(map[string]uint)
for i := 0; i != int(length); i++ {
b := gstrings.data[i*ETH_GSTRING_LEN : i*ETH_GSTRING_LEN+ETH_GSTRING_LEN]
key := string(bytes.Trim(b, "\x00"))
if key != "" {
result[key] = uint(i)
}
}
return result, nil
}
// Features retrieves features of the given interface name.
func (e *Ethtool) Features(intf string) (map[string]bool, error) {
names, err := e.FeatureNames(intf)
if err != nil {
return nil, err
}
length := uint32(len(names))
if length == 0 {
return map[string]bool{}, nil
}
features := ethtoolGfeatures{
cmd: ETHTOOL_GFEATURES,
size: (length + 32 - 1) / 32,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&features))); err != nil {
return nil, err
}
var result = make(map[string]bool, length)
for key, index := range names {
result[key] = isFeatureBitSet(features.blocks, index)
}
return result, nil
}
// Change requests a change in the given device's features.
func (e *Ethtool) Change(intf string, config map[string]bool) error {
names, err := e.FeatureNames(intf)
if err != nil {
return err
}
length := uint32(len(names))
features := ethtoolSfeatures{
cmd: ETHTOOL_SFEATURES,
size: (length + 32 - 1) / 32,
}
for key, value := range config {
if index, ok := names[key]; ok {
setFeatureBit(&features.blocks, index, value)
} else {
return fmt.Errorf("unsupported feature %q", key)
}
}
return e.ioctl(intf, uintptr(unsafe.Pointer(&features)))
}
// Get state of a link.
func (e *Ethtool) LinkState(intf string) (uint32, error) {
x := ethtoolLink{
cmd: ETHTOOL_GLINK,
}
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&x))); err != nil {
return 0, err
}
return x.data, 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 err := e.ioctl(intf, uintptr(unsafe.Pointer(&drvinfo))); err != nil {
return nil, err
}
if drvinfo.n_stats*ETH_GSTRING_LEN > MAX_GSTRINGS*ETH_GSTRING_LEN {
@ -167,11 +456,9 @@ func (e *Ethtool) Stats(intf string) (map[string]uint64, error) {
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)
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&gstrings))); err != nil {
return nil, err
}
stats := ethtoolStats{
@ -180,31 +467,32 @@ func (e *Ethtool) Stats(intf string) (map[string]uint64, error) {
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)
if err := e.ioctl(intf, uintptr(unsafe.Pointer(&stats))); err != nil {
return nil, err
}
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]
key := string(b[:strings.Index(string(b), "\x00")])
if len(key) != 0 {
result[key] = stats.data[i]
}
}
return result, nil
}
// Close closes the ethool handler
func (e *Ethtool) Close() {
syscall.Close(e.fd)
}
// NewEthtool returns a new ethtool handler
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)
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
if err != nil {
return nil, err
}
return &Ethtool{
@ -212,6 +500,7 @@ func NewEthtool() (*Ethtool, error) {
}, nil
}
// BusInfo returns bus information of the given interface name.
func BusInfo(intf string) (string, error) {
e, err := NewEthtool()
if err != nil {
@ -221,6 +510,7 @@ func BusInfo(intf string) (string, error) {
return e.BusInfo(intf)
}
// DriverName returns the driver name of the given interface name.
func DriverName(intf string) (string, error) {
e, err := NewEthtool()
if err != nil {
@ -230,6 +520,7 @@ func DriverName(intf string) (string, error) {
return e.DriverName(intf)
}
// Stats retrieves stats of the given interface name.
func Stats(intf string) (map[string]uint64, error) {
e, err := NewEthtool()
if err != nil {
@ -238,3 +529,13 @@ func Stats(intf string) (map[string]uint64, error) {
defer e.Close()
return e.Stats(intf)
}
// PermAddr returns permanent address of the given interface name.
func PermAddr(intf string) (string, error) {
e, err := NewEthtool()
if err != nil {
return "", err
}
defer e.Close()
return e.PermAddr(intf)
}

View File

@ -26,6 +26,7 @@
package ethtool
import (
"math"
"reflect"
"syscall"
"unsafe"
@ -128,6 +129,9 @@ func (e *Ethtool) CmdGet(ecmd *EthtoolCmd, intf string) (uint32, error) {
var speedval uint32 = (uint32(ecmd.Speed_hi) << 16) |
(uint32(ecmd.Speed) & 0xffff)
if speedval == math.MaxUint16 {
speedval = math.MaxUint32
}
return speedval, nil
}
@ -153,6 +157,9 @@ func (e *Ethtool) CmdSet(ecmd *EthtoolCmd, intf string) (uint32, error) {
var speedval uint32 = (uint32(ecmd.Speed_hi) << 16) |
(uint32(ecmd.Speed) & 0xffff)
if speedval == math.MaxUint16 {
speedval = math.MaxUint32
}
return speedval, nil
}