mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-06 20:10:11 +02:00
fix(mod): probe searches for features built-in the kernel
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
package mod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errBuiltinModuleNotFound = errors.New("builtin module not found")
|
||||||
|
|
||||||
|
func checkModulesBuiltin(modulesPath, moduleName string) error {
|
||||||
|
f, err := os.Open(filepath.Join(modulesPath, "modules.builtin"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
moduleName = strings.TrimSuffix(moduleName, ".ko")
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
line = strings.TrimSuffix(line, ".ko")
|
||||||
|
if strings.HasSuffix(line, "/"+moduleName) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: %s", errBuiltinModuleNotFound, moduleName)
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package mod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errModuleNameUnknown = errors.New("unknown module name")
|
||||||
|
errKernelFeatureIsModule = errors.New("kernel feature is a module, not built-in")
|
||||||
|
errKernelFeatureNotSet = errors.New("kernel feature not set")
|
||||||
|
errKernelFeatureNotFound = errors.New("kernel feature not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkProcConfig checks /proc/config.gz for a the kernel feature corresponding
|
||||||
|
// to the given module name. If the kernel feature is found and set to "y", it returns nil.
|
||||||
|
// If the kernel feature is found and set to "m", it returns an error indicating that the kernel
|
||||||
|
// feature is a module, not built-in.
|
||||||
|
// If the kernel feature is found and not set, it returns an error indicating that the kernel
|
||||||
|
// feature is not set. If the kernel feature is not found, it returns an error indicating that the kernel
|
||||||
|
// feature is not found.
|
||||||
|
func checkProcConfig(moduleName string) error {
|
||||||
|
f, err := os.Open("/proc/config.gz")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
gz, err := gzip.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating gzip reader: %w", err)
|
||||||
|
}
|
||||||
|
defer gz.Close()
|
||||||
|
|
||||||
|
// If any group of kernel features is satisfied, then the module is considered supported.
|
||||||
|
kernelFeatureGroups, ok := moduleNameToKernelFeatureGroups(moduleName)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", errModuleNameUnknown, moduleName)
|
||||||
|
}
|
||||||
|
groups := make([]map[string]bool, len(kernelFeatureGroups))
|
||||||
|
for i, group := range kernelFeatureGroups {
|
||||||
|
featureToOK := make(map[string]bool)
|
||||||
|
for _, feature := range group {
|
||||||
|
featureToOK[feature] = false
|
||||||
|
}
|
||||||
|
groups[i] = featureToOK
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(gz)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
for _, featureToOK := range groups {
|
||||||
|
for name, ok := range featureToOK {
|
||||||
|
switch {
|
||||||
|
case ok:
|
||||||
|
case strings.HasPrefix(line, name+"=m"):
|
||||||
|
return fmt.Errorf("%w: %s", errKernelFeatureIsModule, name)
|
||||||
|
case strings.HasPrefix(line, name+"=y"):
|
||||||
|
featureToOK[name] = true
|
||||||
|
if allFeaturesOK(featureToOK) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(line, "# "+name+" is not set"):
|
||||||
|
return fmt.Errorf("%w: %s", errKernelFeatureNotSet, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: for module name %s", errKernelFeatureNotFound, moduleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moduleNameToKernelFeatureGroups(moduleName string) (featureGroups [][]string, ok bool) {
|
||||||
|
moduleMap := map[string][][]string{
|
||||||
|
"nf_tables": {{"CONFIG_NF_TABLES"}},
|
||||||
|
|
||||||
|
// Netfilter Matches
|
||||||
|
"xt_conntrack": {{"CONFIG_NETFILTER_XT_MATCH_CONNTRACK"}},
|
||||||
|
"xt_connmark": {
|
||||||
|
{"CONFIG_NETFILTER_XT_CONNMARK"},
|
||||||
|
{"CONFIG_NETFILTER_XT_MATCH_CONNMARK", "CONFIG_NETFILTER_XT_TARGET_CONNMARK"},
|
||||||
|
},
|
||||||
|
"xt_mark": {
|
||||||
|
{"CONFIG_NETFILTER_XT_MARK"},
|
||||||
|
{"CONFIG_NETFILTER_XT_MATCH_MARK", "CONFIG_NETFILTER_XT_TARGET_MARK"},
|
||||||
|
},
|
||||||
|
"nf_conntrack_netlink": {{"CONFIG_NF_CT_NETLINK"}},
|
||||||
|
"nf_reject_ipv4": {{"CONFIG_NF_REJECT_IPV4"}},
|
||||||
|
|
||||||
|
// Common Netfilter Targets
|
||||||
|
"xt_log": {{"CONFIG_NETFILTER_XT_TARGET_LOG"}},
|
||||||
|
"xt_reject": {
|
||||||
|
{"CONFIG_IP_NF_TARGET_REJECT", "CONFIG_NF_REJECT_IPV4"},
|
||||||
|
{"CONFIG_NETFILTER_XT_TARGET_REJECT", "CONFIG_NF_REJECT_IPV4"},
|
||||||
|
},
|
||||||
|
"xt_masquerade": {{"CONFIG_NETFILTER_XT_TARGET_MASQUERADE"}},
|
||||||
|
|
||||||
|
// Additional Netfilter Matches
|
||||||
|
"xt_addrtype": {{"CONFIG_NETFILTER_XT_MATCH_ADDRTYPE"}},
|
||||||
|
"xt_comment": {{"CONFIG_NETFILTER_XT_MATCH_COMMENT"}},
|
||||||
|
"xt_multiport": {{"CONFIG_NETFILTER_XT_MATCH_MULTIPORT"}},
|
||||||
|
"xt_state": {{"CONFIG_NETFILTER_XT_MATCH_STATE"}},
|
||||||
|
"xt_tcpudp": {{"CONFIG_NETFILTER_XT_MATCH_TCPUDP"}},
|
||||||
|
|
||||||
|
// Tunneling and Virtualization
|
||||||
|
"tun": {{"CONFIG_TUN"}},
|
||||||
|
"bridge": {{"CONFIG_BRIDGE"}},
|
||||||
|
"veth": {{"CONFIG_VETH"}},
|
||||||
|
"vxlan": {{"CONFIG_VXLAN"}},
|
||||||
|
"wireguard": {{"CONFIG_WIREGUARD"}},
|
||||||
|
|
||||||
|
// Filesystems
|
||||||
|
"overlay": {{"CONFIG_OVERLAY_FS"}},
|
||||||
|
"fuse": {{"CONFIG_FUSE_FS"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
featureGroups, ok = moduleMap[strings.ToLower(moduleName)]
|
||||||
|
return featureGroups, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func allFeaturesOK(featureToOK map[string]bool) bool {
|
||||||
|
for _, ok := range featureToOK {
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
+34
-30
@@ -30,36 +30,7 @@ type moduleInfo struct {
|
|||||||
|
|
||||||
var ErrModulesDirectoryNotFound = errors.New("modules directory not found")
|
var ErrModulesDirectoryNotFound = errors.New("modules directory not found")
|
||||||
|
|
||||||
func getModulesInfo() (modulesInfo map[string]moduleInfo, err error) {
|
func getModulesInfo(modulesPath string) (modulesInfo map[string]moduleInfo, err error) {
|
||||||
var utsName unix.Utsname
|
|
||||||
err = unix.Uname(&utsName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting unix uname release: %w", err)
|
|
||||||
}
|
|
||||||
release := unix.ByteSliceToString(utsName.Release[:])
|
|
||||||
release = strings.TrimSpace(release)
|
|
||||||
|
|
||||||
modulePaths := []string{
|
|
||||||
filepath.Join("/lib/modules", release),
|
|
||||||
filepath.Join("/usr/lib/modules", release),
|
|
||||||
}
|
|
||||||
|
|
||||||
var modulesPath string
|
|
||||||
var found bool
|
|
||||||
for _, modulesPath = range modulePaths {
|
|
||||||
info, err := os.Stat(modulesPath)
|
|
||||||
if err == nil && info.IsDir() {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return nil, fmt.Errorf("%w: %s are not valid existing directories"+
|
|
||||||
"; have you bind mounted the /lib/modules directory?",
|
|
||||||
ErrModulesDirectoryNotFound, strings.Join(modulePaths, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencyFilepath := filepath.Join(modulesPath, "modules.dep")
|
dependencyFilepath := filepath.Join(modulesPath, "modules.dep")
|
||||||
dependencyFile, err := os.Open(dependencyFilepath)
|
dependencyFile, err := os.Open(dependencyFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,6 +82,39 @@ func getModulesInfo() (modulesInfo map[string]moduleInfo, err error) {
|
|||||||
return modulesInfo, nil
|
return modulesInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getModulesPath() (string, error) {
|
||||||
|
release, err := getReleaseName()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getting release name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modulePaths := []string{
|
||||||
|
filepath.Join("/lib/modules", release),
|
||||||
|
filepath.Join("/usr/lib/modules", release),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, modulesPath := range modulePaths {
|
||||||
|
info, err := os.Stat(modulesPath)
|
||||||
|
if err == nil && info.IsDir() {
|
||||||
|
return modulesPath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("%w: %s are not valid existing directories"+
|
||||||
|
"; have you bind mounted the /lib/modules directory?",
|
||||||
|
ErrModulesDirectoryNotFound, strings.Join(modulePaths, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReleaseName() (release string, err error) {
|
||||||
|
var utsName unix.Utsname
|
||||||
|
err = unix.Uname(&utsName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getting unix uname release: %w", err)
|
||||||
|
}
|
||||||
|
release = unix.ByteSliceToString(utsName.Release[:])
|
||||||
|
release = strings.TrimSpace(release)
|
||||||
|
return release, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getBuiltinModules(modulesDirPath string, modulesInfo map[string]moduleInfo) error {
|
func getBuiltinModules(modulesDirPath string, modulesInfo map[string]moduleInfo) error {
|
||||||
file, err := os.Open(filepath.Join(modulesDirPath, "modules.builtin"))
|
file, err := os.Open(filepath.Join(modulesDirPath, "modules.builtin"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,12 +1,49 @@
|
|||||||
package mod
|
package mod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Probe loads the given kernel module and its dependencies.
|
// Probe is a expanded version of modprobe, in which it checks if the Kernel
|
||||||
|
// built-in features contain the given module name.
|
||||||
|
// It first tries to locate the modules directory in [getModulesPath].
|
||||||
|
// If it fails (like on WSL), it then only checks for the kernel feature
|
||||||
|
// in /proc/config.gz with [checkProcConfig].
|
||||||
|
// Otherwise, it first checks if the modules directory modules.builtin
|
||||||
|
// file contains the given module name in [checkModulesBuiltin].
|
||||||
|
// If the module is not found, it then runs the classic [modProbe] behavior,
|
||||||
|
// trying to load the module in the kernel.
|
||||||
|
// If this fails, it does one final try running [checkProcConfig].
|
||||||
func Probe(moduleName string) error {
|
func Probe(moduleName string) error {
|
||||||
modulesInfo, err := getModulesInfo()
|
modulesPath, err := getModulesPath()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrModulesDirectoryNotFound) {
|
||||||
|
err = checkProcConfig(moduleName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("checking /proc/config.gz: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("getting modules path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkModulesBuiltin(modulesPath, moduleName)
|
||||||
|
if err != nil {
|
||||||
|
err = modProbe(modulesPath, moduleName)
|
||||||
|
if err != nil {
|
||||||
|
err = checkProcConfig(moduleName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("checking /proc/config.gz: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// modProbe is the classic modprobe behavior.
|
||||||
|
func modProbe(modulesPath, moduleName string) error {
|
||||||
|
modulesInfo, err := getModulesInfo(modulesPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting modules information: %w", err)
|
return fmt.Errorf("getting modules information: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user