Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kernel_features: fix tests for new KernelConfig #58

Merged
merged 1 commit into from
Aug 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 54 additions & 50 deletions helpers/kernel_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package helpers

import (
"bufio"
"bytes"
"compress/gzip"
"fmt"
"io"
Expand Down Expand Up @@ -178,64 +179,77 @@ type KernelConfig struct {
}

// InitKernelConfig inits external KernelConfig object
func InitKernelConfig() *KernelConfig {
config := KernelConfig{
configs: make(map[KernelConfigOption]interface{}),
needed: make(map[KernelConfigOption]interface{}),
func InitKernelConfig() (*KernelConfig, error) {
x := unix.Utsname{}
if err := unix.Uname(&x); err != nil {
return nil, fmt.Errorf("could not get utsname")
}
if err := config.initKernelConfig(); err != nil {
return nil

releaseVersion := bytes.TrimRight(x.Release[:], "\x00")
releaseFilePath := fmt.Sprintf("/boot/config-%s", releaseVersion)

config := KernelConfig{}

if err1 := config.initKernelConfig(releaseFilePath); err1 != nil {
if err2 := config.initKernelConfig("/proc/config.gz"); err2 != nil {
return nil, err2
}

return nil, err1
}

return &config
return &config, nil
}

// initKernelConfig inits internal KernelConfig data by calling appropriate readConfigFromXXX function
func (k *KernelConfig) initKernelConfig() error {
k.configs = make(map[KernelConfigOption]interface{})
func (k *KernelConfig) initKernelConfig(configFilePath string) error {
var err error
var file *os.File

x := unix.Utsname{}
if err := unix.Uname(&x); err != nil {
return fmt.Errorf("could not determine uname release: %w", err)
if file, err = os.Open(configFilePath); err != nil {
return fmt.Errorf("could not open %v: %w", configFilePath, err)
}
defer file.Close()

if err := k.readConfigFromBootConfigRelease(string(x.Release[:])); err != nil {
if err2 := k.readConfigFromProcConfigGZ(); err != nil {
return err2
}

head := make([]byte, 2)
if _, err = file.Read(head); err != nil {
return err
}

return nil
}

// readConfigFromProcConfigGZ prepares io.Reader for readConfigFromGZScanner (/proc/config.gz)
func (k *KernelConfig) readConfigFromProcConfigGZ() error {
configFile, err := os.Open("/proc/config.gz")
if err != nil {
return fmt.Errorf("could not open /proc/config.gz: %w", err)
// check if its a gziped file
if head[0] == 0x1f && head[1] == 0x8b {
return k.readConfigFromProcConfigGZ(configFilePath)
}

return k.readConfigFromGZScanner(configFile)
// assume it is a text file
return k.readConfigFromBootConfigRelease(configFilePath)
}

// readConfigFromScanner prepares io.Reader for readConfigFromScanner (/boot/config-$(uname -r))
func (k *KernelConfig) readConfigFromBootConfigRelease(release string) error {
path := fmt.Sprintf("/boot/config-%s", strings.TrimRight(release, "\x00"))
// readConfigFromBootConfigRelease prepares io.Reader (/boot/config-$(uname -r)) for readConfigFromScanner
func (k *KernelConfig) readConfigFromBootConfigRelease(filePath string) error {
file, _ := os.Open(filePath) // already checked
k.readConfigFromScanner(file)
file.Close()

configFile, err := os.Open(path)
if err != nil {
return fmt.Errorf("could not open %s: %w", path, err)
}
return nil
}

k.readConfigFromScanner(configFile)
// readConfigFromProcConfigGZ prepares gziped io.Reader (/proc/config.gz) for readConfigFromScanner
func (k *KernelConfig) readConfigFromProcConfigGZ(filePath string) error {
file, _ := os.Open(filePath) // already checked
zreader, _ := gzip.NewReader(file)
k.readConfigFromScanner(zreader)
zreader.Close()
file.Close()

return nil
}

// readConfigFromScanner reads all existing KernelConfigOption's and KernelConfigOptionValue's from given io.Reader
func (k *KernelConfig) readConfigFromScanner(reader io.Reader) {
k.configs = make(map[KernelConfigOption]interface{})
k.needed = make(map[KernelConfigOption]interface{})

scanner := bufio.NewScanner(reader)
for scanner.Scan() {
kv := strings.Split(scanner.Text(), "=")
Expand All @@ -257,26 +271,14 @@ func (k *KernelConfig) readConfigFromScanner(reader io.Reader) {
}
}

// readConfigFromGZScanner reads all existing KernelConfigOption's and KernelConfigOptionValue's from a gzip io.Reader
func (k *KernelConfig) readConfigFromGZScanner(reader io.Reader) error {
zreader, err := gzip.NewReader(reader)
if err != nil {
return err
}

k.readConfigFromScanner(zreader)

return nil
}

// GetValue will return a KernelConfigOptionValue for a given KernelConfigOption when this is a BUILTIN or a MODULE
func (k *KernelConfig) GetValue(option KernelConfigOption) (KernelConfigOptionValue, error) {
value, ok := k.configs[option].(KernelConfigOptionValue)
if ok {
return value, nil
}

return UNDEFINED, fmt.Errorf("given option's value (%s) is a string", option)
return UNDEFINED, fmt.Errorf("given option's value (%s) is undefined", option)
}

// GetValueString will return a KernelConfigOptionValue for a given KernelConfigOption when this is actually a string
Expand Down Expand Up @@ -308,8 +310,8 @@ func (k *KernelConfig) Exists(option KernelConfigOption) bool {
// ExistsValue will return true if a given KernelConfigOption was found in provided KernelConfig
// AND its value is the same as the one provided by KernelConfigOptionValue
func (k *KernelConfig) ExistsValue(option KernelConfigOption, value interface{}) bool {
if _, ok := k.configs[option]; ok {
switch k.configs[option].(type) {
if cfg, ok := k.configs[option]; ok {
switch cfg.(type) {
case KernelConfigOptionValue:
if value == ANY {
return true
Expand Down Expand Up @@ -348,5 +350,7 @@ func (k *KernelConfig) CheckMissing() []KernelConfigOption {
// kernelConfig.AddNeeded(helpers.CONFIG_HZ, "250")
//
func (k *KernelConfig) AddNeeded(option KernelConfigOption, value interface{}) {
k.needed[option] = value
if _, ok := KernelConfigKeyIDToString[option]; ok {
k.needed[option] = value
}
}
126 changes: 51 additions & 75 deletions helpers/kernel_features_test.go
Original file line number Diff line number Diff line change
@@ -1,98 +1,74 @@
package helpers

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
"testing"
)

func TestGetProcGZConfigByPath(t *testing.T) {
testCases := []struct {
name string
goldenFilePath string
expectedMap KernelConfig
expectedError error
}{
{
name: "non-existent",
goldenFilePath: "foobarblahblahblah",
expectedMap: KernelConfig{},
expectedError: errors.New("could not open foobarblahblahblah: open foobarblahblahblah: no such file or directory"),
},
{
name: "invalid zip format",
goldenFilePath: "testdata/tarred_config.tar",
expectedMap: KernelConfig{},
expectedError: errors.New("gzip: invalid header"),
},
{
name: "standard config",
goldenFilePath: "testdata/config_standard.gz",
expectedMap: KernelConfig{CONFIG_BPF: "y", CONFIG_BPF_JIT_ALWAYS_ON: "y", CONFIG_BPF_LSM: "y", CONFIG_BPF_PRELOAD: "y", CONFIG_BPF_PRELOAD_UMD: "m", CONFIG_BPF_SYSCALL: "y", CONFIG_IPV6_SEG6_BPF: "y", CONFIG_NETFILTER_XT_MATCH_BPF: "m"},
expectedError: nil,
},
{
name: "config with comments in it",
goldenFilePath: "testdata/comments_config.gz",
expectedMap: KernelConfig{CONFIG_BPF: "y", CONFIG_BPF_SYSCALL: "y", CONFIG_BPF_PRELOAD_UMD: "m"},
expectedError: nil,
},
}
func TestGetKernelConfigValue(t *testing.T) {

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
var kconfig KernelConfig = make(map[uint32]string)
err := kconfig.getProcGZConfigByPath(tt.goldenFilePath)
assert.Equal(t, tt.expectedError, err)
assert.Equal(t, tt.expectedMap, kconfig)
})
}
}
allConfigFiles := []string{"testdata/config_standard.gz", "testdata/config_comments.gz", "testdata/config_comments"}

func TestGetKernelConfigValue(t *testing.T) {
testCases := []struct {
name string
key uint32
conf KernelConfig
expectedError error
expectedValue string
testName string
givenOptions []KernelConfigOption
givenValues []interface{} // might either be KernelConfigOptionValue or String
missingOptions []KernelConfigOption // options that will be missing from given config files
}{
{
name: "Value present",
key: CONFIG_BPF,
conf: KernelConfig{CONFIG_BPF: "y"},
expectedError: nil,
expectedValue: "y",
testName: "option ok",
givenOptions: []KernelConfigOption{CONFIG_BPF},
givenValues: []interface{}{BUILTIN},
missingOptions: []KernelConfigOption{},
},
{
name: "Value present",
key: CONFIG_BPF,
conf: KernelConfig{CONFIG_BPFILTER: "foo", CONFIG_BPF: "y"},
expectedError: nil,
expectedValue: "y",
testName: "multiple options ok",
givenOptions: []KernelConfigOption{CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_TEST_BPF, CONFIG_HZ},
givenValues: []interface{}{BUILTIN, BUILTIN, MODULE, "250"},
missingOptions: []KernelConfigOption{},
},
{
name: "nil conf",
key: CONFIG_BPF,
conf: nil,
expectedError: errors.New("kernel config value does not exist, it's possible this option is not present in your kernel version or the KernelConfig has not been initialized"),
expectedValue: "",
testName: "multiple options ok with single not ok",
givenOptions: []KernelConfigOption{CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_TEST_BPF, CONFIG_HZ},
givenValues: []interface{}{MODULE, BUILTIN, MODULE, "250"},
missingOptions: []KernelConfigOption{CONFIG_BPF},
},
{
name: "Value not present",
key: CONFIG_BPF_JIT,
conf: KernelConfig{CONFIG_BPF: "y"},
expectedError: errors.New("kernel config value does not exist, it's possible this option is not present in your kernel version or the KernelConfig has not been initialized"),
expectedValue: "",
testName: "multiple options ok with multiple not ok",
givenOptions: []KernelConfigOption{CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_TEST_BPF, CONFIG_HZ, CONFIG_HZ},
givenValues: []interface{}{MODULE, BUILTIN, MODULE, "250", "500"},
missingOptions: []KernelConfigOption{CONFIG_BPF, CONFIG_HZ},
},
{
testName: "undefined value",
givenOptions: []KernelConfigOption{0xFFFFFFFF},
givenValues: []interface{}{UNDEFINED}, // non-existing values will be ignored
missingOptions: []KernelConfigOption{},
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
v, err := tt.conf.GetKernelConfigValue(tt.key)
assert.Equal(t, tt.expectedError, err)
assert.Equal(t, tt.expectedValue, v)
for _, tt := range testCases { // for each of the test cases run:
t.Run(tt.testName, func(test *testing.T) { // a test named testName with the following func():
for _, configFile := range allConfigFiles {
var err error

// initialize the KernelConfig object
config := KernelConfig{}
err = config.initKernelConfig(configFile)
assert.Equal(test, err, nil)

// add needed KernelConfigOptions
for pos, option := range tt.givenOptions {
config.AddNeeded(option, tt.givenValues[pos])
}

// check amount of missing KernelConfigOptions first
missing := config.CheckMissing()
assert.Equal(test, len(tt.missingOptions), len(missing))

// check if missing KernelConfigOptions are the correct ones
assert.ElementsMatch(test, tt.missingOptions, missing)
}
})
}
}
Binary file removed helpers/testdata/comments_config.gz
Binary file not shown.
17 changes: 17 additions & 0 deletions helpers/testdata/config_comments
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# this is a comment in the beginning
CONFIG_BPF=y
CONFIG_BPF_LSM=y
CONFIG_BPF_SYSCALL=y
# this is comment in the middle
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_BPF_PRELOAD=y
CONFIG_BPF_PRELOAD_UMD=m
# CONFIG_BLAH is not set
CONFIG_IPV6_SEG6_BPF=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
CONFIG_HZ_250=y
CONFIG_HZ=250
CONFIG_TEST_BPF=m
# a comment at the end
Binary file added helpers/testdata/config_comments.gz
Binary file not shown.
Binary file modified helpers/testdata/config_standard.gz
Binary file not shown.
Binary file removed helpers/testdata/tarred_config.tar
Binary file not shown.