portlist,tstest: skip tests on kernels with /proc/net/tcp regression

Linux kernel versions 6.6.102-104 and 6.12.42-45 have a regression
in /proc/net/tcp that causes seek operations to fail with "illegal seek".
This breaks portlist tests on these kernels.

Add kernel version detection for Linux systems and a SkipOnKernelVersions
helper to tstest. Use it to skip affected portlist tests on the broken
kernel versions.

Thanks to philiptaron for the list of kernels with the issue and fix.

Updates #16966

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
alexc/better-localbackend-logging
Andrew Dunham 2 weeks ago committed by Andrew Dunham
parent 1ccece0f78
commit 16587746ed

@ -5,12 +5,24 @@ package portlist
import ( import (
"net" "net"
"runtime"
"testing" "testing"
"tailscale.com/tstest" "tailscale.com/tstest"
) )
func maybeSkip(t *testing.T) {
if runtime.GOOS == "linux" {
tstest.SkipOnKernelVersions(t,
"https://github.com/tailscale/tailscale/issues/16966",
"6.6.102", "6.6.103", "6.6.104",
"6.12.42", "6.12.43", "6.12.44", "6.12.45",
)
}
}
func TestGetList(t *testing.T) { func TestGetList(t *testing.T) {
maybeSkip(t)
tstest.ResourceCheck(t) tstest.ResourceCheck(t)
var p Poller var p Poller
@ -25,6 +37,7 @@ func TestGetList(t *testing.T) {
} }
func TestIgnoreLocallyBoundPorts(t *testing.T) { func TestIgnoreLocallyBoundPorts(t *testing.T) {
maybeSkip(t)
tstest.ResourceCheck(t) tstest.ResourceCheck(t)
ln, err := net.Listen("tcp", "127.0.0.1:0") ln, err := net.Listen("tcp", "127.0.0.1:0")
@ -47,6 +60,8 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
} }
func TestPoller(t *testing.T) { func TestPoller(t *testing.T) {
maybeSkip(t)
var p Poller var p Poller
p.IncludeLocalhost = true p.IncludeLocalhost = true
get := func(t *testing.T) []Port { get := func(t *testing.T) []Port {

@ -0,0 +1,50 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
package tstest
import (
"strconv"
"strings"
"golang.org/x/sys/unix"
)
// KernelVersion returns the major, minor, and patch version of the Linux kernel.
// It returns (0, 0, 0) if the version cannot be determined.
func KernelVersion() (major, minor, patch int) {
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return 0, 0, 0
}
release := unix.ByteSliceToString(uname.Release[:])
// Parse version string (e.g., "5.15.0-...")
parts := strings.Split(release, ".")
if len(parts) < 3 {
return 0, 0, 0
}
major, err := strconv.Atoi(parts[0])
if err != nil {
return 0, 0, 0
}
minor, err = strconv.Atoi(parts[1])
if err != nil {
return 0, 0, 0
}
// Patch version may have additional info after a hyphen (e.g., "0-76-generic")
// Extract just the numeric part before any hyphen
patchStr, _, _ := strings.Cut(parts[2], "-")
patch, err = strconv.Atoi(patchStr)
if err != nil {
return 0, 0, 0
}
return major, minor, patch
}

@ -0,0 +1,11 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !linux
package tstest
// KernelVersion returns (0, 0, 0) on unsupported platforms.
func KernelVersion() (major, minor, patch int) {
return 0, 0, 0
}

@ -6,6 +6,7 @@ package tstest
import ( import (
"context" "context"
"fmt"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@ -93,3 +94,20 @@ func Parallel(t *testing.T) {
t.Parallel() t.Parallel()
} }
} }
// SkipOnKernelVersions skips the test if the current
// kernel version is in the specified list.
func SkipOnKernelVersions(t testing.TB, issue string, versions ...string) {
major, minor, patch := KernelVersion()
if major == 0 && minor == 0 && patch == 0 {
t.Logf("could not determine kernel version")
return
}
current := fmt.Sprintf("%d.%d.%d", major, minor, patch)
for _, v := range versions {
if v == current {
t.Skipf("skipping on kernel version %q - see issue %s", current, issue)
}
}
}

@ -3,7 +3,10 @@
package tstest package tstest
import "testing" import (
"runtime"
"testing"
)
func TestReplace(t *testing.T) { func TestReplace(t *testing.T) {
before := "before" before := "before"
@ -22,3 +25,17 @@ func TestReplace(t *testing.T) {
t.Errorf("before = %q; want %q", before, "before") t.Errorf("before = %q; want %q", before, "before")
} }
} }
func TestKernelVersion(t *testing.T) {
switch runtime.GOOS {
case "linux":
default:
t.Skipf("skipping test on %s", runtime.GOOS)
}
major, minor, patch := KernelVersion()
if major == 0 && minor == 0 && patch == 0 {
t.Fatal("KernelVersion returned (0, 0, 0); expected valid version")
}
t.Logf("Kernel version: %d.%d.%d", major, minor, patch)
}

Loading…
Cancel
Save