Repository navigation
New DCE applications - #151
Gabrielcarvfer wants to merge 59 commits into
Conversation
Run the GitHub Actions workflow on pushes to master and on every pull request, and drop the obsolete bake-based workflow (Ubuntu 20.04 runners and upload-artifact@v1 no longer exist). Add utils/build_kernel_deps.sh, which builds what bake used to provide: net-next-nuse (Linux 4.4 libos), freebsd-sim, DCE builds of ip, iperf, thttpd, wget, ping/ping6 and quagga, and the ns-3-dce-quagga module, patched for current ns-3 and toolchains (GCC 11-14, Make 4.4). The workflow caches its output and configures DCE with --enable-kernel-stack. Fix DCE for ns-3.46 and Linux semantics: - set the node on Ipv4Interfaces (Linux and FreeBSD glue) and give kernel-stack nodes an inert ArpL3Protocol for CSMA devices - notify connection success for connectionless kernel sockets - deliver multicast locally in Ipv4DceRouting (raw sockets, OSPF) - implement sigwait() - ns-3 stream sockets: connect() succeeds when the peer accepts and closes at once, and reports ECONNREFUSED when refused - ns-3 stack: IPv6 UDP sockets, IPV6_JOIN/LEAVE_GROUP, IPV6_RECVPKTINFO, ICMP6_FILTER, IPV6_PKTINFO on send, no IPv6 header on raw receive, delivery for raw-only IPv6 protocols such as OSPFv3 - netlink and pktinfo use Linux-style interface indexes consistently - reset glibc's getopt state for each simulated process - drop the NSC stacks from dce-tcp-ns3-nsc-comparison; use 802.11a in the MPTCP handoff examples Enable the disabled select/poll/tcp-socket/signal process-manager tests, all quagga test cases and the dce-xfrm example. 79/79 pass on Ubuntu 22.04, Ubuntu 24.04 and Debian trixie with ns-3.46. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ipv4InterfaceAddress no longer has secondary addresses: drop the IsSecondary() checks in the Linux and FreeBSD IPv4 glue - Ipv4GlobalRouting moved to ns3/global-routing.h - Address::Register() now takes the address kind and its length - CI: build against ns-3.49; drop Ubuntu 22.04, whose CMake (3.22) is older than ns-3.49 requires 79/79 tests and examples pass on Ubuntu 24.04 and Debian trixie. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- TaskManager: - RunningTask(): the task whose fiber runs, ignoring the hi task of DceManager temporary tasks (only a fiber can block). - RunNow(): from the main fiber, run the ready tasks until a condition holds, without advancing time. - SetTaskEndNotifier(): called when a task exits or is stopped. - LinuxSocketImpl (ns-3 sockets backed by the kernel): - Uses the generic UnixFd, not the libos KernelSocketFd. - Delivers the data that arrived with the end of a connection before treating the socket as closed. - After a send failed with EAGAIN, notifies the application again only once time went on: DCCP reports sockets writable while its sends fail, and the failed send wakes the socket again (zero-time livelock). - getaddrinfo: ignore AI_ADDRCONFIG (also the default without hints), which checks the host's addresses, not the node's: without IPv6 on the host, IPv6 addresses did not resolve. - if_nametoindex/if_indextoname ask the kernel of LKL nodes too, and close the socket they open. - ns3waf: the headers copied to build/include/ns3 are outputs of the copy task, so that waf rebuilds what includes them when they change (stale example objects crashed after a class layout change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
utils/build_lkl.sh builds LKL (Linux 6.12, pinned commit, shallow fetch) for DCE: it applies utils/lkl-patches, configures LKL's defconfig plus utils/lkl-dce.config, and relinks liblkl.so without default libraries (its libc references bind to the simulator's libc; DCE loads a copy per kernel instance) and stripped, with the debug information in liblkl.so.debug. It also copies LKL's headers: configure DCE with --with-lkl=<output>. utils/lkl-dce.config adds what the examples and tests use, as the libos kernel had it: SCTP, DCCP (CCID 3), MPTCP, AF_PACKET, IPsec and its crypto, Mobile IPv6, tunnels, multicast routing, more TCP congestion controls. Patch 0001 backports the upstream fix for the 6.12 deadline server counted in nr_running (52d15521eb75), which kept an idle kernel busy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 0002 lkl: stop the tick while the host is idle. The CPU idles in idle_host_task, a regular task, and after system calls while the host task stays current: the tick never stopped, and a simulator processes every tick (~100 per second per node). Enter NOHZ idle there and leave it when work resumes (system call, thread exit, woken task); re-stop the tick after interrupts that wake nothing, at the outermost level only; mark the clocksource valid for high resolution timers. - 0003 crypto: jitter - do not loop forever on a clock that does not advance. Outside FIPS mode, jent_gen_entropy() never gave up on stuck measurements: the kernel hung at boot on simulated time once the DRBG (pulled in by IPsec) was built in. - 0004 lkl: show_stack: stop at the first frame outside of the kernel. It followed the frame chain into host code without frame pointers and crashed the host on any kernel warning. TOUPSTREAM Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
configure --with-lkl=<utils/build_lkl.sh output> builds DCE with LKL, the Linux kernel as a library, as a node's network stack. Without --enable-kernel-stack (libos, Linux 4.4), LKL provides ns3::LinuxSocketFdFactory, so the existing kernel examples and tests run on Linux 6.12 unchanged (the libos library name liblinux.so selects liblkl.so); with it, libos stays ns3::LinuxSocketFdFactory and LKL is ns3::LklSocketFdFactory.
LklKernel (model/lkl/lkl-kernel.*): one kernel per node, its own copy of liblkl.so (a new one for each simulation in a process), with LKL's host operations on DCE:
- Threads are DCE tasks; semaphores and mutexes wait lists of tasks; thread-local storage per task, dropped when the task ends (destructors run in a task of their own).
- Time is the simulation time; timers are simulator events.
- System calls from the main fiber (ns-3 sockets of LinuxSocketImpl) run in a task, driven without advancing time.
- Kernel memory is released when the kernel object is destroyed.
LklSocketFdFactory (model/lkl/lkl-socket-fd-factory.*):
- Boots the kernel at simulation start, mounts procfs.
- Every ns-3 NetDevice becomes a virtio-net device sim<n> (found by MAC address), with the device's MTU; IFF_NOARP when the device cannot carry ARP (point-to-point). The netdev poll task does not re-report frames the kernel has no buffers for; at most 1024 wait.
- Sockets are LklSocketFd, whose operations are system calls; poll and select through a per-node epoll watcher (persistent, edge-triggered: a kernel wait queue).
- Set/Get: sysctls as libos names them (".net.ipv4.tcp_sack"), or any kernel file by absolute path (/proc/net/netstat); ScheduleTask; the Mptcp attribute makes applications' TCP sockets MPTCP sockets.
Build (wscript): LKL_STACK and, without libos, LKL_LINUX; LINUX_STACK (any Linux stack provides ns3::LinuxSocketFdFactory) for LinuxStackHelper, the Linux socket factories and the iperf examples; KERNEL_STACK keeps meaning libos; dce-freebsd is built only with libos.
Examples: dce-lkl-boot (kernels boot, exact 10 s sleep), dce-lkl-p2p (ip configuration, TCP and UDP between two kernels or over loopback; --stack=linux for libos). The cradle, process manager and quagga test suites look for liblkl.so in LKL-only builds (they skipped otherwise).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Routers enable IPv4 forwarding where the examples assumed it, with conf.all.forwarding (conf.default.forwarding only applies to interfaces created afterwards): dce-tcp-ns3-nsc-comparison received nothing on LKL. - The MPTCP test and examples were written for the out-of-tree MPTCP (v0) that libos never had: the test skipped itself, the examples ran single-path TCP if anything. Now on Linux MPTCP (v1), with LKL: - Applications get MPTCP sockets (LklSocketFdFactory::Mptcp); subflows come from "ip mptcp limits/endpoint" (v0's fullmesh path manager, "ip link set multipath" and the net.mptcp.mptcp_* sysctls are gone). - iperf servers no longer use -P 1: they stop listening after that many connections, and Linux MPTCP accepts subflows on the listener. - dce-mptcp test: runs when .net.mptcp.enabled is 1 and checks that packets from both client addresses reach the server. - dce-cradle-mptcp, dce-iperf-mptcp fail unless both paths carry data; the handoff examples report what each address carried. - build_kernel_deps.sh builds iproute2 6.12 (has "ip mptcp"; also works with libos); LIBOS=0 skips net-next-nuse and freebsd-sim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ered - LKL is built (utils/build_lkl.sh, cached on the script, the patches and the kernel configuration) and installed with the DCE binaries. - Matrix: Ubuntu 24.04 and Debian trixie build DCE with LKL as ns3::LinuxSocketFdFactory (no --enable-kernel-stack, dependencies with LIBOS=0); Ubuntu 24.04 also builds the libos configuration (Linux 4.4 and FreeBSD, LKL as ns3::LklSocketFdFactory). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LKL (Linux 6.12) runs every kernel example and test the libos kernel did, so the libos kernel stack goes, and with it the FreeBSD stack, which was built on the same libos API (KernelSocketFdFactory and libos' sim.h headers). - configure: --enable-kernel-stack is gone; --with-lkl provides ns3::LinuxSocketFdFactory and defines LINUX_STACK (KERNEL_STACK, LKL_LINUX and LKL_STACK are gone). - LKL takes over the libos classes and files: LklSocketFdFactory and LklSocketFd become KernelSocketFdFactory and KernelSocketFd (model/kernel-socket-fd*), and the LKL LinuxSocketFdFactory moves to model/linux-socket-fd-factory.cc. - Removed: the libos KernelSocketFdFactory/KernelSocketFd and LinuxSocketFdFactory code, FreeBSDSocketFdFactory, Ipv4FreeBSD, FreeBSDStackHelper, the dce-freebsd and freebsd-iproute examples and the FreeBSD test cases, utils/freebsd-sim-dce.patch, utils/mptcp-build.sh (libos MPTCP kernel); the Python bindings drop their KERNEL_STACK switch. - utils/build_kernel_deps.sh builds only the programs (ip, iperf, ping, quagga, ...) and the quagga module; CI has one job per distribution. - Tests and examples drop their libos branches; the process manager test's stack masks are renamed (LINUX_STACK is the build's macro). - Documentation: the kernel guide, getting started and the subprojects page describe LKL; the FreeBSD page is removed; the kernel developer page is marked as describing the former kernel support. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1241b13 to
cf76231
Compare
A process still running when the simulation stops is deleted with it, and its finished callback then reported success: a program that hung passed. The programs that do not end today are listed explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pthread_kill only woke a thread that did not block the signal, while sigwait callers block the signals they wait for: the thread slept forever. sigwait also consumed both the thread's and the process' pending instance of a signal, losing one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The timer host operation runs in the node's tasks, which run on threads of their own with PthreadFiberManager, the default: Simulator::Schedule must not be called there (ns-3 asserts it in debug builds). Use TaskManager::ScheduleMain, like the other events DCE tasks schedule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@cubic-dev-ai ultrareview |
@thehajime cubic can't start this ultrareview because you've reached your trial's review limit. Trial plans have lower review limits than paid plans. Upgrade now to resume reviews. To help optimise your usage, you can tune cubic to get the most out of your usage limits:
|
DCE deletes the tasks of a process it stops (e.g. ApplicationContainer Stop) at once, wherever they are. A task blocked in an LKL system call leaves the kernel referring to it: the entries on its stack in the socket's wait queues, its LKL wait lists. The next wake-up of the socket crashed the simulation (dce-lkl-p2p --stop). Blocking socket calls (read, recvmsg, sendmsg, accept, connect) are now made non-blocking, and wait in DCE for the socket's events, honoring SO_RCVTIMEO and SO_SNDTIMEO and DCE signals; a blocking send still returns once all its data is queued. The LKL wait lists forget stopped tasks, as do the thread identities of the tasks the kernel did not create, which leaked. The files of a stopped process are released without being closed: release their kernel socket then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LTE devices do not support promiscuous mode, so the promiscuous protocol handler never got their packets: an LTE UE received nothing (the libos stack registered a non-promiscuous handler for them). The destination address of their frames was also converted from the device's Mac64Address; use the address of the kernel device instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The libos stack's LinuxSocketFdFactory enabled it, and scripts written for it rely on it: routers that set no sysctl did not forward (e.g. dce-mptcp-lte-wifi-v6). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dce-mptcp-lte-wifi: the WiFi router was out of range (ns-3 no longer detects frames below -82 dBm), its route to node 1 pointed at its own address, and node 1 had no route back to the WiFi network for the subflow it gets on its LTE-side address. dce-mptcp-lte-wifi-v6: the mobile node's addresses were hardcoded from an older MAC address allocation; compute them from the devices. The connection starts over IPv4, so the IPv6 subflow needs an IPv6 socket (the IPv4-mapped server address) and the server's IPv6 address, which it now announces; the mobile node's address is configured before the announcement arrives. Both examples now fail unless the server receives TCP payload from every address of the client, and run with test.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
if_nametoindex returned the IPv4 interface index, while netlink messages and socket options now use NetDevice index + 1, like Linux with its loopback device first: a route added with "dev" could go to another device. Link messages also reported the previous device as the parent link (IFLA_LINK) of each device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a list of targets, its name became their comma-joined list, so the use of it by name did not resolve. (Headers were still generated first: their group is built before the modules'.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tarballs are checked against pinned SHA-256 digests, and thttpd is downloaded over HTTPS. The iperf and ping DCE patches were applied with "|| true", which also accepted a patch that does not apply, building programs without the changes DCE needs; a patch is now skipped only if already applied. The quagga module patch is skipped the same way, which made a second run abort. A source tree from an earlier run must be at the requested revision (e.g. not a libos-era iproute2 without "ip mptcp"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Breaking out of jent_gen_entropy() on the repetition count test's intermittent failure also ended entropy generation early on a real clock, without reporting it outside of FIPS mode, and for good, since the failure flag stays set. Count consecutive stuck measurements instead, with a limit far above the test's cutoffs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dce-lkl-boot: reject --nodes=0, count a failed uname, fail if the sleep took host time, and run long enough for all the nodes. dce-lkl-p2p: check that tcp-server read all that tcp-client sent, not only that it stopped reading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check that every path carries data counted TCP headers, so a path with only a handshake or ACKs passed. The subflow limits were fixed at 4, while the fullmesh endpoints need nRtrs * nRtrs - 1 subflows: derive them, and reject the router counts Linux MPTCP's 8 subflows cannot cover (more than 3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mobile node was out of the second access point's range (ns-3 no longer detects frames below -82 dBm): place it within reach of both. Its table 3 default route was added before sim1 was up, and failed; the address added at 15.2 s had no source routing rule, and its MPTCP endpoint was added while the address was still tentative, so no subflow was opened from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a static multicast route, Ipv4StaticRouting only forwards the packet, so raw sockets (e.g. a routing daemon's for 224.0.0.5) stopped receiving the group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On EMFILE, dce_socket leaked the socket it had created: with LKL, its kernel socket, so that the kernel soon ran out of descriptors too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7dbe95e to
a8223f7
Compare
irq_enter() brings a stale jiffies up to date only when the idle task is current, but LKL idles in idle_host_task or in a host task that stays current after a system call. With the tick stopped by 0002, interrupts ran with an old jiffies, and the timers armed from them, such as the TCP retransmission timer armed on an incoming ACK, expired early: a transfer losing 10 segments retransmitted 84. Update jiffies on interrupt entry when another task is current, as the idle task would.
7c8fea9 to
ad5fb43
Compare
The kernel re-arms its timer whenever it stops or restarts the tick, and TimerSetOneshot() cancelled the previous ns-3 event before scheduling the new one. A cancelled event stays in the simulator's queue until its time, which is far away when the tick stops: with a busy kernel (a web browser streaming over the simulated network) hundreds of thousands of dead events piled up every second, and the simulator grew by 60-70 MB/s until it ran out of memory. Remove the event instead, on the simulator's thread, and in TimerFree() too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pthread_cond_wait() released the mutex before registering the thread as a waiter, and pthread_mutex_unlock() lets a waiting thread run: a thread could take the mutex, update the predicate and signal before the waiter waited, and the waiter then slept forever. The wait now registers first and releases the mutex without letting other threads run, so that unlocking and sleeping are atomic, as POSIX requires. A waiter which stopped waiting without a signal (a timeout) stayed in the waiting list, and a later pthread_cond_signal() woke it instead of a thread which still waited: it now leaves the list. pthread_mutex_unlock() read the first waiter of an empty list. Mutexes and condition variables were looked up in the list of all those the process ever created, at every operation: Chromium creates tens of thousands. Index them by id. Error checking and adaptive (NSPR's) mutexes are accepted, as normal ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
socketpair() made two sockets not connected to each other: data written to one end never reached the other. The two ends are now connected, for stream and datagram sockets. SOCK_SEQPACKET is a stream socket which keeps the boundaries of the messages: a read returns one message (the rest of it is lost if the buffer is short), and a message is received whole or not at all. sendmsg()/recvmsg() on local stream sockets: - pass descriptors (SCM_RIGHTS): they go with the first byte of the data sent with them, and a read stops before data that came with descriptors, so that they are received with it, as on Linux; - write the iovecs in one write (one message for SOCK_SEQPACKET); - set msg_controllen and msg_flags: the caller used to parse its own uninitialized control buffer; - no longer add -1 (an error, as size_t) to the length read, nor lose the data read into a first iovec when the next read would block; - honour MSG_DONTWAIT and MSG_PEEK. A blocking read returns what has arrived, as on Linux, instead of waiting until the whole buffer is filled. shutdown() only set flags: a thread blocked reading the socket slept on. It now wakes the readers and writers, a socket shut down for reading reads end of file and polls readable, and the peer of one shut down for writing reads end of file. A write to a socket whose peer is gone returned 0, which writers that retry short writes did forever: it fails with EPIPE, and so does a write to a peer shut down for reading. ioctl(): FIONREAD gives the bytes waiting (those of the next message for SOCK_SEQPACKET), FIONBIO sets or clears O_NONBLOCK; both failed with EINVAL. Mojo (Chromium's IPC) and Chromium's audio (SyncSocket) need all of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UcontextFiberManager cannot clone a fiber: its Clone () returns 0, which TaskManager::Clone () and DceManager::Clone () take for the child's return, so fork () returned 0 in the parent. A program forking a helper (Brave launching its crash handler) then ran the child's code itself: it failed to exec the helper and exited with 127. fork () now fails with ENOSYS when the fiber manager cannot clone (FiberManager/TaskManager::CanClone).
PthreadFiberManager's fibers jump from one thread's stack to another's with longjmp(), on purpose. With _FORTIFY_SOURCE, which Ubuntu's compilers define when optimizing, longjmp() checks that it goes to an outer frame of the same stack and aborted ("longjmp causes uninitialized stack frame") whenever the target stack was lower: test-fork, and with it dce-process-manager, crashed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A SIGSEGV, SIGBUS, SIGILL or SIGFPE in a program (its code or the libraries it uses) killed the simulator. The signal handler now resumes the task in a function which ends its process, as the kernel would (no atexit handlers, no stdio flush, "Killed by signal" in its status; dce_exit_signal), and the simulation goes on. Faults in the simulator's own code (ns-3, DCE, the simulation's executable) and stack overflows still end it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eventfd(2), epoll(7) and pipe2(2), which the event loops of GLib, Qt, libevent, libuv and libpulse use: EventFd is a counter with a wait queue, EpollFd a set of entries waited on with the PollTable mechanism of poll(2)/select(2), level-triggered (EPOLLET accepted, EPOLLONESHOT honoured), dropping closed descriptors from the set as the kernel does. epoll_wait() with a zero timeout and nothing ready lets the simulated clock catch up with the wall clock, as poll() does: an event loop polling until a deadline less than a millisecond away would otherwise spin forever, the clock standing still while a task runs. test-eventfd and test-epoll cover them. libc for the Dillo browser and its libraries (FLTK, Xft, fontconfig, freetype, libpng, libjpeg): fortified fgets/fread/open/readlink/realpath/ strlcpy helpers, realpath, utimes, posix_fadvise, system (ENOSYS), iconv, the ctype functions, random_r/initstate_r, pthread_attr_*, longjmp, modf. Fixes: - __sprintf_chk() took glibc's (buf, len, format) arguments instead of (buf, flag, buflen, format): the buffer size was used as the format string (FLTK's Fl_Preferences crashed); - closing a pipe end whose peer was already closed returned EBADF. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- futex(2): GLib's locks (and Rust's std) call syscall(SYS_futex) directly, and a host futex would block the whole simulator. syscall() is now a DCE function that emulates FUTEX_WAIT/WAKE (plain, _BITSET, _PRIVATE, relative and absolute timeouts) and FUTEX_REQUEUE/CMP_REQUEUE with the DCE scheduler, and passes the other system calls to the host (test-futex). - malloc() returned 8 byte aligned memory; glibc guarantees 16 on x86_64 and compilers rely on it (GTK crashed on a movaps). Every block now has a 16 byte header (buffer address, size) and is aligned on 16 bytes, or on what posix_memalign()/memalign()/aligned_alloc() ask for; realloc() and malloc_usable_size() follow. - The file and process calls GIO probes: statx, utimensat, faccessat, scandir64/scandirat, openat64/creat64/freopen64/tmpfile64/mkostemp64, statvfs64, the extended attributes, inotify, posix_spawn, close_range, copy_file_range/splice, fallocate, accept4, recvmmsg, waitid, and about 60 pass-through functions (model/dce-compat.cc). Unsupported operations report ENOSYS/ENOTSUP/ECHILD, which GIO handles. - realpath() no longer resolves symbolic links out of the node's file system; __sprintf_chk/__vsprintf_chk/__vasprintf_chk/__syslog_chk/ __getcwd_chk, difftime, dladdr, getauxval, lroundf and the ctype functions. - A fortified sprintf() whose destination size the compiler cannot see passes (size_t) -1 as that size, which __sprintf_chk/__vsprintf_chk handed to vsnprintf: glibc computes the end of the buffer as s + size, which wraps around, and wrote one character and the terminator (OpenSSL builds its TLS key log lines so). The size is capped at INT_MAX (test-string). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
What a GTK4 web browser with sound (Northstar) needs from DCE, and the slowdowns it showed: - Real time: a zero-timeout poll() that finds nothing ready lets the simulated clock catch up with the wall clock instead of advancing it by 1 us. Programs spinning on zero-timeout GLib sources dragged the whole simulation behind real time (40 s took 130 s, now 43). - libc forwarding: on x86-64 each forwarded libc function is a naked tail jump through the (now hidden) function table instead of __builtin_apply, which copied a 128 byte argument block and the registers on every call and made short functions (strlen, memcpy) many times slower than the functions themselves. - Heap: thread switches no longer walk the whole heap (one list entry per 32 KB) unless the process was fork()ed; KingsleyAlloc::Brk bumps the newest small chunk instead of walking every chunk for free space, and large blocks own a chunk at the back of the list; calloc() does not clear large blocks, fresh anonymous mappings that are already zero. - dlsym(RTLD_DEFAULT/RTLD_NEXT) searches the modules loaded for the process, not the simulator's namespace (SDL2 crashed in glibc's pthread_setname_np). - DceX11Helper::UsePulseAudio gives a DCE application the host's PulseAudio/PipeWire socket through the passthrough, its cookie and a client configuration without shared memory; HostSocketFd rewrites SCM_CREDENTIALS to the simulator's real credentials, which is what the server checks. DceX11Helper::UseSessionBus gives it the host's D-Bus session bus. - DcePcapCheck (helper/dce-pcap-check.h) reads ns-3 captures back, 802.11n A-MPDU aggregated frames included, and counts TCP/UDP packets, payload bytes and HTTP request lines, for examples that must fail as tests when their traffic is missing. - fileno() of fopen()/fdopen() streams, anonymous mmap(), F_GETFD/F_SETFD on pipes, pthread_key_delete() of a key a thread never set, scandir()'s errno, thread affinity and scheduling shims. - Entry points for libpulse, libsystemd, libdbus, libcrypto, sqlite, WAMR and SDL2 (epoll_pwait2, fstatvfs64, fpathconf, mkdirat, readlinkat, signalfd, dladdr1, dlinfo, res_query, SysV semaphores, wcs*, fseeko64/ftello64, sigtimedwait and friends; process-wide host settings such as personality, capabilities and priorities are accepted and ignored); sendfile() as a read/write loop over DCE descriptors (OpenSSL uses it) and __sysconf as an alias of sysconf. - test-eventfd: poll() woken by a write from another thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernel's virtio-net receive buffers are sized for a 1500 byte MTU, but ns-3 devices can have larger ones: the 802.11 devices' MTU is 2296. NetRx copied a received frame into the one 1518 byte buffer it was given and dropped the rest, so every full-size TCP segment on Wi-Fi was truncated and discarded by the kernel. Large downloads stalled after the first window: the receiver never acknowledged them, and the sender's retransmissions were dropped the same way. Offer mergeable receive buffers (VIRTIO_NET_F_MRG_RXBUF): LKL then hands NetRx as many buffers as a frame needs, up to 64 KB. A 2.3 MB HTTP download to Northstar over the simulated 802.11n network completes; before this change it stopped at 25 KB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All the tasks ran on the simulator's thread and so shared its thread local variables: errno, the C++ exception globals, the malloc caches and the thread local variables of the programs, where Chromium keeps the task runner of each of its threads. With the new PerFiberTls attribute (UcontextFiberManager, x86_64; false by default), every task runs on the thread control block and static TLS of a host thread of its own, parked in the kernel as soon as it has started: glibc sets its TLS up and keeps it up to date (dlopen() initializes the static TLS of new modules in every thread), and switching to the task loads its address in %fs (wrfsbase when the CPU has it). The thread control block keeps the simulator thread's pthread_self() and tid, which ns-3 and glibc's locks rely on. The TLS of a task which ends is reused, reset to the initial values of every module. Also: - FiberManager/TaskManager::GetStackBottom (): the stack of a task, for pthread_getattr_np() and __libc_stack_end; - utils/dce-fibers-gdb.py: a gdb command which prints the stack of every task, from a table of the fibers kept for debuggers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DCE loads a program like a shared object and calls its exported main(): the programs had to be built for DCE. An unmodified executable (a prebuilt Chromium) now runs too: - main(): when the program does not export it, it is the one _start hands to __libc_start_main (DceExecutableMain). - Thread local variables: an executable addresses its own at fixed offsets below the thread pointer (local-exec TLS), where the simulator's executable keeps its own. With PerFiberTls, DCE copies their initial value there, in each of the program's threads, in room the simulation's executable reserves with DCE_RESERVE_EXECUTABLE_TLS (size); the words the dynamic loader relocated (pointers) are fixed after loading. - The program's own allocator: an executable defining malloc() and friends (Chromium's PartitionAlloc) interposes them on the libraries it uses; with RTLD_DEEPBIND they used DCE's and freed blocks across allocators. DCE's malloc() and friends now forward to the program's, except for the blocks DCE allocated before it was there. - Symbol versions: a program built against an older glibc binds to pthread_create@GLIBC_2.2.5 and friends, which DCE's libraries do not define (they have the current versions only): it bound to the host's glibc. The ELF cache makes the references to the libraries DCE replaces, and to the dynamic loader, unversioned. - __libc_stack_end: the top of the main thread's stack, which V8 and the garbage collectors read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- readlink("/proc/self/exe") (and /proc/<pid>/exe) is the program's path, in the node's file system when it was found there; programs find their data files next to it.
- openat() relative to a directory ran find(1) through a shell with a command line built from a destroyed temporary, and threw when the result was empty: it is now the host's openat() on the directory's descriptor.
- fcntl(F_DUPFD, F_DUPFD_CLOEXEC): the lowest descriptor from the argument on; F_DUPFD_CLOEXEC returned a host descriptor.
- dlerror(): the error of the last dlopen()/dlsym() which failed (it was the host's, empty when DCE's loader gave up before dlopen()).
- dlopen() of a bare name searches the multiarch directories (/usr/lib/x86_64-linux-gnu: NSS loads libsoftokn3.so so).
- Signals: SIG_IGN and SIG_DFL are no longer called as handlers (SIG_DFL ends the process, but for the signals ignored by default).
- exit() no longer runs the thread specific data destructors (POSIX).
- gettimeofday() with a timezone: UTC instead of an assertion.
- Yield() once the simulation is over (the destructors of the programs' libraries run when the task managers are disposed of) does nothing.
- The ELF cache no longer asserts that the RELRO part starts the data segment: lld gives it a PT_LOAD of its own.
- open64() passed its va_list as the file creation mode.
- socket() with SOCK_NONBLOCK or SOCK_CLOEXEC failed on the ns-3 stack, and setsockopt(SO_SNDBUF) failed on its UDP sockets.
- A task woken up after its process had exited (ping -c exits from its SIGALRM handler while blocked on a socket) was run after being freed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A DCE application can now talk to a service of the host over an AF_UNIX socket. The new DceHostUnixSocketPaths global value lists socket path prefixes (e.g. /tmp/.X11-unix/): when a DCE process connects a stream socket to a matching path, connect() replaces its never-connected local socket by a HostSocketFd (model/host-socket-fd.h), a connected host socket seen as a regular DCE descriptor. The host socket stays non-blocking: blocking reads and writes are emulated by sleeping the DCE task, and the socket's readiness is checked every millisecond while a task polls it, so this works with the real-time simulator. DceX11Helper (helper/dce-x11-helper.h) enables the passthrough for the X server, installs the host's X authority cookies in the node's file system as wildcard entries (a DCE application sees its node's hostname, so host-bound entries would not match) and sets DISPLAY, XAUTHORITY and HOME. libc for the X11 client libraries and libbsd: the fortified __*_chk helpers, the glibc 2.38 __isoc23_* scanf/strto* entry points, SysV shared memory, getpw*_r/getgr*_r, getres*id, arc4random*, wctomb, reallocarray, raise, __register_atfork, __asprintf_chk and a few others. socket() accepts SOCK_NONBLOCK/SOCK_CLOEXEC for AF_UNIX sockets too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Functions which programs run by DCE call and which DCE did not have, or which went to the host with the process's virtual paths or descriptors. For ffmpeg: - the libm functions, fcntl64, readdir64, mkstemp64, getrlimit64/setrlimit64, clock, __sysv_signal and the POSIX strerror_r. For Chromium: - memfd_create, gettid (unique, the pid for the main thread), shm_open/shm_unlink (in the node's /dev/shm); syscall() emulates gettid, getpid, memfd_create and close too. - The stat ABI of programs built against glibc < 2.33 (__xstat64 & co). - symlink(at), link(at), chmod/fchmodat, fchownat (the users are not the host's: it succeeds when the file exists), renameat(2), mkfifo, mknod, lutimes, mkdtemp, mkostemp, futimens, posix_fadvise64, sendfile64, tcdrain, pthread_getname_np. - getrlimit/setrlimit: the host's, with errno set. - pthread_getattr_np(): the stack of the thread's task. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unmodified ffmpeg command line tool, run by DCE on the Linux kernel stack (or the ns-3 stack), streams an MPEG-TS over UDP from a server station through an 802.11n access point to a client station, where another ffmpeg receives it.
- Real-time mode: the received stream goes to a named pipe that a host player reads live (--player="vlc --play-and-exit - < {}"), or, with --viewer=1, the receiving ffmpeg shows it in a window on the host display (xv output device, DceX11Helper) with its sound on the host's PulseAudio server.
- Test mode (--realtime=0 --fifo=0): the example compares the elementary streams of the sent and received files and fails if they differ, and requires the UDP stream in the access point's capture (DcePcapCheck). test.py runs it so with both stacks when bin_dce/ffmpeg and bin_dce/video.ts exist.
- utils/build_kernel_deps.sh builds a minimal single-threaded PIE ffmpeg 5.1 (the last version whose command line tool works without threads), with the xv and pulse output devices when their headers are installed.
- example/dce-wifi-video-sample.ts is the 47 s clip, installed as bin_dce/video.ts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- example/x11-hello.cc, a minimal Xlib client, and example/gtk4-hello.cc, a minimal GTK4 application, are built as DCE applications when libx11-dev and libgtk-4-dev are installed. - dce-x11-hello runs one of them on a node, where it draws its window on the host display through the host socket passthrough (DceX11Helper), and fails if it cannot. --env, --args and --hostfs (the host's /usr and /etc, for fonts and toolkit data) set the program's environment. - test.py runs both when DISPLAY is set (CI runs the tests under xvfb-run). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A real web browser runs on a Wi-Fi station and draws its window on the host display (DceX11Helper); it browses a small web site that thttpd serves from another station through the 802.11n access point, or the Internet. Browsers (--browser): - dillo (default): Dillo 3.1.1 (FLTK, no JavaScript), built for DCE by utils/build_kernel_deps.sh when the FLTK headers are installed. - northstar: Northstar, a GTK4 browser with its own engine and JavaScript (QuickJS), built by utils/build_kernel_deps.sh against a minimal static libcurl when the GTK4 headers are installed. It is linked as a shared object with global-dynamic TLS and an exported main(), which DCE needs for a program built for it; it plays sound through its SDL2 mixer on the host's PulseAudio server, and talks to the host's D-Bus session bus. - chrome and brave: unmodified prebuilt Chromium (or Chrome for Testing) and Brave (with its ad blocker), found in DCE_PATH, run as one process (--single-process, no zygote, sandbox nor crash handler) with software rendering on X11 (no GPU compositing or raster, even where the display offers it), in a 1024x640 window, from /opt/chrome in the browser's file system so that they find their data files next to /proc/self/exe. The tasks get thread local storage of their own (PerFiberTls) and the example reserves the room for the browser's own thread local variables (DCE_RESERVE_EXECUTABLE_TLS). Names are resolved with getaddrinfo(), which DCE answers with the host's resolver; the browser's own DNS client would ask the host's nameserver (often systemd-resolved's 127.0.0.53) through the simulated network. YouTube streams and plays, with sound. The web site: - index.html, a 1 MB page and a JavaScript check; - video.html plays the bundled clip: as an MPEG-1 program stream for Northstar's own player (bin_dce/video.mpg, made by the DCE ffmpeg at build time), as VP9/Opus WebM for Chromium and Brave (bin_dce/video.webm, made with the system's ffmpeg when it has a VP9 encoder); - numbers-server (example/numbers-server.cc), a tiny HTTP server built as a DCE application: its page polls /next from JavaScript twice a second and shows the Fibonacci numbers the server answers. The real network: --tap=<device> bridges the access point to a host tap device (TapBridge over a CSMA link to a ghost node), so that the browser reaches the Internet through the simulated Wi-Fi; the host routes and NATs the simulated networks. utils/dce-tap-setup.sh up|down creates a persistent tap owned by the user (host 10.99.0.1, AP 10.99.0.2, stations 10.1.1.0/24), with firewalld or an nftables table of its own. The simulated clock then starts at the current time (certificates are not valid in 2010), checksums are enabled for the real frames, and --youtubeConsent preloads YouTube's "reject all" consent cookie for Northstar. Captures: the access point's Wi-Fi is captured with radiotap headers (dce-browser-1-0.pcap), and the browser's TLS keys go to dce-browser-tls-keys.log in the run directory as it writes them (through a link in its file system), so that Wireshark decrypts the capture even after an interrupted run. The example checks the traffic before exiting: at least --minRequests HTTP requests to the simulated servers, or HTTP/HTTPS payload through the access point with --tap. test.py runs the Dillo and Northstar variants (numbers page included) when their builds and an X display are available. utils/build_kernel_deps.sh fetches a pinned commit no branch has any more (Northstar's) by hash. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- build-and-run.sh: one script to create a Distrobox build container, build ns-3, the kernel-stack dependencies, LKL and DCE, run the tests, print the environment, and run the demos: "demo vlc" (dce-wifi-video, with a host player or the in-simulation viewer), "demo northstar" and "demo chrome" (dce-browser; chrome downloads a Chromium continuous build, which keeps its symbols and the messages of its CHECKs), through the tap device when it exists. - CI installs the headers the new examples build against (X11, Xv, GTK4, FLTK, SDL2, PulseAudio, codecs...) and runs the tests under xvfb-run and dbus-run-session (the X server of the graphical examples, the session bus Northstar registers on); the test output it uploads drops symbolic links too, as dce-browser links its browser's /usr and /etc to the host's. - wscript: build_a_script forwards its arguments through dce_kw, like the other DCE programs. - .gitignore: testpy-output/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- doc/source/dce-wifi-video.rst: the ffmpeg streaming example. - doc/source/dce-x11.rst: graphical applications inside DCE (how the host socket passthrough works, the X11/GTK4 examples, the browsers, sound, traffic checks, event loops, limits). - RELEASE_NOTES: the new examples, the new DCE features and the bugs fixed since dce-1.12. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ad5fb43 to
da408de
Compare
|
Now also works with Chrome (sandbox disabled) |
Add free_sized and free_aligned_sized, which GLib calls when glibc has them: without them, memory from the DCE allocator reached the glibc ones and failed with a double free. Include <termios.h>, as <termio.h> no longer exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like LTE devices, NR devices do not support promiscuous mode and only deliver received packets to non-promiscuous handlers, so the kernel of a UE received nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A walking UE browses through the 5G-LENA EPC and a tap bridge to the real network, handing over between two gNBs with the A3 algorithm and real RRC. Writes RSRP, data rate and handover traces, the traffic of the UE and the TLS keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
60 issues found across 194 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="model/kernel-socket-fd-factory.cc">
<violation number="1" location="model/kernel-socket-fd-factory.cc:486">
P0: `lkl_netdev_add` runs after `lkl_start_kernel`, but LKL requires every host netdev to be added before the kernel starts. Move netdev registration before `m_kernel->Boot()`, then perform the post-boot ioctl configuration separately.</violation>
</file>
<file name="model/utils.cc">
<violation number="1" location="model/utils.cc:241">
P1: Default signal termination calls `dce_exit()`, so it runs exit handlers, flushes stdio, and reports a normal exit code. Use the signal-specific termination path.</violation>
</file>
<file name="model/dce-signal.cc">
<violation number="1" location="model/dce-signal.cc:202">
P1: `raise()` currently kills the process with SIGKILL for every signal because `dce_kill()` hard-codes SIGKILL. Deliver the requested signal to the calling thread instead.</violation>
</file>
<file name="model/local-datagram-socket-fd.cc">
<violation number="1" location="model/local-datagram-socket-fd.cc:475">
P1: This socketpair implementation makes AF_UNIX/SOCK_DGRAM pairs unreadable: both ends are CONNECTED, but datagram reads only accept BINDED and Recvmsg rejects CONNECTED. Update the receive paths or pair state handling.</violation>
</file>
<file name="model/dce-futex.cc">
<violation number="1" location="model/dce-futex.cc:121">
P1: A thread moved by FUTEX_REQUEUE that later times out is still removed from the wrong list. `RemoveWaiter(uaddr, current)` searches only the original address, so the timeout is reported as a wake-up and the stale entry stays in the `uaddr2` queue. Track the address each waiter is currently queued on, and update it in `FutexRequeue`, so `FutexWait` removes the entry from the correct list.</violation>
<violation number="2" location="model/dce-futex.cc:182">
P2: `FUTEX_WAKE_BITSET` currently wakes all waiters on the address and ignores `val3`, allowing unrelated bitset waits to return. Store each waiter's bitset and filter wakeups by the requested mask.</violation>
<violation number="3" location="model/dce-futex.cc:216">
P1: `dce_syscall` reads six variadic arguments even for syscalls that pass fewer or none, so ordinary calls such as `syscall(SYS_getpid)` have undefined behavior. Decode only the arguments required by each syscall before dispatching.</violation>
</file>
<file name="model/dce-fd.cc">
<violation number="1" location="model/dce-fd.cc:425">
P2: `SOCK_CLOEXEC` is discarded here but never applied to the DCE descriptor. Preserve the flag and set `FD_CLOEXEC`, otherwise callers silently lose close-on-exec semantics.</violation>
<violation number="2" location="model/dce-fd.cc:561">
P2: This parser accepts an oversized AF_UNIX length and can read past `sun_path`. Reject lengths greater than `sizeof(struct sockaddr_un)` before deriving `maxLen`.</violation>
<violation number="3" location="model/dce-fd.cc:842">
P2: A negative `F_DUPFD` lower bound reaches `dce_dup2` and creates descriptor `-1`. Reject negative bounds with `EINVAL` before searching for a free descriptor.</violation>
<violation number="4" location="model/dce-fd.cc:1039">
P2: `dce_fcntl64` unconditionally reads a variadic argument, but valid `F_GETFD`/`F_GETFL` calls provide none. Handle no-argument commands without calling `va_arg`.</violation>
<violation number="5" location="model/dce-fd.cc:1065">
P2: `pipe2` accepts `O_CLOEXEC` but never sets `FD_CLOEXEC` on either pipe end. Apply the descriptor flag after creating both ends.</violation>
<violation number="6" location="model/dce-fd.cc:1088">
P1: `__readlink_chk` ignores `buflen`, allowing `dce_readlink` to write beyond a fortified caller's buffer when `bufsize > buflen`. Reject that case before forwarding the request.</violation>
<violation number="7" location="model/dce-fd.cc:1160">
P1: `__realpath_chk` drops `resolvedlen` before `dce_realpath` uses `strcpy`, so a short destination can overflow. Check the destination capacity before copying, or pass the bound through the realpath implementation.</violation>
</file>
<file name="model/dce-alloc.cc">
<violation number="1" location="model/dce-alloc.cc:33">
P1: `Allocate` can wrap the requested size before passing it to the `uint32_t` Kingsley allocator, returning an undersized block for large requests. Reject arithmetic overflow and totals above `UINT32_MAX` before the addition.</violation>
<violation number="2" location="model/dce-alloc.cc:187">
P2: `dce_posix_memalign` accepts powers of two smaller than `sizeof(void*)`, although POSIX requires `EINVAL` for those values. Validate the pointer-size multiple while keeping `dce_memalign`'s separate alignment contract.</violation>
</file>
<file name="model/libc-ns3.h">
<violation number="1" location="model/libc-ns3.h:745">
P1: These newly exported fortified wrappers bypass their destination-size checks. Validate each object-size argument before delegating, rather than exposing the unchecked compatibility implementations.</violation>
</file>
<file name="model/dce-stdio.cc">
<violation number="1" location="model/dce-stdio.cc:174">
P2: Successful exec leaves stale entries in `streamFds`, so a reused `FILE *` can return the wrong descriptor from `dce_fileno`. Erase these entries when exec closes the old streams or clear the map before creating the new image.</violation>
<violation number="2" location="model/dce-stdio.cc:807">
P1: `dce___fgets_chk` ignores the destination size and can overflow fortified callers. Reject `n > size` before calling `dce_fgets`.</violation>
<violation number="3" location="model/dce-stdio.cc:811">
P1: `dce___fread_chk` ignores `ptrlen` and can overflow fortified callers. Reject element counts whose total size exceeds `ptrlen`.</violation>
</file>
<file name="model/dce-manager.cc">
<violation number="1" location="model/dce-manager.cc:705">
P1: `Clone` returns `uint16_t`, so this `return -1` becomes 65535. `dce_fork` passes that to the guest as a pid, so `fork()` never reports -1 and the `ENOSYS` in `thread->err` is never seen. Change the return type to `int` (in dce-manager.h too) so the error can be returned.</violation>
<violation number="2" location="model/dce-manager.cc:1438">
P1: `execve` loads the new executable's allocator and TLS image into `pTemp`, but the successful replacement never transfers either field to `process`; subsequent allocations and newly-created threads therefore retain the old executable state. Copy both fields during the `Execve` state replacement and release the previous allocator.</violation>
</file>
<file name="model/epoll-fd.cc">
<violation number="1" location="model/epoll-fd.cc:147">
P1: Polling an epoll descriptor consumes `EPOLLONESHOT` entries because `EpollFd::Poll` reuses `Scan`, which disables ready entries. Keep readiness probing separate from `epoll_wait` event consumption so `poll(epfd, ...)` cannot lose one-shot events.</violation>
</file>
<file name="model/local-socket-fd.cc">
<violation number="1" location="model/local-socket-fd.cc:259">
P2: The new oversized-message path sets EMSGSIZE, but Write() overwrites it with ENOMEM. Preserve the errno from DoRecvPacket() so oversized SOCK_SEQPACKET sends report EMSGSIZE.</violation>
<violation number="2" location="model/local-socket-fd.cc:348">
P1: Plain reads consume ancillary-data offsets without discarding their SCM_RIGHTS entries. A later recvmsg can deliver an old descriptor with unrelated data; remove ancillary records when their associated bytes are consumed by any read.</violation>
<violation number="3" location="model/local-socket-fd.cc:352">
P2: Short SOCK_SEQPACKET receives discard the rest of the record but never report MSG_TRUNC. Propagate the truncation result from ReadData() and set msg->msg_flags before returning.</violation>
</file>
<file name="model/local-stream-socket-fd.cc">
<violation number="1" location="model/local-stream-socket-fd.cc:388">
P2: MSG_PEEK bypasses the socket's blocking and nonblocking receive logic. An empty blocking peek returns EOF-like 0 instead of waiting; route peeks through a receive helper that preserves those semantics.</violation>
<violation number="2" location="model/local-stream-socket-fd.cc:489">
P1: SCM_RIGHTS parsing accepts a malformed cmsg_len and can underflow before reading fd values. Validate each header's length and bounds before subtracting CMSG_LEN(0) or accessing CMSG_DATA.</violation>
<violation number="3" location="model/local-stream-socket-fd.cc:526">
P1: Sendmsg queues SCM_RIGHTS before knowing that any payload was delivered. Roll back the queued entry and its references whenever Write() fails without sending data.</violation>
</file>
<file name="model/dce-compat.cc">
<violation number="1" location="model/dce-compat.cc:57">
P2: `dce_accept4` drops `SOCK_CLOEXEC`, so accepted descriptors leak across simulated exec. Set `FD_CLOEXEC` when that flag is requested and validate unsupported flags.</violation>
<violation number="2" location="model/dce-compat.cc:341">
P2: `dce___getcwd_chk` ignores `buflen`, so a request larger than the destination object bypasses the fortified bounds check. Validate `size <= buflen` before calling `dce_getcwd`.</violation>
<violation number="3" location="model/dce-compat.cc:345">
P2: `dce___fgets_unlocked_chk` ignores the destination size, so `n > size` can write past the fortified buffer. Enforce the object-size check before calling `dce_fgets`.</violation>
<violation number="4" location="model/dce-compat.cc:877">
P1: A simulated application can change the simulator's host-wide resource limits through `dce_setrlimit`, affecting other DCE applications and the simulator. Keep limits per simulated process or reject the setter instead of calling the host `setrlimit`.</violation>
</file>
<file name="model/libc.cc">
<violation number="1" location="model/libc.cc:38">
P1: The x86 forwarding path puts extended asm with a memory operand inside a naked function, which GCC does not support reliably. Use a basic-asm/out-of-line assembly trampoline or retain the supported forwarding implementation.</violation>
</file>
<file name="test/test-eventfd.cc">
<violation number="1" location="test/test-eventfd.cc:100">
P2: This infinite wait can hang the entire test suite when the wake-up path regresses. Use a finite timeout (the following check already uses 5 seconds) while still asserting that the writer wakes the poll.</violation>
</file>
<file name="model/dce-pthread.cc">
<violation number="1" location="model/dce-pthread.cc:461">
P2: A terminated joinable thread remains searchable with `task == 0`, but this line passes that null task to `GetStackBottom()` and can crash the simulator. Guard `target->task` before querying its stack.</violation>
</file>
<file name="model/dce-dl.cc">
<violation number="1" location="model/dce-dl.cc:55">
P2: The fallback error path dereferences `filename` even when `dlopen(NULL, ...)` selected `exeFullPath`. Use the selected load name (`filename != 0 ? filename : exeFullPath.c_str()`) when constructing the message.</violation>
<violation number="2" location="model/dce-dl.cc:81">
P2: `dlerror()` stores its pending error on `Process`, so DCE threads can consume one another’s lookup errors. Store the error string and pending flag per `Thread` instead.</violation>
</file>
<file name="build-and-run.sh">
<violation number="1" location="build-and-run.sh:8">
P2: The script only works when launched from the repository root because it uses the caller's working directory as `DCE_DIR`. Derive `DCE_DIR` from the script location so `/path/to/build-and-run.sh build` works from any directory.</violation>
<violation number="2" location="build-and-run.sh:85">
P2: The `env` command is re-entered through distrobox, and this banner goes to stdout. That output is meant to be sourced, so the banner breaks `eval "$(./build-and-run.sh env)"`. Send the banner to stderr.</violation>
<violation number="3" location="build-and-run.sh:126">
P2: `setup-container` omits `unzip`, but the Chrome demo executes `unzip` to extract its download. Add `unzip` to the container package list.</violation>
</file>
<file name="utils/dce-fibers-gdb.py">
<violation number="1" location="utils/dce-fibers-gdb.py:21">
P1: This saves the registers of the currently selected frame, not the innermost frame. If the user ran `up` before `dce-fibers`, the restore at the end writes the caller's rsp/rip/rbp into the real registers. Use `gdb.newest_frame()` so the saved values are the real ones.</violation>
</file>
<file name="model/dce.cc">
<violation number="1" location="model/dce.cc:465">
P2: `ResetGetoptIfOtherProcess` leaves glibc's hidden getopt state untouched when a process resumes with `optind > 1`, so it can inherit the previous process's cluster or permutation state. Preserve the complete parser state per process or use a reentrant/per-process getopt implementation instead of resetting only at the start of argv.</violation>
<violation number="2" location="model/dce.cc:1014">
P2: The new `/proc/self/exe` handling reads `exePath`, but that field is not propagated on exec or fork. After `execv`, `Execve` never copies `pTemp.exePath` into `process->exePath`, so readlink returns the old program's path. A forked child also starts with an empty `exePath` because `Clone` does not copy it, so the branch is skipped. Set `process->exePath = pTemp.exePath;` in `Execve` and copy `exePath` in `Clone`.</violation>
</file>
<file name="model/local-socket-fd-factory.cc">
<violation number="1" location="model/local-socket-fd-factory.cc:77">
P2: The new SOCK_SEQPACKET sockets report SOCK_STREAM from SO_TYPE. Preserve the requested socket type in LocalStreamSocketFd and return SOCK_SEQPACKET for these descriptors.</violation>
<violation number="2" location="model/local-socket-fd-factory.cc:80">
P2: Enabling SOCK_SEQPACKET exposes a zero-length-record deadlock: a zero-sized front buffer is never consumed, blocking later messages. Handle zero-length records explicitly in enqueue and receive logic.</violation>
<violation number="3" location="model/local-socket-fd-factory.cc:80">
P2: The SOCK_SEQPACKET socket only keeps message boundaries on the socket created by `socket()`. `LocalStreamSocketFd::Accept` builds the accepted socket with the peer constructor, which never copies `m_messages`, so connections accepted from a SEQPACKET listener behave as plain streams. Propagate the message-boundary flag to the accepted socket in `Accept`, for example by setting `socket->KeepMessageBoundaries ()` when the listener has it set.</violation>
</file>
<file name="model/unix-datagram-socket-fd.cc">
<violation number="1" location="model/unix-datagram-socket-fd.cc:204">
P2: `MSG_PEEK` bypasses `ICMP6_FILTER`, so callers can observe a blocked ICMPv6 type with `recvmsg`. Apply the filter before exposing peeked packets without consuming the accepted packet.</violation>
</file>
<file name="utils/dce-tap-setup.sh">
<violation number="1" location="utils/dce-tap-setup.sh:79">
P2: `down` unconditionally removes masquerading from firewalld’s default zone, including a rule that existed before this script ran. Track whether `up` added it or isolate the demo in its own firewalld zone.</violation>
</file>
<file name="model/ucontext-fiber-manager.cc">
<violation number="1" location="model/ucontext-fiber-manager.cc:198">
P2: Each newly allocated fiber TLS permanently consumes a detached host thread and stack because `ParkedThread` cannot exit and `Delete` never joins it. Reclaim or explicitly cap these helper threads so simulations with many concurrent tasks cannot exhaust host thread or memory limits.</violation>
</file>
<file name="model/dce-epoll.cc">
<violation number="1" location="model/dce-epoll.cc:103">
P2: `dce_epoll_pwait` ignores its temporary signal mask, and `dce_epoll_pwait2` does the same. Apply the mask for the duration of the wait and restore the previous mask on every return path.</violation>
</file>
<file name="doc/source/getting-started.rst">
<violation number="1" location="doc/source/getting-started.rst:233">
P2: Copying `liblkl.so` into `build/bin_dce` is not enough: LKL is located only through the `DCE_PATH` environment variable. Export `DCE_PATH` to include `build/bin_dce` after the copy, as the bake sections already do.</violation>
</file>
<file name="example/numbers-server.cc">
<violation number="1" location="example/numbers-server.cc:94">
P2: The browser polls `/next` twice per second, but this 64-bit Fibonacci state wraps after about 94 requests. Use arbitrary-precision values or explicitly reset/label the series before the default 600-second demo stops.</violation>
</file>
<file name="model/cooja-loader-factory.cc">
<violation number="1" location="model/cooja-loader-factory.cc:385">
P2: `RTLD_NEXT` is not equivalent to `RTLD_DEFAULT`; this branch searches `m_modules` from the beginning for both. Preserve next-object lookup semantics instead of returning the first matching DCE symbol.</violation>
</file>
<file name="example/dce-wifi-video.cc">
<violation number="1" location="example/dce-wifi-video.cc:557">
P2: `waitpid (playerPid, ...)` blocks indefinitely if the player never gets a writer. The player's shell sits in `open()` on the FIFO when ffmpeg exits before it opens the output. Stop the player with a signal after the simulation ends, or poll with `WNOHANG` and a deadline.</violation>
</file>
<file name="utils/build_lkl.sh">
<violation number="1" location="utils/build_lkl.sh:74">
P2: `rm -rf "${OUT_DIR}/include"` will delete the kernel source's top-level `include/` if `out_dir` equals the LKL tree. Reject that case before the removal.</violation>
</file>
<file name="model/host-socket-fd.cc">
<violation number="1" location="model/host-socket-fd.cc:160">
P2: `CheckReadiness` reschedules itself every millisecond with no exit other than `m_closed`. A socket that is polled and never closed keeps the event queue non-empty, so the simulation cannot end on its own. Consider rescheduling only while a poller is registered, or stopping the check when no waiters remain.</violation>
</file>
<file name="model/unix-socket-fd.cc">
<violation number="1" location="model/unix-socket-fd.cc:309">
P2: `SO_SNDBUF` now succeeds even when the socket has no `SndBufSize` attribute, but `getsockopt(SO_SNDBUF)` still returns `EINVAL` and the requested value is lost. Store a per-socket value or implement matching get/set behavior.</violation>
<violation number="2" location="model/unix-socket-fd.cc:561">
P2: `getsockopt(IPPROTO_ICMPV6, ICMP6_FILTER)` returns success on non-raw sockets, while `setsockopt` rejects those sockets. Add the same raw-socket validation to the get path.</violation>
</file>
<file name="model/epoll-fd.h">
<violation number="1" location="model/epoll-fd.h:73">
P2: Registrations are keyed by fd number and only purged lazily in Scan(), so closing an fd without EPOLL_CTL_DEL and reusing its number leaves a stale entry. Re-adding that number then returns EEXIST, and a wait reports the old user data for the new file. Key entries by the underlying open file (not the fd number), or purge them when the last reference to the file is closed.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
|
|
||
| typedef int (*NetdevAdd)(struct lkl_netdev *, struct lkl_netdev_args *); | ||
| NetdevAdd netdevAdd = (NetdevAdd) m_kernel->Lookup ("lkl_netdev_add"); | ||
| int id = netdevAdd (&dev->nd, &args); |
There was a problem hiding this comment.
P0: lkl_netdev_add runs after lkl_start_kernel, but LKL requires every host netdev to be added before the kernel starts. Move netdev registration before m_kernel->Boot(), then perform the post-boot ioctl configuration separately.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/kernel-socket-fd-factory.cc, line 486:
<comment>`lkl_netdev_add` runs after `lkl_start_kernel`, but LKL requires every host netdev to be added before the kernel starts. Move netdev registration before `m_kernel->Boot()`, then perform the post-boot ioctl configuration separately.</comment>
<file context>
@@ -1,964 +1,653 @@
+
+ typedef int (*NetdevAdd)(struct lkl_netdev *, struct lkl_netdev_args *);
+ NetdevAdd netdevAdd = (NetdevAdd) m_kernel->Lookup ("lkl_netdev_add");
+ int id = netdevAdd (&dev->nd, &args);
+ NS_ASSERT_MSG (id >= 0, "lkl_netdev_add failed: " << id);
+
</file context>
| default: | ||
| // the default action of the others ends the process | ||
| NS_LOG_DEBUG ("signal " << handler.signal << " ends the process"); | ||
| dce_exit (128 + handler.signal); |
There was a problem hiding this comment.
P1: Default signal termination calls dce_exit(), so it runs exit handlers, flushes stdio, and reports a normal exit code. Use the signal-specific termination path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/utils.cc, line 241:
<comment>Default signal termination calls `dce_exit()`, so it runs exit handlers, flushes stdio, and reports a normal exit code. Use the signal-specific termination path.</comment>
<file context>
@@ -214,6 +220,42 @@ UtilsSendSignal (Process *process, int signum)
+ default:
+ // the default action of the others ends the process
+ NS_LOG_DEBUG ("signal " << handler.signal << " ends the process");
+ dce_exit (128 + handler.signal);
+ return;
+ }
</file context>
| dce_exit (128 + handler.signal); | |
| dce_exit_signal (handler.signal); |
| { | ||
| Thread *current = Current (); | ||
| NS_ASSERT (current != 0); | ||
| return dce_kill (current->process->pid, sig); |
There was a problem hiding this comment.
P1: raise() currently kills the process with SIGKILL for every signal because dce_kill() hard-codes SIGKILL. Deliver the requested signal to the calling thread instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/dce-signal.cc, line 202:
<comment>`raise()` currently kills the process with SIGKILL for every signal because `dce_kill()` hard-codes SIGKILL. Deliver the requested signal to the calling thread instead.</comment>
<file context>
@@ -165,3 +186,25 @@ int dce_sigprocmask (int how, const sigset_t *set, sigset_t *oldset)
+{
+ Thread *current = Current ();
+ NS_ASSERT (current != 0);
+ return dce_kill (current->process->pid, sig);
+}
+
</file context>
| return dce_kill (current->process->pid, sig); | |
| sigaddset (¤t->pendingSignals, sig); | |
| UtilsDoSignal (); | |
| return 0; |
| void | ||
| LocalDatagramSocketFd::Pair (LocalDatagramSocketFd *a, LocalDatagramSocketFd *b) | ||
| { | ||
| a->m_state = CONNECTED; |
There was a problem hiding this comment.
P1: This socketpair implementation makes AF_UNIX/SOCK_DGRAM pairs unreadable: both ends are CONNECTED, but datagram reads only accept BINDED and Recvmsg rejects CONNECTED. Update the receive paths or pair state handling.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/local-datagram-socket-fd.cc, line 475:
<comment>This socketpair implementation makes AF_UNIX/SOCK_DGRAM pairs unreadable: both ends are CONNECTED, but datagram reads only accept BINDED and Recvmsg rejects CONNECTED. Update the receive paths or pair state handling.</comment>
<file context>
@@ -469,6 +469,17 @@ LocalDatagramSocketFd::Bind (const struct sockaddr *my_addr, socklen_t addrlen)
+void
+LocalDatagramSocketFd::Pair (LocalDatagramSocketFd *a, LocalDatagramSocketFd *b)
+{
+ a->m_state = CONNECTED;
+ a->m_peer = b;
+ b->AddConnected (a);
</file context>
| va_list ap; | ||
| va_start (ap, number); | ||
| long a[6]; | ||
| for (int i = 0; i < 6; i++) |
There was a problem hiding this comment.
P1: dce_syscall reads six variadic arguments even for syscalls that pass fewer or none, so ordinary calls such as syscall(SYS_getpid) have undefined behavior. Decode only the arguments required by each syscall before dispatching.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/dce-futex.cc, line 216:
<comment>`dce_syscall` reads six variadic arguments even for syscalls that pass fewer or none, so ordinary calls such as `syscall(SYS_getpid)` have undefined behavior. Decode only the arguments required by each syscall before dispatching.</comment>
<file context>
@@ -0,0 +1,246 @@
+ va_list ap;
+ va_start (ap, number);
+ long a[6];
+ for (int i = 0; i < 6; i++)
+ {
+ a[i] = va_arg (ap, long);
</file context>
| ) | ||
|
|
||
| # The headers DCE is built with (some are generated by the build). | ||
| rm -rf "${OUT_DIR}/include" |
There was a problem hiding this comment.
P2: rm -rf "${OUT_DIR}/include" will delete the kernel source's top-level include/ if out_dir equals the LKL tree. Reject that case before the removal.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At utils/build_lkl.sh, line 74:
<comment>`rm -rf "${OUT_DIR}/include"` will delete the kernel source's top-level `include/` if `out_dir` equals the LKL tree. Reject that case before the removal.</comment>
<file context>
@@ -0,0 +1,78 @@
+)
+
+# The headers DCE is built with (some are generated by the build).
+rm -rf "${OUT_DIR}/include"
+cp -a "${LKL_DIR}/tools/lkl/include" "${OUT_DIR}/include"
+
</file context>
| rm -rf "${OUT_DIR}/include" | |
| if [ "${OUT_DIR}" = "${LKL_DIR}" ]; then | |
| echo "out_dir must not be the LKL source tree" >&2 | |
| exit 1 | |
| fi | |
| rm -rf "${OUT_DIR}/include" |
| } | ||
| // The pollers cannot be observed from here, so keep checking until the | ||
| // socket is closed: one event per millisecond. | ||
| m_check = Simulator::Schedule (MilliSeconds (1), &HostSocketFd::CheckReadiness, this); |
There was a problem hiding this comment.
P2: CheckReadiness reschedules itself every millisecond with no exit other than m_closed. A socket that is polled and never closed keeps the event queue non-empty, so the simulation cannot end on its own. Consider rescheduling only while a poller is registered, or stopping the check when no waiters remain.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/host-socket-fd.cc, line 160:
<comment>`CheckReadiness` reschedules itself every millisecond with no exit other than `m_closed`. A socket that is polled and never closed keeps the event queue non-empty, so the simulation cannot end on its own. Consider rescheduling only while a poller is registered, or stopping the check when no waiters remain.</comment>
<file context>
@@ -0,0 +1,378 @@
+ }
+ // The pollers cannot be observed from here, so keep checking until the
+ // socket is closed: one event per millisecond.
+ m_check = Simulator::Schedule (MilliSeconds (1), &HostSocketFd::CheckReadiness, this);
+}
+
</file context>
| switch (level) | ||
| { | ||
| case IPPROTO_ICMPV6: | ||
| if (optname != ICMP6_FILTER || *optlen < (socklen_t) sizeof (struct icmp6_filter)) |
There was a problem hiding this comment.
P2: getsockopt(IPPROTO_ICMPV6, ICMP6_FILTER) returns success on non-raw sockets, while setsockopt rejects those sockets. Add the same raw-socket validation to the get path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/unix-socket-fd.cc, line 561:
<comment>`getsockopt(IPPROTO_ICMPV6, ICMP6_FILTER)` returns success on non-raw sockets, while `setsockopt` rejects those sockets. Add the same raw-socket validation to the get path.</comment>
<file context>
@@ -483,6 +557,22 @@ UnixSocketFd::Getsockopt (int level, int optname,
switch (level)
{
+ case IPPROTO_ICMPV6:
+ if (optname != ICMP6_FILTER || *optlen < (socklen_t) sizeof (struct icmp6_filter))
+ {
+ current->err = optname != ICMP6_FILTER ? ENOPROTOOPT : EINVAL;
</file context>
| if (optname != ICMP6_FILTER || *optlen < (socklen_t) sizeof (struct icmp6_filter)) | |
| if (m_socket->GetInstanceTypeId ().GetName () != "ns3::Ipv6RawSocketImpl") | |
| { | |
| current->err = ENOPROTOOPT; | |
| return -1; | |
| } | |
| if (optname != ICMP6_FILTER || *optlen < (socklen_t) sizeof (struct icmp6_filter)) |
| } | ||
| // Only ns-3 TCP sockets have a send buffer attribute; like the | ||
| // kernel, accept the option on the other socket types. | ||
| m_socket->SetAttributeFailSafe ("SndBufSize", UintegerValue (*val)); |
There was a problem hiding this comment.
P2: SO_SNDBUF now succeeds even when the socket has no SndBufSize attribute, but getsockopt(SO_SNDBUF) still returns EINVAL and the requested value is lost. Store a per-socket value or implement matching get/set behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/unix-socket-fd.cc, line 309:
<comment>`SO_SNDBUF` now succeeds even when the socket has no `SndBufSize` attribute, but `getsockopt(SO_SNDBUF)` still returns `EINVAL` and the requested value is lost. Store a per-socket value or implement matching get/set behavior.</comment>
<file context>
@@ -300,11 +304,9 @@ UnixSocketFd::Setsockopt (int level, int optname,
- }
+ // Only ns-3 TCP sockets have a send buffer attribute; like the
+ // kernel, accept the option on the other socket types.
+ m_socket->SetAttributeFailSafe ("SndBufSize", UintegerValue (*val));
} break;
case SO_RCVBUF:
</file context>
| int Scan (struct epoll_event *events, int maxevents, PollTable *table, | ||
| std::map<int, FileUsage *> &referenced); | ||
|
|
||
| std::map<int, Entry> m_entries; |
There was a problem hiding this comment.
P2: Registrations are keyed by fd number and only purged lazily in Scan(), so closing an fd without EPOLL_CTL_DEL and reusing its number leaves a stale entry. Re-adding that number then returns EEXIST, and a wait reports the old user data for the new file. Key entries by the underlying open file (not the fd number), or purge them when the last reference to the file is closed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At model/epoll-fd.h, line 73:
<comment>Registrations are keyed by fd number and only purged lazily in Scan(), so closing an fd without EPOLL_CTL_DEL and reusing its number leaves a stale entry. Re-adding that number then returns EEXIST, and a wait reports the old user data for the new file. Key entries by the underlying open file (not the fd number), or purge them when the last reference to the file is closed.</comment>
<file context>
@@ -0,0 +1,78 @@
+ int Scan (struct epoll_event *events, int maxevents, PollTable *table,
+ std::map<int, FileUsage *> &referenced);
+
+ std::map<int, Entry> m_entries;
+};
+
</file context>
VLC, NorthStar web browser, GTK4 patches to DCE.
Plus X11 and PulseAudio forwarding, so most GUI applications can run with audio too.
Requires DCE patch #150
Requires NorthStar patch nordstjernen-web/northstar-web-browser#17
Even better with extra streaming patches for NorthStar nordstjernen-web/northstar-web-browser#18