1
0
Fork 0
telegraf/plugins/inputs/ethtool/namespace_linux.go

182 lines
4.6 KiB
Go
Raw Normal View History

//go:build linux
package ethtool
import (
"math"
"net"
"runtime"
"github.com/safchain/ethtool"
"github.com/vishvananda/netns"
"github.com/influxdata/telegraf"
)
type namespace interface {
name() string
interfaces() ([]namespacedInterface, error)
driverName(intf namespacedInterface) (string, error)
stats(intf namespacedInterface) (map[string]uint64, error)
get(intf namespacedInterface) (map[string]uint64, error)
}
type namespacedInterface struct {
net.Interface
namespace namespace
}
type namespacedAction struct {
result chan<- namespacedResult
f func(*namespaceGoroutine) (interface{}, error)
}
type namespacedResult struct {
result interface{}
err error
}
type namespaceGoroutine struct {
namespaceName string
handle netns.NsHandle
ethtoolClient *ethtool.Ethtool
c chan namespacedAction
log telegraf.Logger
}
func (n *namespaceGoroutine) name() string {
return n.namespaceName
}
func (n *namespaceGoroutine) interfaces() ([]namespacedInterface, error) {
interfaces, err := n.do(func(n *namespaceGoroutine) (interface{}, error) {
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
namespacedInterfaces := make([]namespacedInterface, 0, len(interfaces))
for _, iface := range interfaces {
namespacedInterfaces = append(
namespacedInterfaces,
namespacedInterface{
Interface: iface,
namespace: n,
},
)
}
return namespacedInterfaces, nil
})
return interfaces.([]namespacedInterface), err
}
func (n *namespaceGoroutine) driverName(intf namespacedInterface) (string, error) {
driver, err := n.do(func(n *namespaceGoroutine) (interface{}, error) {
return n.ethtoolClient.DriverName(intf.Name)
})
return driver.(string), err
}
func (n *namespaceGoroutine) stats(intf namespacedInterface) (map[string]uint64, error) {
driver, err := n.do(func(n *namespaceGoroutine) (interface{}, error) {
return n.ethtoolClient.Stats(intf.Name)
})
return driver.(map[string]uint64), err
}
func (n *namespaceGoroutine) get(intf namespacedInterface) (map[string]uint64, error) {
result, err := n.do(func(n *namespaceGoroutine) (interface{}, error) {
ecmd := ethtool.EthtoolCmd{}
speed32, err := n.ethtoolClient.CmdGet(&ecmd, intf.Name)
if err != nil {
return nil, err
}
var speed = uint64(speed32)
if speed == math.MaxUint32 {
speed = math.MaxUint64
}
var link32 uint32
link32, err = n.ethtoolClient.LinkState(intf.Name)
if err != nil {
return nil, err
}
return map[string]uint64{
"speed": speed,
"duplex": uint64(ecmd.Duplex),
"autoneg": uint64(ecmd.Autoneg),
"link": uint64(link32),
}, nil
})
if result != nil {
return result.(map[string]uint64), err
}
return nil, err
}
// start locks a goroutine to an OS thread and ties it to the namespace, then
// loops for actions to run in the namespace.
func (n *namespaceGoroutine) start() error {
n.c = make(chan namespacedAction)
started := make(chan error)
go func() {
// We're going to hold this thread locked permanently. We're going to
// do this for every namespace. This makes it very likely that the Go
// runtime will spin up new threads to replace it. To avoid thread
// leaks, we don't unlock when we're done and instead let this thread
// die.
runtime.LockOSThread()
// If this goroutine is for the initial namespace, we are already in
// the correct namespace. Switching would require CAP_SYS_ADMIN, which
// we may not have. Don't switch if the desired namespace matches the
// current one.
initialNamespace, err := netns.Get()
if err != nil {
n.log.Errorf("Could not get initial namespace: %s", err)
started <- err
return
}
if !initialNamespace.Equal(n.handle) {
if err := netns.Set(n.handle); err != nil {
n.log.Errorf("Could not switch to namespace %q: %s", n.namespaceName, err.Error())
started <- err
return
}
}
// Every namespace needs its own connection to ethtool
e, err := ethtool.NewEthtool()
if err != nil {
n.log.Errorf("Could not create ethtool client for namespace %q: %s", n.namespaceName, err.Error())
started <- err
return
}
n.ethtoolClient = e
started <- nil
for command := range n.c {
result, err := command.f(n)
command.result <- namespacedResult{
result: result,
err: err,
}
close(command.result)
}
}()
return <-started
}
// do runs a function inside the OS thread tied to the namespace.
func (n *namespaceGoroutine) do(f func(*namespaceGoroutine) (interface{}, error)) (interface{}, error) {
result := make(chan namespacedResult)
n.c <- namespacedAction{
result: result,
f: f,
}
r := <-result
return r.result, r.err
}