unix: add listns syscall introduced in Linux 6.19 Add support for the listns(2) system call available since Linux 6.19. The system call enumerates namespace IDs with optional filtering by namespace type and owning user namespace. Change-Id: I8d880a51bcd97004ab81d09ba0f877aae967f3ca GitHub-Last-Rev: 43d1dc0ae5ac2789a666cdf6eec92ca2554ef7c7 GitHub-Pull-Request: golang/sys#279 Reviewed-on: https://go-review.googlesource.com/c/sys/+/781686 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Junyang Shao <shaojunyang@google.com> Reviewed-by: Florian Lehner <lhnr.flrn@gmail.com> Reviewed-by: Michael Pratt <mpratt@google.com> Auto-Submit: Ian Lance Taylor <iant@golang.org> Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
diff --git a/unix/linux/types.go b/unix/linux/types.go index 8c9a907..130eaa1 100644 --- a/unix/linux/types.go +++ b/unix/linux/types.go
@@ -153,6 +153,7 @@ #include <linux/nexthop.h> #include <linux/nfc.h> #include <linux/nl80211.h> +#include <linux/nsfs.h> #include <linux/openat2.h> #include <linux/perf_event.h> #include <linux/pps.h> @@ -568,6 +569,16 @@ unsigned int flags; struct ptp_clock_time on; }; + +// the one defined in linux/nsfs.h has anonymous nested struct +struct my_ns_id_req { + __u32 size; + __u32 spare; + __u64 ns_id; + __u32 ns_type; + __u32 spare2; + __u64 user_ns_id; +}; */ import "C" @@ -1121,6 +1132,22 @@ const SizeofOpenHow = C.sizeof_struct_open_how +type NsIdReq C.struct_my_ns_id_req + +const ( + NS_ID_REQ_SIZE_VER0 = C.NS_ID_REQ_SIZE_VER0 + LISTNS_CURRENT_USER = C.LISTNS_CURRENT_USER + + TIME_NS = C.TIME_NS + MNT_NS = C.MNT_NS + CGROUP_NS = C.CGROUP_NS + UTS_NS = C.UTS_NS + IPC_NS = C.IPC_NS + USER_NS = C.USER_NS + PID_NS = C.PID_NS + NET_NS = C.NET_NS +) + const ( RESOLVE_BENEATH = C.RESOLVE_BENEATH RESOLVE_IN_ROOT = C.RESOLVE_IN_ROOT
diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index 8fbfb0d..e54d20a 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go
@@ -2050,6 +2050,7 @@ //sysnb Kill(pid int, sig syscall.Signal) (err error) //sys Klogctl(typ int, buf []byte) (n int, err error) = SYS_SYSLOG //sys Lgetxattr(path string, attr string, dest []byte) (sz int, err error) +//sys Listns(req *NsIdReq, nsIds []uint64, flags uint) (n int, err error) //sys Listxattr(path string, dest []byte) (sz int, err error) //sys Llistxattr(path string, dest []byte) (sz int, err error) //sys Lremovexattr(path string, attr string) (err error)
diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index 7bf78e6..03f3ebf 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go
@@ -141,6 +141,108 @@ } } +func TestListns(t *testing.T) { + callListns := func(tb testing.TB, req *unix.NsIdReq, nsIDs []uint64, flags uint, context string) int { + tb.Helper() + n, err := unix.Listns(req, nsIDs, flags) + if err != nil { + if errors.Is(err, unix.ENOSYS) { + tb.Skip("listns syscall is not available (need Linux >= 6.19), skipping test") + } + if errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) { + tb.Skipf("listns requires additional privileges in this environment, skipping test: %v", err) + } + tb.Fatalf("%s: Listns failed: %v", context, err) + } + if n < 0 || n > len(nsIDs) { + tb.Fatalf("%s: Listns returned unexpected count %d, want [0, %d]", context, n, len(nsIDs)) + } + return n + } + + t.Run("all namespaces", func(t *testing.T) { + req := &unix.NsIdReq{ + Size: unix.NS_ID_REQ_SIZE_VER0, + Ns_id: 0, + Ns_type: 0, + User_ns_id: 0, + } + ids := make([]uint64, 64) + callListns(t, req, ids, 0, "all namespaces") + }) + + t.Run("network namespaces only", func(t *testing.T) { + req := &unix.NsIdReq{ + Size: unix.NS_ID_REQ_SIZE_VER0, + Ns_id: 0, + Ns_type: uint32(unix.NET_NS), + User_ns_id: 0, + } + ids := make([]uint64, 64) + callListns(t, req, ids, 0, "network namespaces only") + }) + + t.Run("current user namespace owner", func(t *testing.T) { + req := &unix.NsIdReq{ + Size: unix.NS_ID_REQ_SIZE_VER0, + Ns_id: 0, + Ns_type: 0, + User_ns_id: unix.LISTNS_CURRENT_USER, + } + ids := make([]uint64, 64) + callListns(t, req, ids, 0, "current user namespace owner") + }) + + t.Run("network and mount namespaces", func(t *testing.T) { + req := &unix.NsIdReq{ + Size: unix.NS_ID_REQ_SIZE_VER0, + Ns_id: 0, + Ns_type: uint32(unix.NET_NS | unix.MNT_NS), + User_ns_id: 0, + } + ids := make([]uint64, 64) + callListns(t, req, ids, 0, "network and mount namespaces") + }) + + t.Run("pagination via ns_id", func(t *testing.T) { + const pageSize = 10 + req := &unix.NsIdReq{ + Size: unix.NS_ID_REQ_SIZE_VER0, + Ns_id: 0, + Ns_type: 0, + User_ns_id: 0, + } + ids := make([]uint64, pageSize) + + total := 0 + for { + n := callListns(t, req, ids, 0, "pagination") + if n == 0 { + break + } + + // Continue from the last seen namespace ID. + last := ids[n-1] + if last == req.Ns_id { + t.Fatalf("pagination did not advance: last=%d", last) + } + total += n + req.Ns_id = last + + // Partial batch means we've exhausted all results. + if n < pageSize { + break + } + } + + t.Logf("total ns: %d", total) + if total == 0 { + t.Fatalf("pagination should return at least one namespace") + } + }) + +} + func TestIoctlGetRTCTime(t *testing.T) { f, err := os.Open("/dev/rtc0") if err != nil {
diff --git a/unix/zsyscall_linux.go b/unix/zsyscall_linux.go index 86d3b56..ad47efc 100644 --- a/unix/zsyscall_linux.go +++ b/unix/zsyscall_linux.go
@@ -1167,6 +1167,23 @@ // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Listns(req *NsIdReq, nsIds []uint64, flags uint) (n int, err error) { + var _p0 unsafe.Pointer + if len(nsIds) > 0 { + _p0 = unsafe.Pointer(&nsIds[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := Syscall6(SYS_LISTNS, uintptr(unsafe.Pointer(req)), uintptr(_p0), uintptr(len(nsIds)), uintptr(flags), 0, 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Listxattr(path string, dest []byte) (sz int, err error) { var _p0 *byte _p0, err = BytePtrFromString(path)
diff --git a/unix/ztypes_linux.go b/unix/ztypes_linux.go index 538a18f..18da7aa 100644 --- a/unix/ztypes_linux.go +++ b/unix/ztypes_linux.go
@@ -933,6 +933,29 @@ const SizeofOpenHow = 0x18 +type NsIdReq struct { + Size uint32 + Spare uint32 + Ns_id uint64 + Ns_type uint32 + Spare2 uint32 + User_ns_id uint64 +} + +const ( + NS_ID_REQ_SIZE_VER0 = 0x20 + LISTNS_CURRENT_USER = 0xffffffffffffffff + + TIME_NS = 0x80 + MNT_NS = 0x20000 + CGROUP_NS = 0x2000000 + UTS_NS = 0x4000000 + IPC_NS = 0x8000000 + USER_NS = 0x10000000 + PID_NS = 0x20000000 + NET_NS = 0x40000000 +) + const ( RESOLVE_BENEATH = 0x8 RESOLVE_IN_ROOT = 0x10