134 lines
3.6 KiB
Go
134 lines
3.6 KiB
Go
package ethtool
|
|
|
|
import (
|
|
"net"
|
|
"runtime"
|
|
|
|
ethtoolLib "github.com/safchain/ethtool"
|
|
"github.com/vishvananda/netns"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
)
|
|
|
|
type NamespacedAction struct {
|
|
result chan<- NamespacedResult
|
|
f func(*NamespaceGoroutine) (interface{}, error)
|
|
}
|
|
|
|
type NamespacedResult struct {
|
|
Result interface{}
|
|
Error error
|
|
}
|
|
|
|
type NamespaceGoroutine struct {
|
|
name string
|
|
handle netns.NsHandle
|
|
ethtoolClient *ethtoolLib.Ethtool
|
|
c chan NamespacedAction
|
|
Log telegraf.Logger
|
|
}
|
|
|
|
func (n *NamespaceGoroutine) Name() string {
|
|
return n.name
|
|
}
|
|
|
|
func (n *NamespaceGoroutine) Interfaces() ([]NamespacedInterface, error) {
|
|
interfaces, err := n.Do(func(n *NamespaceGoroutine) (interface{}, error) {
|
|
interfaces, err := net.Interfaces()
|
|
if err != nil {
|
|
n.Log.Errorf(`Could not get interfaces in namespace "%s": %s`, n.name, err)
|
|
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
|
|
}
|
|
|
|
// 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 "%s": %s`, n.name, err)
|
|
started <- err
|
|
return
|
|
}
|
|
}
|
|
|
|
// Every namespace needs its own connection to ethtool
|
|
e, err := ethtoolLib.NewEthtool()
|
|
if err != nil {
|
|
n.Log.Errorf(`Could not create ethtool client for namespace "%s": %s`, n.name, err)
|
|
started <- err
|
|
return
|
|
}
|
|
n.ethtoolClient = e
|
|
started <- nil
|
|
for command := range n.c {
|
|
result, err := command.f(n)
|
|
command.result <- NamespacedResult{
|
|
Result: result,
|
|
Error: 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.Error
|
|
}
|