mirror of
https://github.com/netbirdio/netbird.git
synced 2025-01-05 21:49:03 +01:00
bb40325977
This PR showcases the implementation of additional linter rules. I've updated the golangci-lint GitHub Actions to the latest available version. This update makes sure that the tool works the same way locally - assuming being updated regularly - and with the GitHub Actions. I've also taken care of keeping all the GitHub Actions up to date, which helps our code stay current. But there's one part, goreleaser that's a bit tricky to test on our computers. So, it's important to take a close look at that. To make it easier to understand what I've done, I've made separate changes for each thing that the new linters found. This should help the people reviewing the changes see what's going on more clearly. Some of the changes might not be obvious at first glance. Things to consider for the future CI runs on Ubuntu so the static analysis only happens for Linux. Consider running it for the rest: Darwin, Windows
361 lines
7.7 KiB
Go
361 lines
7.7 KiB
Go
//go:build linux && !android
|
|
|
|
// Package iface provides wireguard network interface creation and management
|
|
package iface
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/vishvananda/netlink"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Holds logic to check existence of kernel modules used by wireguard interfaces
|
|
// Copied from https://github.com/paultag/go-modprobe and
|
|
// https://github.com/pmorjan/kmod
|
|
|
|
type status int
|
|
|
|
const (
|
|
defaultModuleDir = "/lib/modules"
|
|
unknown status = iota
|
|
unloaded
|
|
unloading
|
|
loading
|
|
live
|
|
inuse
|
|
envDisableWireGuardKernel = "NB_WG_KERNEL_DISABLED"
|
|
)
|
|
|
|
type module struct {
|
|
name string
|
|
path string
|
|
}
|
|
|
|
var (
|
|
// ErrModuleNotFound is the error resulting if a module can't be found.
|
|
ErrModuleNotFound = errors.New("module not found")
|
|
moduleLibDir = defaultModuleDir
|
|
// get the root directory for the kernel modules. If this line panics,
|
|
// it's because getModuleRoot has failed to get the uname of the running
|
|
// kernel (likely a non-POSIX system, but maybe a broken kernel?)
|
|
moduleRoot = getModuleRoot()
|
|
)
|
|
|
|
// Get the module root (/lib/modules/$(uname -r)/)
|
|
func getModuleRoot() string {
|
|
uname := unix.Utsname{}
|
|
if err := unix.Uname(&uname); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
i := 0
|
|
for ; uname.Release[i] != 0; i++ {
|
|
}
|
|
|
|
return filepath.Join(moduleLibDir, string(uname.Release[:i]))
|
|
}
|
|
|
|
// tunModuleIsLoaded check if tun module exist, if is not attempt to load it
|
|
func tunModuleIsLoaded() bool {
|
|
_, err := os.Stat("/dev/net/tun")
|
|
if err == nil {
|
|
return true
|
|
}
|
|
|
|
log.Infof("couldn't access device /dev/net/tun, go error %v, "+
|
|
"will attempt to load tun module, if running on container add flag --cap-add=NET_ADMIN", err)
|
|
|
|
tunLoaded, err := tryToLoadModule("tun")
|
|
if err != nil {
|
|
log.Errorf("unable to find or load tun module, got error: %v", err)
|
|
}
|
|
return tunLoaded
|
|
}
|
|
|
|
// WireGuardModuleIsLoaded check if we can load WireGuard mod (linux only)
|
|
func WireGuardModuleIsLoaded() bool {
|
|
|
|
if os.Getenv(envDisableWireGuardKernel) == "true" {
|
|
log.Debugf("WireGuard kernel module disabled because the %s env is set to true", envDisableWireGuardKernel)
|
|
return false
|
|
}
|
|
|
|
if canCreateFakeWireGuardInterface() {
|
|
return true
|
|
}
|
|
|
|
loaded, err := tryToLoadModule("wireguard")
|
|
if err != nil {
|
|
log.Info(err)
|
|
return false
|
|
}
|
|
|
|
return loaded
|
|
}
|
|
|
|
func canCreateFakeWireGuardInterface() bool {
|
|
link := newWGLink("mustnotexist")
|
|
|
|
// We willingly try to create a device with an invalid
|
|
// MTU here as the validation of the MTU will be performed after
|
|
// the validation of the link kind and hence allows us to check
|
|
// for the existance of the wireguard module without actually
|
|
// creating a link.
|
|
//
|
|
// As a side-effect, this will also let the kernel lazy-load
|
|
// the wireguard module.
|
|
link.attrs.MTU = math.MaxInt
|
|
|
|
err := netlink.LinkAdd(link)
|
|
|
|
return errors.Is(err, syscall.EINVAL)
|
|
}
|
|
|
|
func tryToLoadModule(moduleName string) (bool, error) {
|
|
if isModuleEnabled(moduleName) {
|
|
return true, nil
|
|
}
|
|
modulePath, err := getModulePath(moduleName)
|
|
if err != nil {
|
|
return false, fmt.Errorf("couldn't find module path for %s, error: %v", moduleName, err)
|
|
}
|
|
if modulePath == "" {
|
|
return false, nil
|
|
}
|
|
|
|
log.Infof("trying to load %s module", moduleName)
|
|
|
|
err = loadModuleWithDependencies(moduleName, modulePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("couldn't load %s module, error: %v", moduleName, err)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func isModuleEnabled(name string) bool {
|
|
builtin, builtinErr := isBuiltinModule(name)
|
|
state, statusErr := moduleStatus(name)
|
|
return (builtinErr == nil && builtin) || (statusErr == nil && state >= loading)
|
|
}
|
|
|
|
func getModulePath(name string) (string, error) {
|
|
var foundPath string
|
|
skipRemainingDirs := false
|
|
|
|
err := filepath.WalkDir(
|
|
moduleRoot,
|
|
func(path string, info fs.DirEntry, err error) error {
|
|
if skipRemainingDirs {
|
|
return fs.SkipDir
|
|
}
|
|
if err != nil {
|
|
// skip broken files
|
|
return nil //nolint:nilerr
|
|
}
|
|
|
|
if !info.Type().IsRegular() {
|
|
return nil
|
|
}
|
|
|
|
nameFromPath := pathToName(path)
|
|
if nameFromPath == name {
|
|
foundPath = path
|
|
skipRemainingDirs = true
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return foundPath, nil
|
|
}
|
|
|
|
func pathToName(s string) string {
|
|
s = filepath.Base(s)
|
|
for ext := filepath.Ext(s); ext != ""; ext = filepath.Ext(s) {
|
|
s = strings.TrimSuffix(s, ext)
|
|
}
|
|
return cleanName(s)
|
|
}
|
|
|
|
func cleanName(s string) string {
|
|
return strings.ReplaceAll(strings.TrimSpace(s), "-", "_")
|
|
}
|
|
|
|
func isBuiltinModule(name string) (bool, error) {
|
|
f, err := os.Open(filepath.Join(moduleRoot, "/modules.builtin"))
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer func() {
|
|
err := f.Close()
|
|
if err != nil {
|
|
log.Errorf("failed closing modules.builtin file, %v", err)
|
|
}
|
|
}()
|
|
|
|
var found bool
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if pathToName(line) == name {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return false, err
|
|
}
|
|
return found, nil
|
|
}
|
|
|
|
// /proc/modules
|
|
//
|
|
// name | memory size | reference count | references | state: <Live|Loading|Unloading>
|
|
// macvlan 28672 1 macvtap, Live 0x0000000000000000
|
|
func moduleStatus(name string) (status, error) {
|
|
state := unknown
|
|
f, err := os.Open("/proc/modules")
|
|
if err != nil {
|
|
return state, err
|
|
}
|
|
defer func() {
|
|
err := f.Close()
|
|
if err != nil {
|
|
log.Errorf("failed closing /proc/modules file, %v", err)
|
|
}
|
|
}()
|
|
|
|
state = unloaded
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
fields := strings.Fields(scanner.Text())
|
|
if fields[0] == name {
|
|
if fields[2] != "0" {
|
|
state = inuse
|
|
break
|
|
}
|
|
switch fields[4] {
|
|
case "Live":
|
|
state = live
|
|
case "Loading":
|
|
state = loading
|
|
case "Unloading":
|
|
state = unloading
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return state, err
|
|
}
|
|
|
|
return state, nil
|
|
}
|
|
|
|
func loadModuleWithDependencies(name, path string) error {
|
|
deps, err := getModuleDependencies(name)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't load list of module %s dependecies", name)
|
|
}
|
|
for _, dep := range deps {
|
|
err = loadModule(dep.name, dep.path)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't load dependecy module %s for %s", dep.name, name)
|
|
}
|
|
}
|
|
return loadModule(name, path)
|
|
}
|
|
|
|
func loadModule(name, path string) error {
|
|
state, err := moduleStatus(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if state >= loading {
|
|
return nil
|
|
}
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
err := f.Close()
|
|
if err != nil {
|
|
log.Errorf("failed closing %s file, %v", path, err)
|
|
}
|
|
}()
|
|
|
|
// first try finit_module(2), then init_module(2)
|
|
err = unix.FinitModule(int(f.Fd()), "", 0)
|
|
if errors.Is(err, unix.ENOSYS) {
|
|
buf, err := io.ReadAll(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return unix.InitModule(buf, "")
|
|
}
|
|
return err
|
|
}
|
|
|
|
// getModuleDependencies returns a module dependencies
|
|
func getModuleDependencies(name string) ([]module, error) {
|
|
f, err := os.Open(filepath.Join(moduleRoot, "/modules.dep"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
err := f.Close()
|
|
if err != nil {
|
|
log.Errorf("failed closing modules.dep file, %v", err)
|
|
}
|
|
}()
|
|
|
|
var deps []string
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
fields := strings.Fields(line)
|
|
if pathToName(strings.TrimSuffix(fields[0], ":")) == name {
|
|
deps = fields
|
|
break
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(deps) == 0 {
|
|
return nil, ErrModuleNotFound
|
|
}
|
|
deps[0] = strings.TrimSuffix(deps[0], ":")
|
|
|
|
var modules []module
|
|
for _, v := range deps {
|
|
if pathToName(v) != name {
|
|
modules = append(modules, module{
|
|
name: pathToName(v),
|
|
path: filepath.Join(moduleRoot, v),
|
|
})
|
|
}
|
|
}
|
|
|
|
return modules, nil
|
|
}
|