4caac1a92f
* Add `bridge_network_hairpin_mode` client config setting * Add node attribute: `nomad.bridge.hairpin_mode` * Changed format string to use `%q` to escape user provided data * Add test to validate template JSON for developer safety Co-authored-by: Daniel Bennett <dbennett@hashicorp.com>
110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
//go:build linux
|
|
|
|
package fingerprint
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/shirou/gopsutil/v3/host"
|
|
)
|
|
|
|
const bridgeKernelModuleName = "bridge"
|
|
|
|
const (
|
|
dynamicModuleRe = `%s\s+.*$`
|
|
builtinModuleRe = `.+/%s.ko$`
|
|
dependsModuleRe = `.+/%s.ko(\.xz)?:.*$`
|
|
)
|
|
|
|
func (f *BridgeFingerprint) Fingerprint(req *FingerprintRequest, resp *FingerprintResponse) error {
|
|
if err := f.detect(bridgeKernelModuleName); err != nil {
|
|
f.logger.Warn("failed to detect bridge kernel module, bridge network mode disabled", "error", err)
|
|
return nil
|
|
}
|
|
|
|
resp.NodeResources = &structs.NodeResources{
|
|
Networks: []*structs.NetworkResource{{
|
|
Mode: "bridge",
|
|
}},
|
|
NodeNetworks: []*structs.NodeNetworkResource{{
|
|
Mode: "bridge",
|
|
Device: req.Config.BridgeNetworkName,
|
|
}},
|
|
}
|
|
|
|
resp.AddAttribute("nomad.bridge.hairpin_mode",
|
|
strconv.FormatBool(req.Config.BridgeNetworkHairpinMode))
|
|
|
|
resp.Detected = true
|
|
return nil
|
|
}
|
|
|
|
func (f *BridgeFingerprint) regexp(pattern, module string) *regexp.Regexp {
|
|
return regexp.MustCompile(fmt.Sprintf(pattern, module))
|
|
}
|
|
|
|
func (f *BridgeFingerprint) detect(module string) error {
|
|
// accumulate errors from every place we might find the module
|
|
var errs error
|
|
|
|
// check if the module has been dynamically loaded
|
|
dynamicPath := "/proc/modules"
|
|
if err := f.searchFile(module, dynamicPath, f.regexp(dynamicModuleRe, module)); err != nil {
|
|
errs = multierror.Append(errs, err)
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
// will need kernel info to look for builtin and unloaded modules
|
|
hostInfo, err := host.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if the module is builtin to the kernel
|
|
builtinPath := fmt.Sprintf("/lib/modules/%s/modules.builtin", hostInfo.KernelVersion)
|
|
if err := f.searchFile(module, builtinPath, f.regexp(builtinModuleRe, module)); err != nil {
|
|
errs = multierror.Append(errs, err)
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
// check if the module is dynamic but unloaded (will have a dep entry)
|
|
dependsPath := fmt.Sprintf("/lib/modules/%s/modules.dep", hostInfo.KernelVersion)
|
|
if err := f.searchFile(module, dependsPath, f.regexp(dependsModuleRe, module)); err != nil {
|
|
errs = multierror.Append(errs, err)
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
func (f *BridgeFingerprint) searchFile(module, filename string, re *regexp.Regexp) error {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open %s: %v", filename, err)
|
|
}
|
|
defer func() {
|
|
_ = file.Close()
|
|
}()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
if re.MatchString(scanner.Text()) {
|
|
return nil // found the module!
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return fmt.Errorf("failed to scan %s: %v", filename, err)
|
|
}
|
|
|
|
return fmt.Errorf("module %s not in %s", module, filename)
|
|
}
|