func addRoute(cid, CIDR, ifc string, pid int) bool { runtime.LockOSThread() defer runtime.UnlockOSThread() origins, err := netns.Get() if err != nil { logs.Info("Get orignal namespace failed", err) return false } defer origins.Close() ns, err := netns.GetFromPid(pid) if err != nil { logs.Info("Get container namespace failed", err) return false } netns.Set(ns) defer ns.Close() defer netns.Set(origins) if err := addRouteByLink(CIDR, ifc); err != nil { logs.Info("Add route failed", err) return false } logs.Info("Add route success", cid[:12], CIDR, ifc) return true }
// Search the network namespace of a process for interfaces matching a predicate // Note that the predicate is called while the goroutine is inside the process' netns func FindNetDevs(processID int, match func(link netlink.Link) bool) ([]NetDev, error) { var netDevs []NetDev ns, err := netns.GetFromPid(processID) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } defer ns.Close() err = weavenet.WithNetNSUnsafe(ns, func() error { return forEachLink(func(link netlink.Link) error { if match(link) { netDev, err := linkToNetDev(link) if err != nil { return err } netDevs = append(netDevs, netDev) } return nil }) }) return netDevs, err }
func GetNetDevsWithPredicate(processID int, predicate func(link netlink.Link) bool) ([]NetDev, error) { // Bail out if this container is running in the root namespace nsToplevel, err := netns.GetFromPid(1) if err != nil { return nil, fmt.Errorf("unable to open root namespace: %s", err) } nsContainr, err := netns.GetFromPid(processID) if err != nil { return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err) } if nsToplevel.Equal(nsContainr) { return nil, nil } return FindNetDevs(processID, predicate) }
func setUpVLan(cid, ips string, pid int, veth netlink.Link) bool { runtime.LockOSThread() defer runtime.UnlockOSThread() origns, err := netns.Get() if err != nil { logs.Info("Get orignal namespace failed", err) return false } defer origns.Close() ns, err := netns.GetFromPid(pid) if err != nil { logs.Info("Get container namespace failed", err) return false } netns.Set(ns) defer ns.Close() defer netns.Set(origns) if err := BindAndSetup(veth, ips); err != nil { logs.Info("Bind and setup NIC failed", err) DelVlan(veth) return false } logs.Info("Add vlan device success", cid[:12]) return true }
func setDefaultRoute(cid, gateway string, pid int) bool { runtime.LockOSThread() defer runtime.UnlockOSThread() origins, err := netns.Get() if err != nil { logs.Info("Get orignal namespace failed", err) return false } defer origins.Close() ns, err := netns.GetFromPid(pid) if err != nil { logs.Info("Get container namespace failed", err) return false } netns.Set(ns) defer ns.Close() defer netns.Set(origins) if err := delDefaultRoute(); err != nil { logs.Info("Delete default routing table failed", err) return false } if err := addDefaultRoute(gateway); err != nil { logs.Info("Add default route failed", err) return false } logs.Info("Set default route success", cid[:12], gateway) return true }
func WithNetNSLinkByPidUnsafe(pid int, ifName string, work func(link netlink.Link) error) error { ns, err := netns.GetFromPid(pid) if err != nil { return err } defer ns.Close() return WithNetNSLinkUnsafe(ns, ifName, work) }
func (probe *DockerProbe) registerContainer(id string) { probe.Lock() defer probe.Unlock() if _, ok := probe.containerMap[id]; ok { return } info, err := probe.client.ContainerInspect(context.Background(), id) if err != nil { logging.GetLogger().Errorf("Failed to inspect Docker container %s: %s", id, err.Error()) return } nsHandle, err := netns.GetFromPid(info.State.Pid) if err != nil { return } defer nsHandle.Close() namespace := probe.containerNamespace(info.State.Pid) logging.GetLogger().Debugf("Register docker container %s and PID %d", info.ID, info.State.Pid) var n *graph.Node if probe.hostNs.Equal(nsHandle) { // The container is in net=host mode n = probe.Root } else { n = probe.Register(namespace, graph.Metadata{"Name": info.Name[1:], "Manager": "docker"}) } probe.Graph.Lock() metadata := graph.Metadata{ "Type": "container", "Name": info.Name[1:], "Docker/ContainerID": info.ID, "Docker/ContainerName": info.Name, "Docker/ContainerPID": info.State.Pid, } containerNode := probe.Graph.NewNode(graph.GenID(), metadata) probe.Graph.Link(n, containerNode, graph.Metadata{"RelationType": "membership"}) probe.Graph.Unlock() probe.containerMap[info.ID] = ContainerInfo{ Pid: info.State.Pid, Node: containerNode, } }
func EnterDockerNetNs(container *docker.Container) error { var err error origNs, err = netns.Get() if err != nil { return log.Criticalf("could not obtain current netns. netns not supported?: %s", err) } // netns.GetFromDocker() does not work for recent dockers. // So we use netns.GetFromPid() directly. // https://github.com/vishvananda/netns/pull/10 newNs, err = netns.GetFromPid(container.State.Pid) if err != nil { return log.Criticalf("Could not get netns for container %s (pid=%d). The container has exited??: %s", container.ID, container.State.Pid, err) } runtime.LockOSThread() netns.Set(newNs) return nil }
func parent() { cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[2:]...)...) cmd.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET, } cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr fmt.Println("Pid:", os.Getpid()) endpointCreate() ns, err := netns.GetFromPid(os.Getpid()) if err != nil { panic(err) } must(netns.Set(ns)) temp_path := prepareOverlay("fs/orig", "fs/rootfs") if err := cmd.Start(); err != nil { fmt.Println("ERROR", err) os.Exit(1) } path := fmt.Sprintf("/proc/%d/ns/net", cmd.Process.Pid) tmpfile, err := os.OpenFile(path, os.O_RDONLY, 0) if err != nil { panic(err) } nsfd := int(tmpfile.Fd()) endpointSetNs(nsfd) if err := cmd.Wait(); err != nil { fmt.Println("ERROR", err) os.Exit(1) } endpointDestroy() unmountOverlay("fs/rootfs", temp_path) }
func attach(args []string) error { if len(args) < 4 { cmdUsage("attach-container", "[--no-multicast-route] [--keep-tx-on] <container-id> <bridge-name> <mtu> <cidr>...") } keepTXOn := false withMulticastRoute := true for i := 0; i < len(args); { switch args[i] { case "--no-multicast-route": withMulticastRoute = false args = append(args[:i], args[i+1:]...) case "--keep-tx-on": keepTXOn = true args = append(args[:i], args[i+1:]...) default: i++ } } pid, nsContainer, err := containerPidAndNs(args[0]) if err != nil { return err } if nsHost, err := netns.GetFromPid(1); err != nil { return fmt.Errorf("unable to open host namespace: %s", err) } else if nsHost.Equal(nsContainer) { return fmt.Errorf("Container is running in the host network namespace, and therefore cannot be\nconnected to weave. Perhaps the container was started with --net=host.") } mtu, err := strconv.Atoi(args[2]) if err != nil && args[3] != "" { return fmt.Errorf("unable to parse mtu %q: %s", args[2], err) } cidrs, err := parseCIDRs(args[3:]) if err != nil { return err } err = weavenet.AttachContainer(nsContainer, fmt.Sprint(pid), weavenet.VethName, args[1], mtu, withMulticastRoute, cidrs, keepTXOn) // If we detected an error but the container has died, tell the user that instead. if err != nil && !processExists(pid) { err = fmt.Errorf("Container %s died", args[0]) } return err }
func containerPidAndNs(containerID string) (int, netns.NsHandle, error) { c, err := docker.NewVersionedClientFromEnv("1.18") if err != nil { return 0, 0, fmt.Errorf("unable to connect to docker: %s", err) } container, err := c.InspectContainer(containerID) if err != nil { return 0, 0, fmt.Errorf("unable to inspect container %s: %s", containerID, err) } if container.State.Pid == 0 { return 0, 0, fmt.Errorf("container %s not running", containerID) } ns, err := netns.GetFromPid(container.State.Pid) if err != nil { return 0, 0, fmt.Errorf("unable to open namespace for container %s: %s", containerID, err) } return container.State.Pid, ns, nil }
func networkStatsFromNs(pid int) ([]info.InterfaceStats, error) { // Lock the OS Thread so we only change the ns for this thread exclusively runtime.LockOSThread() defer runtime.UnlockOSThread() stats := []info.InterfaceStats{} // Save the current network namespace origns, _ := netns.Get() defer origns.Close() // Switch to the pid netns pidns, err := netns.GetFromPid(pid) defer pidns.Close() if err != nil { return stats, nil } netns.Set(pidns) // Defer setting back to original ns defer netns.Set(origns) ifaceStats, err := scanInterfaceStats() if err != nil { return stats, fmt.Errorf("couldn't read network stats: %v", err) } ifaces, err := net.Interfaces() if err != nil { return stats, fmt.Errorf("cannot find interfaces: %v", err) } for _, iface := range ifaces { if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 { if s, ok := ifaceStats[iface.Name]; ok { stats = append(stats, s) } } } return stats, nil }
func (c Container) ApplyRules(fw firewall) error { // Lock the OS Thread so we don't accidentally switch namespaces runtime.LockOSThread() defer runtime.UnlockOSThread() // Save the current network namespace origns, _ := netns.Get() defer origns.Close() // Create a new network namespace pid, _ := c.firstPid() log.Printf("pid %v", pid) newns, _ := netns.GetFromPid(pid) defer newns.Close() // Switch to the container namespace netns.Set(newns) // Input cmd := append([]string{"-t", "filter", "-F", "INPUT"}) err := exec.Command(iptablesPath, cmd...).Run() if err != nil { log.Println("Couldn't apply clear rule") } for pos, rule := range fw.Input.Rules { if rule.Type != "" { sport, _ := strconv.Atoi(rule.SourcePort) dport, _ := strconv.Atoi(rule.DestinationPort) if *Debug { log.Printf("ApplyRules - Source : %s SourcePort %v Destination : %s DestinationPort : %v Proto : %s Type : %s", rule.Source, sport, rule.Destination, dport, rule.Proto, rule.Type) } ippos := pos + 1 args := []string{"-t", "filter", "-I", "INPUT", strconv.Itoa(ippos), "-p", rule.Proto, "--source", rule.Source, "--destination-port", rule.DestinationPort, "-j", strings.ToUpper(rule.Type), "--wait"} if *Debug { log.Printf("ApplyRules cmd %s - args : %s", iptablesPath, args) } cmd := exec.Cmd{Path: iptablesPath, Args: append([]string{iptablesPath}, args...)} err := cmd.Run() if err != nil { log.Println("Couldn't apply rule %v", pos) } } } // Switch back to the original namespace netns.Set(origns) runtime.UnlockOSThread() return nil }
func AddVlannetwork(etcdval string, vlanid string, containerName string) { ss := strings.Split(etcdval, ",") hostif := ss[0] if ok := utils.ValidateHostIface(hostif); !ok { log.Warnf("the host interface not exist") return } vlandevName := hostif + "." + vlanid hostEth, _ := netlink.LinkByName(hostif) intvlanid, err := strconv.Atoi(vlanid) if err != nil { log.Warnf("the vlan id convert error: \n") return } var vlandev *netlink.Vlan if ok := utils.ValidateHostIface(vlandevName); ok { } else { //not exist ,create the vlan device vlandev = &netlink.Vlan{ LinkAttrs: netlink.LinkAttrs{ Name: vlandevName, ParentIndex: hostEth.Attrs().Index, }, VlanId: intvlanid, } if err := netlink.LinkAdd(vlandev); err != nil { log.Warnf("failed to create vlandev: [ %v ] with the error: %s", vlandev, err) return } } netlink.LinkSetUp(vlandev) macvlanname, _ := utils.GenerateRandomName("vlan"+vlanid, 5) //create the macvlan device macvlandev := &netlink.Macvlan{ LinkAttrs: netlink.LinkAttrs{ Name: macvlanname, ParentIndex: vlandev.Attrs().Index, }, Mode: netlink.MACVLAN_MODE_BRIDGE, } if err := netlink.LinkAdd(macvlandev); err != nil { log.Warnf("failed to create Macvlan: [ %v ] with the error: %s", macvlandev, err) return } dockerPid := utils.DockerPid(containerName) //the macvlandev can be use directly, don't get netlink.byname again. netlink.LinkSetNsPid(macvlandev, dockerPid) runtime.LockOSThread() defer runtime.UnlockOSThread() //get root network naAddVlannetworkmespace origns, _ := netns.Get() defer origns.Close() //enter the docker container network dockerNS, _ := netns.GetFromPid(dockerPid) defer dockerNS.Close() netns.Set(dockerNS) netlink.LinkSetDown(macvlandev) netlink.LinkSetName(macvlandev, "eth1") _, network, _ := net.ParseCIDR(ss[1]) if _, ok := ipallocs[vlanid]; !ok { log.Fatalf("the ipallocator is null \n") } ip, _ := ipallocs[vlanid].RequestIP(network, nil) ind := strings.LastIndex(ss[1], "/") ipstring := ip.String() + ss[1][ind:] addr, err := netlink.ParseAddr(ipstring) netlink.AddrAdd(macvlandev, addr) netlink.LinkSetUp(macvlandev) /* routes, _ := netlink.RouteList(nil, netlink.FAMILY_V4) for _, r := range routes { if r.Dst == nil { if err := netlink.RouteDel(&r); err != nil { log.Warnf("delete the default error: ", err) } } } if CligwIP == "" { log.Fatal("container gw is null") } defaultRoute := &netlink.Route{ Dst: nil, Gw: net.ParseIP(CligwIP), LinkIndex: macvlandev1.Attrs().Index, } if err := netlink.RouteAdd(defaultRoute); err != nil { log.Warnf("create default route error: ", err) } */ netns.Set(origns) }
//netlink is not avaible in MAC OS, build fail. func AddContainerNetworking() { if CliIF == "" { log.Fatal("the host-interface is missing,please give one") } if ok := utils.ValidateHostIface(CliIF); !ok { log.Fatalf("the host-interface [ %s ] was not found.", CliIF) } hostmacvlanname, _ := utils.GenerateRandomName(hostprefix, hostlen) hostEth, _ := netlink.LinkByName(CliIF) //create the macvlan device macvlandev := &netlink.Macvlan{ LinkAttrs: netlink.LinkAttrs{ Name: hostmacvlanname, ParentIndex: hostEth.Attrs().Index, }, Mode: netlink.MACVLAN_MODE_BRIDGE, } if err := netlink.LinkAdd(macvlandev); err != nil { log.Fatalf("failed to create Macvlan: [ %v ] with the error: %s", macvlandev.Attrs().Name, err) } // log.Infof("Created Macvlan port: [ %s ] using the mode: [ %s ]", macvlan.Name, macvlanMode) // ugly, actually ,can get the ns from netns.getfromDocker. the netns have many function, netns.getformpid // netns.getfromdocker the arg can not be the container name dockerPid := utils.DockerPid(CliCName) //the macvlandev can be use directly, don't get netlink.byname again. netlink.LinkSetNsPid(macvlandev, dockerPid) runtime.LockOSThread() defer runtime.UnlockOSThread() //get root network namespace origns, _ := netns.Get() defer origns.Close() //enter the docker container network dockerNS, _ := netns.GetFromPid(dockerPid) defer dockerNS.Close() netns.Set(dockerNS) // use macvlandev can cause error,need type assertion. netlink.Macvlan not must be netlink.Link,fmz(realy? the vlan action add) // it is wrong, macvlandev1, _ := netlink.LinkByName(macvlandev.Attrs().Name) // when the eth is up, set name fail,: Device or resource busy netlink.LinkSetDown(macvlandev1) netlink.LinkSetName(macvlandev1, "eth1") addr, err := netlink.ParseAddr(CliIP) if err != nil { log.Fatalf("failed to parse the ip address %v", CliIP) } netlink.AddrAdd(macvlandev1, addr) netlink.LinkSetUp(macvlandev1) /* set the default route, have some problem. Dst == 0.0.0.0/0? no defaultgw := &netlink.Route{ Dst: nil, } netlink.RouteDel(defaultgw) ip, _ := net.ParseIP("8.8.8.8") routes, _ := netlink.RouteGet(ip) for _, r := range routes { netlink.RouteDel(&r) } */ //if use ip instruction, it also can config the container, --privileged have no effect. // The sublime test code(test this function) is strange, it only can avaiable in first time. And then fail(even need to reboot) // got it, //following code successfully delete the default route in docker container,but error in my host ,no such process routes, _ := netlink.RouteList(nil, netlink.FAMILY_V4) for _, r := range routes { if r.Dst == nil { if err := netlink.RouteDel(&r); err != nil { log.Warnf("delete the default error: ", err) } } } if CligwIP == "" { log.Fatal("container gw is null") } defaultRoute := &netlink.Route{ Dst: nil, Gw: net.ParseIP(CligwIP), LinkIndex: macvlandev1.Attrs().Index, } if err := netlink.RouteAdd(defaultRoute); err != nil { log.Warnf("create default route error: ", err) } netns.Set(origns) }
func listNetworks() error { // open the database dbpath := path.Join(stateDir, ipallocator.DBFile) db, err := bolt.Open(dbpath, 0666, nil) if err != nil { if os.IsNotExist(err) { return fmt.Errorf("You have not allocated any IPs") } return fmt.Errorf("Opening database at %s failed: %v", dbpath, err) } defer db.Close() var networks []network if err := db.View(func(tx *bolt.Tx) error { // Retrieve the jobs bucket. b := tx.Bucket(ipallocator.IPBucket) return b.ForEach(func(k, v []byte) error { n := network{ ip: net.ParseIP(string(k)), } // get the pid n.pid, err = strconv.Atoi(string(v)) if err != nil { return fmt.Errorf("parsing pid %s as int failed: %v", v, err) } // check the process _, err := os.FindProcess(n.pid) if err != nil { n.status = "does not exist" } else { n.status = "running" } // get the veth pair from the pid n.vethPair, err = vethPair(n.pid, bridgeName) if err != nil { return fmt.Errorf("Getting vethpair failed for pid %d: %v", n.pid, err) } // try to get the namespace handle n.fd, _ = netns.GetFromPid(n.pid) if n.fd <= 0 { n.status = "destroyed" } networks = append(networks, n) return nil }) }); err != nil { return fmt.Errorf("Getting networks from db failed: %v", err) } w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) fmt.Fprint(w, "IP\tLOCAL VETH\tPID\tSTATUS\tNS FD\n") for _, n := range networks { fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%d\n", n.ip.String(), n.vethPair.Attrs().Name, n.pid, n.status, n.fd) } w.Flush() return nil }
// configureInterface configures the network interface in the network namespace. func configureInterface(name string, pid int, addr *net.IPNet, gatewayIP string) error { // Lock the OS Thread so we don't accidentally switch namespaces runtime.LockOSThread() defer runtime.UnlockOSThread() // Save the current network namespace origns, err := netns.Get() if err != nil { return fmt.Errorf("Getting current network namespace failed: %v", err) } defer origns.Close() // Get the namespace newns, err := netns.GetFromPid(pid) if err != nil { return fmt.Errorf("Getting network namespace for pid %d failed: %v", pid, err) } defer newns.Close() // Enter the namespace if err := netns.Set(newns); err != nil { return fmt.Errorf("Entering network namespace failed: %v", err) } // Find the network interface identified by the name iface, err := netlink.LinkByName(name) if err != nil { return fmt.Errorf("Getting link by name %s failed: %v", name, err) } // Bring the interface down if err := netlink.LinkSetDown(iface); err != nil { return fmt.Errorf("Bringing interface [ %#v ] down failed: %v", iface, err) } // Change the interface name to eth0 in the namespace if err := netlink.LinkSetName(iface, containerInterface); err != nil { return fmt.Errorf("Renaming interface %s to %s failed: %v", name, defaultContainerInterface, err) } // Add the IP address ipAddr := &netlink.Addr{IPNet: addr, Label: ""} if err := netlink.AddrAdd(iface, ipAddr); err != nil { return fmt.Errorf("Setting interface %s ip to %s failed: %v", name, addr.String(), err) } // Bring the interface up if err := netlink.LinkSetUp(iface); err != nil { return fmt.Errorf("Bringing interface [ %#v ] up failed: %v", iface, err) } // Add the gateway route gw := net.ParseIP(gatewayIP) err = netlink.RouteAdd(&netlink.Route{ Scope: netlink.SCOPE_UNIVERSE, LinkIndex: iface.Attrs().Index, Gw: gw, }) if err != nil { return fmt.Errorf("Adding route %s to interface %s failed: %v", gw.String(), name, err) } // Switch back to the original namespace if err := netns.Set(origns); err != nil { return fmt.Errorf("Switching back to original namespace failed: %v", err) } return nil }
func AddDHCPNetwork() { if ok := utils.ValidateHostIface(flat.CliIF); !ok { log.Fatalf("the host-interface [ %s ] was not found.", flat.CliIF) } hostmacvlanname, _ := utils.GenerateRandomName(hostprefix, hostlen) hostEth, _ := netlink.LinkByName(flat.CliIF) //create the macvlan device macvlandev := &netlink.Macvlan{ LinkAttrs: netlink.LinkAttrs{ Name: hostmacvlanname, ParentIndex: hostEth.Attrs().Index, }, Mode: netlink.MACVLAN_MODE_BRIDGE, } if err := netlink.LinkAdd(macvlandev); err != nil { log.Fatalf("failed to create Macvlan: [ %v ] with the error: %s", macvlandev.Attrs().Name, err) } dockerPid := utils.DockerPid(flat.CliCName) netlink.LinkSetNsPid(macvlandev, dockerPid) runtime.LockOSThread() defer runtime.UnlockOSThread() //get root network namespace origns, _ := netns.Get() defer origns.Close() //enter the docker container network dockerNS, _ := netns.GetFromPid(dockerPid) defer dockerNS.Close() //get the dochclient name dhcpClientPath := "/home/fmzhen/go/src/github.com/fmzhen/docker-macvlan/macvlan/dhcp/dhcpclient.sh" out, err := exec.Command(dhcpClientPath).Output() if err != nil { log.Fatal("exec the dhcpclient.sh error: ", err) } dhcpClient := string(out) // like ip netns exec xxx, just the netns ,so the command in host can exec netns.Set(dockerNS) // use macvlandev can cause error,need type assertion. netlink.Macvlan not must be netlink.Link,fmz macvlandev1, _ := netlink.LinkByName(macvlandev.Attrs().Name) netlink.LinkSetDown(macvlandev1) netlink.LinkSetName(macvlandev1, "eth1") netlink.LinkSetUp(macvlandev1) //delete the default route routes, _ := netlink.RouteList(nil, netlink.FAMILY_V4) for _, r := range routes { if r.Dst == nil { if err := netlink.RouteDel(&r); err != nil { log.Warnf("delete the default error: ", err) } } } //it doesn't work, the problem at the atgs don't pass to the shell script dhcpReqpath := "/home/fmzhen/go/src/github.com/fmzhen/docker-macvlan/macvlan/dhcp/dhcpReq.sh" exec.Command(dhcpReqpath, dhcpClient, string(dockerPid), flat.CliCName).Run() netns.Set(origns) }
func (c Container) netMetrics() []Metric { // Lock the OS Thread so we don't accidentally switch namespaces runtime.LockOSThread() defer runtime.UnlockOSThread() // Save the current network namespace origns, _ := netns.Get() defer origns.Close() // Create a new network namespace pid, _ := c.firstPid() newns, _ := netns.GetFromPid(pid) defer newns.Close() // Switch to the container namespace netns.Set(newns) data, _ := exec.Command("ip", "-s", "-o", "link").Output() // Switch back to the original namespace netns.Set(origns) runtime.UnlockOSThread() int_re, _ := regexp.Compile(`^\d+: ([^:]+): .*$`) prefix := "network" var metrics []Metric var interface_name string var name string var rx []string var tx []string for _, link_info := range strings.Split(string(data), "\n") { split_link_info := strings.Split(link_info, "\\") if len(split_link_info) >= 6 { interface_name = int_re.FindStringSubmatch(split_link_info[0])[1] name = prefix + "." + interface_name rx = strings.Fields(split_link_info[3]) tx = strings.Fields(split_link_info[5]) if len(rx) == 6 { metrics = append(metrics, Metric{name + ".rx.bytes", rx[0]}) metrics = append(metrics, Metric{name + ".rx.packets", rx[1]}) metrics = append(metrics, Metric{name + ".rx.errors", rx[2]}) metrics = append(metrics, Metric{name + ".rx.dropped", rx[3]}) metrics = append(metrics, Metric{name + ".rx.overrun", rx[4]}) metrics = append(metrics, Metric{name + ".rx.mcast", rx[5]}) } if len(tx) == 6 { metrics = append(metrics, Metric{name + ".tx.bytes", tx[0]}) metrics = append(metrics, Metric{name + ".tx.packets", tx[1]}) metrics = append(metrics, Metric{name + ".tx.errors", tx[2]}) metrics = append(metrics, Metric{name + ".tx.dropped", tx[3]}) metrics = append(metrics, Metric{name + ".tx.overrun", tx[4]}) metrics = append(metrics, Metric{name + ".tx.mcast", tx[5]}) } } } return metrics }