diff --git a/unix/linux/types.go b/unix/linux/types.go index c6a2b6210..f9e0b79f4 100644 --- a/unix/linux/types.go +++ b/unix/linux/types.go @@ -150,6 +150,7 @@ struct termios2 { #include #include #include +#include #include #include #include @@ -537,6 +538,16 @@ struct my_ptp_perout_request { 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" @@ -1090,6 +1101,22 @@ type OpenHow C.struct_open_how 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 ce4d7ab1e..449fd36ec 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -2032,6 +2032,7 @@ func Getrandom(buf []byte, flags int) (n int, err error) { //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 99332eace..d07b1408e 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -141,6 +141,108 @@ func TestIoctlRetInt(t *testing.T) { } } +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.Skipf("listns syscall is not available (need Linux >= 6.19), skipping test: %v", err) + } + 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 80f40e401..249b3b397 100644 --- a/unix/zsyscall_linux.go +++ b/unix/zsyscall_linux.go @@ -1150,6 +1150,23 @@ func Lgetxattr(path string, attr string, dest []byte) (sz int, err error) { // 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 d11d5b96a..cb8b07265 100644 --- a/unix/ztypes_linux.go +++ b/unix/ztypes_linux.go @@ -933,6 +933,29 @@ type OpenHow struct { 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