diff --git a/pkg/coretag/coretag_test.go b/pkg/coretag/coretag_test.go index 5eb2e51b0..1930716c4 100644 --- a/pkg/coretag/coretag_test.go +++ b/pkg/coretag/coretag_test.go @@ -22,14 +22,14 @@ import ( ) func TestEnable(t *testing.T) { - major, minor, err := hostos.KernelVersion() + version, err := hostos.KernelVersion() if err != nil { t.Fatalf("Unable to parse kernel version: %v", err) } // Skip running test when running on Linux kernel < 5.14 because core tagging // is not available. - if major < 5 && minor < 14 { - t.Skipf("Running on Linux kernel: %d.%d < 5.14. Core tagging not available. Skipping test.", major, minor) + if version.LessThan(5, 14) { + t.Skipf("Running on Linux kernel: %s < 5.14. Core tagging not available. Skipping test.", version) return } if err := Enable(); err != nil { diff --git a/pkg/cpuid/cpuid_parse_amd64_test.go b/pkg/cpuid/cpuid_parse_amd64_test.go index 4bf67e484..519d72f5e 100644 --- a/pkg/cpuid/cpuid_parse_amd64_test.go +++ b/pkg/cpuid/cpuid_parse_amd64_test.go @@ -17,38 +17,40 @@ package cpuid -func archSkipFeature(feature Feature, major, minor int) bool { +import "gvisor.dev/gvisor/pkg/hostos" + +func archSkipFeature(feature Feature, version hostos.Version) bool { switch { // Block 0. - case feature == X86FeatureSDBG && (major < 4 || major == 4 && minor < 3): + case feature == X86FeatureSDBG && version.AtLeast(4, 3): // SDBG only exposed in // b1c599b8ff80ea79b9f8277a3f9f36a7b0cfedce (4.3). return true // Block 2. - case feature == X86FeatureRDT && (major < 4 || major == 4 && minor < 10): + case feature == X86FeatureRDT && version.AtLeast(4, 10): // RDT only exposed in // 4ab1586488cb56ed8728e54c4157cc38646874d9 (4.10). return true // Block 3. - case feature == X86FeatureAVX512VBMI && (major < 4 || major == 4 && minor < 10): + case feature == X86FeatureAVX512VBMI && version.AtLeast(4, 10): // AVX512VBMI only exposed in // a8d9df5a509a232a959e4ef2e281f7ecd77810d6 (4.10). return true - case feature == X86FeatureUMIP && (major < 4 || major == 4 && minor < 15): + case feature == X86FeatureUMIP && version.AtLeast(4, 15): // UMIP only exposed in // 3522c2a6a4f341058b8291326a945e2a2d2aaf55 (4.15). return true - case feature == X86FeaturePKU && (major < 4 || major == 4 && minor < 9): + case feature == X86FeaturePKU && version.AtLeast(4, 9): // PKU only exposed in // dfb4a70f20c5b3880da56ee4c9484bdb4e8f1e65 (4.9). return true // Block 4. - case feature == X86FeatureXSAVES && (major < 4 || major == 4 && minor < 8): + case feature == X86FeatureXSAVES && version.AtLeast(4, 8): // XSAVES only exposed in // b8be15d588060a03569ac85dc4a0247460988f5b (4.8). return true // Block 5. - case feature == X86FeaturePERFCTR_LLC && (major < 4 || major == 4 && minor < 14): + case feature == X86FeaturePERFCTR_LLC && version.AtLeast(4, 14): // PERFCTR_LLC renamed in // 910448bbed066ab1082b510eef1ae61bb792d854 (4.14). return true diff --git a/pkg/cpuid/cpuid_parse_arm64_test.go b/pkg/cpuid/cpuid_parse_arm64_test.go index aaced4a24..42532aa6b 100644 --- a/pkg/cpuid/cpuid_parse_arm64_test.go +++ b/pkg/cpuid/cpuid_parse_arm64_test.go @@ -17,6 +17,6 @@ package cpuid -func archSkipFeature(feature Feature, major, minor int) bool { +func archSkipFeature(feature Feature, version hostos.Version) bool { return false } diff --git a/pkg/cpuid/cpuid_parse_test.go b/pkg/cpuid/cpuid_parse_test.go index 738a01cb5..bf1a4e284 100644 --- a/pkg/cpuid/cpuid_parse_test.go +++ b/pkg/cpuid/cpuid_parse_test.go @@ -31,7 +31,7 @@ import ( // analog in the actual CPUID feature set. func TestHostFeatureFlags(t *testing.T) { // Extract the kernel version. - major, minor, err := hostos.KernelVersion() + version, err := hostos.KernelVersion() if err != nil { t.Fatalf("Unable to parse kernel version: %v", err) } @@ -54,7 +54,7 @@ func TestHostFeatureFlags(t *testing.T) { for feature, info := range allFeatures { // Special cases not consistently visible. We don't mind if // they are exposed in earlier versions. - if archSkipFeature(feature, major, minor) { + if archSkipFeature(feature, version) { continue } diff --git a/pkg/hostos/BUILD b/pkg/hostos/BUILD index d7a0a981f..c30ce3aa6 100644 --- a/pkg/hostos/BUILD +++ b/pkg/hostos/BUILD @@ -6,5 +6,8 @@ go_library( name = "hostos", srcs = ["hostos.go"], visibility = ["//:sandbox"], - deps = ["@org_golang_x_sys//unix:go_default_library"], + deps = [ + "@org_golang_x_mod//semver:go_default_library", + "@org_golang_x_sys//unix:go_default_library", + ], ) diff --git a/pkg/hostos/hostos.go b/pkg/hostos/hostos.go index dcd2178e4..de0a893a7 100644 --- a/pkg/hostos/hostos.go +++ b/pkg/hostos/hostos.go @@ -17,41 +17,71 @@ package hostos import ( "fmt" - "strconv" + "regexp" "strings" + "sync" + "golang.org/x/mod/semver" "golang.org/x/sys/unix" ) -// KernelVersion returns the major and minor release version of the kernel using uname(). -func KernelVersion() (int, int, error) { - var u unix.Utsname - if err := unix.Uname(&u); err != nil { - return 0, 0, err +// Version represents a semantic version of the form "%d.%d[.%d]". +type Version struct { + version string +} + +// AtLeast returns whether vr is at least version major.minor. +func (vr Version) AtLeast(major, minor int) bool { + return semver.Compare(vr.version, fmt.Sprintf("v%d.%d", major, minor)) >= 0 +} + +// LessThan returns whether vr is less than version major.minor. +func (vr Version) LessThan(major, minor int) bool { + return !vr.AtLeast(major, minor) +} + +// String implements fmt.Stringer. +func (vr Version) String() string { + if vr.version == "" { + return "unknown" } + // Omit the "v" prefix required by semver. + return vr.version[1:] +} - var sb strings.Builder - for _, b := range u.Release { - if b == 0 { - break +// These values are effectively local to KernelVersion, but kept here so as to +// work with sync.Once. +var ( + semVersion Version + unameErr error + once sync.Once +) + +// KernelVersion returns the version of the kernel using uname(). +func KernelVersion() (Version, error) { + once.Do(func() { + var utsname unix.Utsname + if err := unix.Uname(&utsname); err != nil { + unameErr = err + return } - sb.WriteByte(byte(b)) - } - s := strings.Split(sb.String(), ".") - if len(s) < 2 { - return 0, 0, fmt.Errorf("kernel release missing major and minor component: %s", sb.String()) - } + var sb strings.Builder + for _, b := range utsname.Release { + if b == 0 { + break + } + sb.WriteByte(byte(b)) + } - major, err := strconv.Atoi(s[0]) - if err != nil { - return 0, 0, fmt.Errorf("error parsing major version %q in %q: %w", s[0], sb.String(), err) - } - - minor, err := strconv.Atoi(s[1]) - if err != nil { - return 0, 0, fmt.Errorf("error parsing minor version %q in %q: %w", s[1], sb.String(), err) - } + versionRegexp := regexp.MustCompile(`[0-9]+\.[0-9]+(\.[0-9]+)?`) + version := "v" + string(versionRegexp.Find([]byte(sb.String()))) + if !semver.IsValid(version) { + unameErr = fmt.Errorf("invalid version found in release %q", sb.String()) + return + } + semVersion.version = version + }) - return major, minor, nil + return semVersion, unameErr } diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index 53030ad61..9196fb041 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -40,6 +40,7 @@ go_library( "//pkg/fd", "//pkg/flipcall", "//pkg/fspath", + "//pkg/hostos", "//pkg/log", "//pkg/memutil", "//pkg/rand", @@ -119,7 +120,6 @@ go_library( "//runsc/specutils/seccomp", "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", "@org_golang_google_protobuf//proto:go_default_library", - "@org_golang_x_mod//semver:go_default_library", "@org_golang_x_sys//unix:go_default_library", ], ) diff --git a/runsc/boot/network.go b/runsc/boot/network.go index d524a9765..0d4f64e16 100644 --- a/runsc/boot/network.go +++ b/runsc/boot/network.go @@ -17,12 +17,11 @@ package boot import ( "fmt" "net" - "regexp" "runtime" "strings" - "golang.org/x/mod/semver" "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/hostos" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/link/ethernet" @@ -197,16 +196,12 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct } // Choose a dispatch mode. - // Get the Linux kernel version. Uname will return something like - // "5.17.7-1distro-amd64", from which we'd want "5.17.7". - var uname unix.Utsname - if err := unix.Uname(&uname); err != nil { + dispatchMode := fdbased.RecvMMsg + version, err := hostos.KernelVersion() + if err != nil { return err } - re := regexp.MustCompile(`[0-9]+\.[0-9]+(\.[0-9]+)?`) - version := "v" + string(re.Find(uname.Release[:])) - dispatchMode := fdbased.RecvMMsg - if semver.IsValid(version) && semver.Compare(version, "v5.6") >= 0 { + if version.AtLeast(5, 6) { dispatchMode = fdbased.PacketMMap } else { log.Infof("Host kernel version < 5.6, falling back to RecvMMsg dispatch")