Files
ctrld/cmd/cli/net_linux.go
T

163 lines
5.1 KiB
Go

package cli
import (
"net"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
)
// The kernel gives an Ethernet-like link the type ARPHRD_ETHER.
const arphrdEther = 1
// Tests point the readers at a temporary directory.
var (
sysfsNetRoot = "/sys/class/net"
sysfsVirtualNetRoot = "/sys/devices/virtual/net"
)
// noiseInterfacePrefixes name the interfaces of a container host. They change
// every few seconds, and no traffic of the host goes through them.
var noiseInterfacePrefixes = []string{"docker", "veth", "virbr", "br-"}
// usbTetherDrivers name the kernel modules of a phone that shares its data
// connection over the cable.
var usbTetherDrivers = []string{"ipheth", "rndis_host"}
// sysfsInfo holds what sysfs knows about one interface.
type sysfsInfo struct {
Type int
DevType string
Driver string
Wireless bool
}
// patchNetIfaceName patches network interface names on Linux
// This is a no-op on Linux as interface names don't need special handling
func patchNetIfaceName(iface *net.Interface) (bool, error) { return true, nil }
// validInterface reports whether the *net.Interface is a valid one.
// Only non-virtual interfaces are considered valid.
// This prevents DNS configuration on virtual interfaces like docker, veth, etc.
func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bool {
_, ok := validIfacesMap[iface.Name]
return ok
}
// sysfsVirtualInterfaces returns the virtual interfaces of the host. The
// nameserver code has its own reader of the fixed path; this one reads the
// root that a test can point elsewhere.
func sysfsVirtualInterfaces() map[string]struct{} {
s := make(map[string]struct{})
entries, _ := os.ReadDir(sysfsVirtualNetRoot)
for _, entry := range entries {
if entry.IsDir() {
s[strings.TrimSpace(entry.Name())] = struct{}{}
}
}
return s
}
// sysfsInterfaceInfo reads the sysfs entry of an interface. A missing file
// leaves its field empty.
func sysfsInterfaceInfo(name string) sysfsInfo {
dir := filepath.Join(sysfsNetRoot, name)
info := sysfsInfo{DevType: sysfsUeventValue(dir, "DEVTYPE"), Driver: sysfsDriver(dir)}
info.Type, _ = strconv.Atoi(readSysfsAttribute(dir, "type"))
if entry, err := os.Stat(filepath.Join(dir, "wireless")); err == nil && entry.IsDir() {
info.Wireless = true
}
return info
}
// readSysfsAttribute reads one attribute without its trailing newline.
func readSysfsAttribute(dir, file string) string {
b, err := os.ReadFile(filepath.Join(dir, file))
if err != nil {
return ""
}
return strings.TrimSpace(string(b))
}
// sysfsUeventValue reads one entry of the uevent file.
func sysfsUeventValue(dir, key string) string {
for _, line := range strings.Split(readSysfsAttribute(dir, "uevent"), "\n") {
after, ok := strings.CutPrefix(strings.TrimSpace(line), key+"=")
if !ok {
continue
}
return strings.TrimSpace(after)
}
return ""
}
// sysfsDriver names the kernel module behind an interface. The driver link of
// the device points at the module, and the uevent of the device names it too.
func sysfsDriver(dir string) string {
device := filepath.Join(dir, "device")
if target, err := os.Readlink(filepath.Join(device, "driver")); err == nil {
return filepath.Base(target)
}
if driver := sysfsUeventValue(device, "DRIVER"); driver != "" {
return driver
}
return sysfsUeventValue(dir, "DRIVER")
}
// sysfsHardwarePort names the link in the words that the link type mapping
// reads. The port names of macOS use the same words.
func sysfsHardwarePort(info sysfsInfo) string {
switch {
case info.Wireless:
return "Wi-Fi"
case slices.Contains(usbTetherDrivers, info.Driver):
return hardwarePortIPhoneUSB
case info.DevType != "":
return info.DevType
case info.Type == arphrdEther:
return "Ethernet"
}
return ""
}
// sysfsClass names the kind of an interface. The name rule comes before the
// device type, so an interface that went away keeps its class. A bridge, a
// bond, and a VLAN carry the traffic of the host, so they stay hardware.
func sysfsClass(name string, info sysfsInfo) string {
switch {
case info.DevType == "tun" || info.DevType == "tap":
return "tunnel"
case hasInterfacePrefix(name, noiseInterfacePrefixes...):
return "virtual"
case info.Wireless || info.Type == arphrdEther:
return "hardware"
}
return ""
}
// platformInterfaceMeta returns the Linux class and link name of an interface.
// Linux has no service name.
func platformInterfaceMeta(name string) (class, hardwarePort, service string) {
info := sysfsInterfaceInfo(name)
return sysfsClass(name, info), sysfsHardwarePort(info), ""
}
// platformVirtualInterfaces returns the container interfaces of the host. The
// kernel lists the uplink bridges and the bonds beside them, and those carry
// the traffic of the host.
func platformVirtualInterfaces() map[string]struct{} {
noise := make(map[string]struct{})
for name := range sysfsVirtualInterfaces() {
if hasInterfacePrefix(name, noiseInterfacePrefixes...) {
noise[name] = struct{}{}
}
}
return noise
}
// refreshInterfaceMeta does no work. Each sysfs read answers from the kernel
// of this moment, so there is no cache to refresh.
func refreshInterfaceMeta() {}