Skip to content

Add support for WiFi - #152

Open
Gabrielcarvfer wants to merge 71 commits into
direct-code-execution:masterfrom
Gabrielcarvfer:lkl-wifi
Open

Gabrielcarvfer wants to merge 71 commits into
direct-code-execution:masterfrom
Gabrielcarvfer:lkl-wifi

Conversation

@Gabrielcarvfer

@Gabrielcarvfer Gabrielcarvfer commented Oct 27, 2026 •

Copy link
Copy Markdown

Allowed conformance testing of ns-3 mesh


Summary by cubic

Adds Linux WiFi support to DCE: kernel (LKL) nodes can now run mac80211 radios on ns-3 WiFi PHYs, so Linux 802.11s mesh points and ns-3 mesh points can share one channel and be compared frame by frame. Along the way, the deprecated libos (Linux 4.4) and FreeBSD kernel stacks are removed in favor of the Linux Kernel Library (LKL, Linux 6.12), and DCE grows real applications inside the simulation.

New Features

  • LklWifiDevice and LklWifiHelper give kernel nodes a mac80211 radio on an ns-3 WifiPhy with legacy, HT, VHT and HE single user rates, A-MPDU aggregation and Block Acks; the radio passes control frames and ACK SNR to the kernel and no longer stalls simulated time on pending interrupts.
  • dce-lkl-mesh runs Linux and ns-3 dot11s mesh points on one channel with pcap outputs; dce-lkl-wifi-litmus exercises ns-3 wifi behaviors against Linux; dce-lkl-boot and dce-lkl-p2p cover kernel boot and TCP/UDP between kernels.
  • Real applications run inside the simulation: dce-wifi-video streams real ffmpeg video over 802.11n, and dce-x11-hello, dce-browser and dce-browser-nr run X11/GTK4 clients and unmodified Chromium/Brave in windows on the host display.
  • New DCE support for those: AF_UNIX host socket passthrough, per-task thread-local storage, loading unmodified PIE executables, eventfd/epoll/futex, local socket SCM_RIGHTS, and crash handling that ends only the offending process.

Migration

  • ns3::LinuxSocketFdFactory now means LKL; --with-lkl replaces --enable-kernel-stack.
  • FreeBSD stack, libos kernel, and their examples, helpers, and documentation are removed.
  • utils/build_lkl.sh builds LKL (pinned Linux 6.12 commit plus patches); utils/build_kernel_deps.sh builds the kernel programs, iw, and the extra example programs.

Written for commit 37fdd1d. Summary will update on new commits.

View guided diff

Gabrielcarvfer and others added 30 commits October 7, 2026 17:14
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>
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>
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>
The interface selected with IPV6_PKTINFO was ignored when sending on a connected socket. Like Linux, sendmsg now fails with ENODEV for an interface that does not exist, instead of using the default route, and with EINVAL for a control message longer than the control buffer, instead of reading past it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The option was accepted and ignored, so the socket received the ICMPv6 types it blocked. ns-3 does not expose its raw socket's filter: DCE keeps it, drops the blocked messages when receiving, and returns it from getsockopt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a send failed with EAGAIN, the poll loop notified the application again only once time went on, and then waited for the socket to wake: when the kernel had already reported the socket writable at the same time, nothing woke it, and the application stopped sending (DCCP bulk transfers ran at a fourth of their rate). Wait at most 1 ms then.

dce-tcp-ns3-nsc-comparison now fails if a flow stalls or the flows use little of the bottleneck.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each node loads a copy of liblkl.so from elf-cache, where gdb did not find the liblkl.so.debug its debug link names: link it next to the copy when it is next to the library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gabrielcarvfer and others added 12 commits October 23, 2026 19:30
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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

40 issues found across 112 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="utils/build_kernel_deps.sh">

<violation number="1" location="utils/build_kernel_deps.sh:27">
P2: Git tags are mutable, and `fetch_git` does not verify a commit hash for these sources. Pin each git source to a commit SHA, as `QUAGGA_MODULE_REV` already does, so the build is reproducible and the source is verified.</violation>
</file>

<file name="example/dce-iperf.cc">

<violation number="1" location="example/dce-iperf.cc:66">
P2: Removing the freebsd branch means `--stack=freebsd` silently skips stack setup and falls through to address assignment on nodes with no IP stack. Consider rejecting unknown stack names explicitly, e.g. `NS_ABORT_MSG` or `NS_LOG_ERROR` plus `return 0` in an `else` branch.</violation>

<violation number="2" location="example/dce-iperf.cc:66">
P3: `dce-iperf` now uses the LKL build flag, but its fallback still tells users to build the removed dce-linux module. Report the required `--with-lkl` configuration instead.</violation>
</file>

<file name="utils/lkl-patches/0004-lkl-show_stack-stop-at-the-first-frame-outside-of-th.patch">

<violation number="1" location="utils/lkl-patches/0004-lkl-show_stack-stop-at-the-first-frame-outside-of-th.patch:35">
P2: This `arch/lkl` fix to `show_stack()` ships without a focused test. Add a test that exercises a walk ending at a non-kernel return address and at a non-increasing `next_frame`, so a regression is caught.</violation>
</file>

<file name="model/linux/linux-socket-impl.cc">

<violation number="1" location="model/linux/linux-socket-impl.cc:362">
P2: `m_sendBlocked` is updated only by `Send`, but `SendTo` uses `Sendmsg` and leaves the flag clear on EAGAIN. Track EAGAIN from `SendTo` too, otherwise unwritable datagram or raw sockets can spin on zero-delay `NotifySend` callbacks.</violation>

<violation number="2" location="model/linux/linux-socket-impl.cc:643">
P3: The FIONREAD ioctl runs on every POLLIN event, but `pending` only matters when a hangup or error bit is set. Move the ioctl inside that condition so ordinary data notifications skip the extra fake-task round trip and syscall.</violation>
</file>

<file name="test/test-signal.cc">

<violation number="1" location="test/test-signal.cc:40">
P3: `g_sent` is shared between threads through a plain `volatile bool`, which is a data race under the C++ memory model. Use `std::atomic<bool>` (or `__atomic` builtins) for the flag, or drop the flag and rely on `pthread_join`.</violation>
</file>

<file name="doc/source/dce-user-kernel.rst">

<violation number="1" location="doc/source/dce-user-kernel.rst:14">
P2: This overstates the naming rule. Loopback devices get no kernel device, and `LklWifiDevice` radios become `wlan<n>`, not `sim<n>`. Qualify the sentence, e.g. 'every ns-3 NetDevice except loopback becomes a kernel device: `sim<n>`, or `wlan<n>` for an `LklWifiDevice`'.</violation>

<violation number="2" location="doc/source/dce-user-kernel.rst:150">
P3: `Run` is not a public DCE API and this page does not define it. Replace these calls with `DceApplicationHelper` or define the helper so the Wi-Fi example is copyable.</violation>
</file>

<file name="example/dce-iperf-emulation.cc">

<violation number="1" location="example/dce-iperf-emulation.cc:84">
P2: Removing the `freebsd` branch leaves no `else` for unrecognized stack names. `--stack=freebsd` now skips stack setup and runs the DCE iperf apps on nodes with no DceManager, so it fails later with no clear error. Add an `else` that calls `NS_FATAL_ERROR` on an unknown stack name.</violation>
</file>

<file name="model/lkl/lkl-kernel.cc">

<violation number="1" location="model/lkl/lkl-kernel.cc:490">
P2: The `stat()` call on the source file is inside `NS_ASSERT_MSG`, so it is skipped in builds where assertions are compiled out. `src` is then read uninitialized in the freshness check. Move the call out of the assertion and assert only on its result.</violation>
</file>

<file name="model/unix-datagram-socket-fd.cc">

<violation number="1" location="model/unix-datagram-socket-fd.cc:205">
P2: `MSG_PEEK` bypasses `ICMP6_FILTER`, so a blocked ICMPv6 type is still returned to callers that peek. Apply the filter to peeking reads without repeatedly returning the same blocked packet.</violation>

<violation number="2" location="model/unix-datagram-socket-fd.cc:255">
P3: After this change `ipv4` is never used in this block. Remove the `Ptr<Ipv4> ipv4 = node->GetObject<Ipv4> ();` declaration above, since the interface index now comes straight from the tag.</violation>
</file>

<file name="model/lkl/lkl-kernel.h">

<violation number="1" location="model/lkl/lkl-kernel.h:55">
P3: `GetTlsKeys()` and `GetPages()` expose the internal containers by mutable reference, but they are only used for insert, erase and lookup inside lkl-kernel.cc. Replace them with narrow methods such as AddTlsKey/RemoveTlsKey and AddPage/RemovePage, or keep the containers private.</violation>
</file>

<file name="test/ipv4-dce-routing-test.cc">

<violation number="1" location="test/ipv4-dce-routing-test.cc:34">
P3: The `Local` callback ignores its `iif` argument, so the test never checks the interface reported for local multicast delivery. Record `iif` and assert it equals `inIf` so the new `lcb` call is verified end to end.</violation>
</file>

<file name=".github/workflows/build-and-test.yml">

<violation number="1" location=".github/workflows/build-and-test.yml:50">
P2: The new apt-get list still lacks `patch`, `autoconf`, `automake` and `libtool`. `build_kernel_deps.sh` calls `patch` to apply the iperf/iputils patches and runs `autogen.sh` for libnl, which needs those autotools. Add them to the install list, or the kernel-deps step will fail on a cache miss.</violation>
</file>

<file name="helper/lkl-wifi-helper.cc">

<violation number="1" location="helper/lkl-wifi-helper.cc:80">
P3: `AssignStreams` silently skips non-LKL devices, while `EnablePcap` aborts on them. Use the same abort here so a wrong device container fails loudly instead of leaving streams unassigned.</violation>
</file>

<file name="utils/lkl-patches/0005-lkl-update-jiffies-on-interrupts-while-the-tick-is-s.patch">

<violation number="1" location="utils/lkl-patches/0005-lkl-update-jiffies-on-interrupts-while-the-tick-is-s.patch:4">
P2: This `arch/lkl` fix has no upstream tracking. Add a `TOUPSTREAM` line to the commit message, as patches 0003 and 0004 do. Also add a focused test that checks timers armed from interrupts during a stopped tick do not expire early.

(Based on your team's feedback about upstreaming LKL fixes with tests.)</violation>
</file>

<file name="model/kernel-socket-fd.h">

<violation number="1" location="model/kernel-socket-fd.h:14">
P3: The class comment overstates what `KernelSocketFd` does. Several operations are emulated in DCE or return errors without making a kernel call, and the blocking variants wait in DCE. Reword it to say most operations map to kernel system calls and that some are emulated.</violation>
</file>

<file name="example/dce-lkl-mesh.cc">

<violation number="1" location="example/dce-lkl-mesh.cc:45">
P2: This example cannot fail its conformance run when `iw`, `ip`, or `ping` exits nonzero: `Finished` only logs the status and `main` always returns 0. Track nonzero child statuses and return failure.</violation>

<violation number="2" location="example/dce-lkl-mesh.cc:168">
P2: The ping start times are hard-coded at 5 + 6*i seconds, but `stop` defaults to 30 s. With more than 4 nodes, the later pings start late or after the simulation has stopped, so they are silently cut off or never run. Derive the stop time from the node count, or abort/warn when the schedule does not fit in `stop`.</violation>

<violation number="3" location="example/dce-lkl-mesh.cc:168">
P2: `--linux=0` is accepted but this loop unconditionally accesses `linuxNodes.Get (0)`, so the example aborts instead of handling the invalid topology. Reject zero Linux nodes after parsing.</violation>
</file>

<file name="test/dce-manager-test.cc">

<violation number="1" location="test/dce-manager-test.cc:279">
P1: The linux-stack gate now searches for `liblkl.so`, but `DoRun` still loads `liblinux.so` for the same `LinuxSocketFdFactory`. When only `liblkl.so` is present, the gate passes and every linux-stack test then fails to load its library. Change the `Library` value in the linux branch of `DoRun` to `liblkl.so` as well.</violation>
</file>

<file name="example/dce-mptcp-handoff-v6.cc">

<violation number="1" location="example/dce-mptcp-handoff-v6.cc:70">
P3: The per-address counter includes TCP/MPTCP headers and pure ACKs, not just payload. Either rename the output to reflect L4 bytes, or count only the TCP payload if the intent is goodput.</violation>
</file>

<file name="model/kernel-socket-fd-factory.cc">

<violation number="1" location="model/kernel-socket-fd-factory.cc:582">
P2: For non-Mac48 addresses, `RxFromDevice` no longer converts Mac64Address the way the previous code did. Broadcast destinations now become the kernel device's own MAC, and the source becomes all zeros. Restrict the fallback to LTE devices, or keep the Mac64-to-Mac48 conversion for other device types.</violation>
</file>

<file name="model/linux/linux-socket-impl.h">

<violation number="1" location="model/linux/linux-socket-impl.h:88">
P2: This header change leaves the checked-in Python binding definitions stale. `bindings/modulegen__gcc_LP64.py` and `bindings/modulegen__gcc_ILP32.py` still declare `m_kernsock` as `ns3::KernelSocketFd *`. Regenerate the bindings, or update those two `add_instance_attribute` lines to `ns3::UnixFd *`.</violation>

<violation number="2" location="model/linux/linux-socket-impl.h:120">
P3: The comment is inaccurate: the flag stays set after a later successful send and is only cleared by `Poll()` on a POLLOUT notification. Reword it to say it records an EAGAIN not yet followed by a writable notification.</violation>
</file>

<file name="example/dce-lkl-p2p.cc">

<violation number="1" location="example/dce-lkl-p2p.cc:41">
P3: This comment is inaccurate. `tcp-server` reads a fixed 100 x 10240 bytes and ignores EOF, so say that instead of "until the end of the connection".</violation>

<violation number="2" location="example/dce-lkl-p2p.cc:124">
P3: With `--loopback --stop`, the UDP server and client still run on the single node. The non-stop branch skips UDP when `loopback` is set, so guard the stop branch's UDP calls the same way.</violation>

<violation number="3" location="example/dce-lkl-p2p.cc:125">
P3: The header says every program must exit with status 0, but the `--stop` clients are not checked. Pass `check` (drop the `false`) so the clients are counted, or correct the header.</violation>
</file>

<file name="model/dce-signal.cc">

<violation number="1" location="model/dce-signal.cc:134">
P2: The new `sigwaitSet` wait only works if the sender wakes this thread. `UtilsSendSignal` in `model/utils.cc` returns after waking the first eligible blocked thread, so a sigwait-ing thread later in `process->threads` can be skipped and hang. Prefer threads with the signal in `sigwaitSet` in a first pass before the existing candidate check.</violation>
</file>

<file name="model/unix-socket-fd.cc">

<violation number="1" location="model/unix-socket-fd.cc:413">
P2: The new IPV6_JOIN_GROUP/IPV6_LEAVE_GROUP handling has no test. Add a test that joins and leaves a group on a valid interface, on interface 0, and on an out-of-range interface index.</violation>

<violation number="2" location="model/unix-socket-fd.cc:432">
P2: `ipv6mr_interface == 0` is treated as "all interfaces", but Linux resolves 0 by route lookup and returns ENODEV when no route exists. Resolve the interface the way Linux does, or return ENODEV for 0. Correct the "0 means all interfaces" comment either way.</violation>

<violation number="3" location="model/unix-socket-fd.cc:452">
P2: Joined groups are never tracked per socket, so they are not dropped when the socket is closed. Linux drops a socket's memberships on close. Record the joins per socket and remove them in `ClearSocket()` or the destructor.</violation>

<violation number="4" location="model/unix-socket-fd.cc:563">
P3: The getsockopt path does not check the socket type, unlike setsockopt. On a non-raw socket it returns a PASSALL filter instead of ENOPROTOOPT. Add the same `GetInstanceTypeId` check here.</violation>
</file>

<file name="example/dce-iperf-heterogeneous-multihop.cc">

<violation number="1" location="example/dce-iperf-heterogeneous-multihop.cc:165">
P3: `dce-iperf-heterogeneous-multihop` now uses the LKL build flag, but its fallback still directs users to the removed dce-linux module. Report the required `--with-lkl` configuration instead.</violation>
</file>

<file name="model/kernel-socket-fd.cc">

<violation number="1" location="model/kernel-socket-fd.cc:37">
P2: Stopping a DCE process can destroy this object without closing its LKL descriptor, because cleanup may run outside the node task. That leaks the kernel socket and its epoll registration; defer the close to the factory's kernel task during cleanup instead of silently leaving `m_fd` open.</violation>
</file>

<file name="model/dce.cc">

<violation number="1" location="model/dce.cc:458">
P2: The `optind <= 1` reset mistakes an in-progress short-option cluster for a fresh parse. Switching processes during `-ab` makes the original process return `a` twice; preserve the hidden cluster cursor or use a reentrant/process-local parser.</violation>
</file>

<file name="utils/lkl-patches/0002-lkl-stop-the-tick-while-the-host-is-idle.patch">

<violation number="1" location="utils/lkl-patches/0002-lkl-stop-the-tick-while-the-host-is-idle.patch:121">
P3: The new nohz state machine has no focused regression test. Add a deterministic LKL test that exercises idle entry and wakeup across simulated time, including a failed syscall, and verifies timer delivery.</violation>
</file>

<file name="model/lkl-wifi-device.cc">

<violation number="1" location="model/lkl-wifi-device.cc:132">
P3: `SetPhy` snapshots `CwMin` into `m_cw`, so per-device `CwMin` changes after installation do not affect the first access. Initialize the current window when a new queue/backoff begins while preserving the exponential window across retries.</violation>
</file>

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

View guided diff | Re-trigger cubic

Comment thread test/dce-manager-test.cc
TypeId tid;
bool kern_linux = TypeId::LookupByNameFailSafe ("ns3::LinuxSocketFdFactory", &tid);
std::string filePath = SearchExecFile ("DCE_PATH", "liblinux.so", 0);
std::string filePath = SearchExecFile ("DCE_PATH", "liblkl.so", 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The linux-stack gate now searches for liblkl.so, but DoRun still loads liblinux.so for the same LinuxSocketFdFactory. When only liblkl.so is present, the gate passes and every linux-stack test then fails to load its library. Change the Library value in the linux branch of DoRun to liblkl.so as well.

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 test/dce-manager-test.cc, line 279:

<comment>The linux-stack gate now searches for `liblkl.so`, but `DoRun` still loads `liblinux.so` for the same `LinuxSocketFdFactory`. When only `liblkl.so` is present, the gate passes and every linux-stack test then fails to load its library. Change the `Library` value in the linux branch of `DoRun` to `liblkl.so` as well.</comment>

<file context>
@@ -264,7 +276,7 @@ DceManagerTestSuite::DceManagerTestSuite ()
   TypeId tid;
   bool kern_linux = TypeId::LookupByNameFailSafe ("ns3::LinuxSocketFdFactory", &tid);
-  std::string filePath = SearchExecFile ("DCE_PATH", "liblinux.so", 0);
+  std::string filePath = SearchExecFile ("DCE_PATH", "liblkl.so", 0);
   if (kern_linux && (filePath.length () > 0))
     {
</file context>

SRC="${DEPS_DIR}/src"
JOBS="${JOBS:-$(nproc)}"

IPROUTE2_REV="v6.12.0" # matches LKL's kernel; has "ip mptcp"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Git tags are mutable, and fetch_git does not verify a commit hash for these sources. Pin each git source to a commit SHA, as QUAGGA_MODULE_REV already does, so the build is reproducible and the source is verified.

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_kernel_deps.sh, line 27:

<comment>Git tags are mutable, and `fetch_git` does not verify a commit hash for these sources. Pin each git source to a commit SHA, as `QUAGGA_MODULE_REV` already does, so the build is reproducible and the source is verified.</comment>

<file context>
@@ -0,0 +1,198 @@
+SRC="${DEPS_DIR}/src"
+JOBS="${JOBS:-$(nproc)}"
+
+IPROUTE2_REV="v6.12.0" # matches LKL's kernel; has "ip mptcp"
+LIBNL_REV="libnl3_10_0"
+IW_REV="v6.9"
</file context>

Comment thread example/dce-iperf.cc
else if (stack == "linux")
{
#ifdef KERNEL_STACK
#ifdef LINUX_STACK

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Removing the freebsd branch means --stack=freebsd silently skips stack setup and falls through to address assignment on nodes with no IP stack. Consider rejecting unknown stack names explicitly, e.g. NS_ABORT_MSG or NS_LOG_ERROR plus return 0 in an else branch.

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 example/dce-iperf.cc, line 66:

<comment>Removing the freebsd branch means `--stack=freebsd` silently skips stack setup and falls through to address assignment on nodes with no IP stack. Consider rejecting unknown stack names explicitly, e.g. `NS_ABORT_MSG` or `NS_LOG_ERROR` plus `return 0` in an `else` branch.</comment>

<file context>
@@ -63,7 +63,7 @@ int main (int argc, char *argv[])
   else if (stack == "linux")
     {
-#ifdef KERNEL_STACK
+#ifdef LINUX_STACK
       dceManager.SetNetworkStack ("ns3::LinuxSocketFdFactory", "Library", StringValue ("liblinux.so"));
       dceManager.Install (nodes);
</file context>

+ * frame pointers: stop at the first frame outside of the
+ * kernel, and on frames that do not go up the stack.
+ */
+ if (!__kernel_text_address(addr))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This arch/lkl fix to show_stack() ships without a focused test. Add a test that exercises a walk ending at a non-kernel return address and at a non-increasing next_frame, so a regression is caught.

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/lkl-patches/0004-lkl-show_stack-stop-at-the-first-frame-outside-of-th.patch, line 35:

<comment>This `arch/lkl` fix to `show_stack()` ships without a focused test. Add a test that exercises a walk ending at a non-kernel return address and at a non-increasing `next_frame`, so a regression is caught.</comment>

<file context>
@@ -0,0 +1,46 @@
++		 * frame pointers: stop at the first frame outside of the
++		 * kernel, and on frames that do not go up the stack.
++		 */
++		if (!__kernel_text_address(addr))
++			break;
+ 		printk("%s #%02d [<0x%016lx>] %pS\n",
</file context>

This document describes how to retrieve, configure, compile and use this custom kernel in DCE.
As an example, it shows how to enable an in-tree but optional protocol - Stream Control Transmission Protocol (SCTP) - and an out of tree protocol: Multipath TCP (MPTCP).
Although other protocols may not adapt these patterns as-is, you will see what's needed to implement for your purpose.
Each node gets its own kernel; every ns-3 NetDevice of the node becomes a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This overstates the naming rule. Loopback devices get no kernel device, and LklWifiDevice radios become wlan<n>, not sim<n>. Qualify the sentence, e.g. 'every ns-3 NetDevice except loopback becomes a kernel device: sim<n>, or wlan<n> for an LklWifiDevice'.

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 doc/source/dce-user-kernel.rst, line 14:

<comment>This overstates the naming rule. Loopback devices get no kernel device, and `LklWifiDevice` radios become `wlan<n>`, not `sim<n>`. Qualify the sentence, e.g. 'every ns-3 NetDevice except loopback becomes a kernel device: `sim<n>`, or `wlan<n>` for an `LklWifiDevice`'.</comment>

<file context>
@@ -1,237 +1,92 @@
-This document describes how to retrieve, configure, compile and use this custom kernel in DCE.
-As an example, it shows how to enable an in-tree but optional protocol - Stream Control Transmission Protocol (SCTP) - and an out of tree protocol: Multipath TCP (MPTCP).
-Although other protocols may not adapt these patterns as-is, you will see what's needed to implement for your purpose.
+Each node gets its own kernel; every ns-3 NetDevice of the node becomes a
+kernel network device named ``sim<n>``, and the sockets of DCE
+applications are kernel sockets.
</file context>

ns3stack.Install (routersR);

#ifdef KERNEL_STACK
#ifdef LINUX_STACK

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: dce-iperf-heterogeneous-multihop now uses the LKL build flag, but its fallback still directs users to the removed dce-linux module. Report the required --with-lkl configuration 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 example/dce-iperf-heterogeneous-multihop.cc, line 165:

<comment>`dce-iperf-heterogeneous-multihop` now uses the LKL build flag, but its fallback still directs users to the removed dce-linux module. Report the required `--with-lkl` configuration instead.</comment>

<file context>
@@ -162,7 +162,7 @@ main (int argc, char *argv[])
   ns3stack.Install (routersR);
 
-#ifdef KERNEL_STACK
+#ifdef LINUX_STACK
   LinuxStackHelper linuxStack;
 #endif
</file context>

Comment thread example/dce-iperf.cc
else if (stack == "linux")
{
#ifdef KERNEL_STACK
#ifdef LINUX_STACK

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: dce-iperf now uses the LKL build flag, but its fallback still tells users to build the removed dce-linux module. Report the required --with-lkl configuration 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 example/dce-iperf.cc, line 66:

<comment>`dce-iperf` now uses the LKL build flag, but its fallback still tells users to build the removed dce-linux module. Report the required `--with-lkl` configuration instead.</comment>

<file context>
@@ -63,7 +63,7 @@ int main (int argc, char *argv[])
   else if (stack == "linux")
     {
-#ifdef KERNEL_STACK
+#ifdef LINUX_STACK
       dceManager.SetNetworkStack ("ns3::LinuxSocketFdFactory", "Library", StringValue ("liblinux.so"));
       dceManager.Install (nodes);
</file context>

Comment on lines +150 to +152
Run (node, Seconds (0.5), "iw", "dev wlan0 set type mp");
LinuxStackHelper::RunIp (node, Seconds (0.6), "link set wlan0 up");
Run (node, Seconds (0.7), "iw", "dev wlan0 mesh join mesh freq 5500");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Run is not a public DCE API and this page does not define it. Replace these calls with DceApplicationHelper or define the helper so the Wi-Fi example is copyable.

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 doc/source/dce-user-kernel.rst, line 150:

<comment>`Run` is not a public DCE API and this page does not define it. Replace these calls with `DceApplicationHelper` or define the helper so the Wi-Fi example is copyable.</comment>

<file context>
@@ -244,19 +99,70 @@ You need first to add to your *DCE_PATH* the path to the previously compiled *li
+    NetDeviceContainer devices = wifi.Install (phy, nodes);
+    LklWifiHelper::EnablePcap (phy, "lkl-wifi", devices);
+    ...
+    Run (node, Seconds (0.5), "iw", "dev wlan0 set type mp");
+    LinuxStackHelper::RunIp (node, Seconds (0.6), "link set wlan0 up");
+    Run (node, Seconds (0.7), "iw", "dev wlan0 mesh join mesh freq 5500");
</file context>
Suggested change
Run (node, Seconds (0.5), "iw", "dev wlan0 set type mp");
LinuxStackHelper::RunIp (node, Seconds (0.6), "link set wlan0 up");
Run (node, Seconds (0.7), "iw", "dev wlan0 mesh join mesh freq 5500");
DceApplicationHelper iw;
iw.SetBinary ("iw");
iw.SetStackSize (1 << 16);
iw.ResetArguments ();
iw.ParseArguments ("dev wlan0 set type mp");
ApplicationContainer apps = iw.Install (node);
apps.Start (Seconds (0.5));
LinuxStackHelper::RunIp (node, Seconds (0.6), "link set wlan0 up");
iw.ResetArguments ();
iw.ParseArguments ("dev wlan0 mesh join mesh freq 5500");
apps = iw.Install (node);
apps.Start (Seconds (0.7));

+ * loop would, so that an idle kernel gets no timer interrupts until its
+ * next timer actually expires.
+ */
+static bool idle_host_nohz;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new nohz state machine has no focused regression test. Add a deterministic LKL test that exercises idle entry and wakeup across simulated time, including a failed syscall, and verifies timer delivery.

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/lkl-patches/0002-lkl-stop-the-tick-while-the-host-is-idle.patch, line 121:

<comment>The new nohz state machine has no focused regression test. Add a deterministic LKL test that exercises idle entry and wakeup across simulated time, including a failed syscall, and verifies timer delivery.</comment>

<file context>
@@ -0,0 +1,222 @@
++ * loop would, so that an idle kernel gets no timer interrupts until its
++ * next timer actually expires.
++ */
++static bool idle_host_nohz;
++
++static void idle_host_enter(void)
</file context>

Comment thread model/lkl-wifi-device.cc
NS_LOG_FUNCTION (this << phy << carrier);
m_phy = phy;
m_carrier = carrier;
m_cw = m_cwMin;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: SetPhy snapshots CwMin into m_cw, so per-device CwMin changes after installation do not affect the first access. Initialize the current window when a new queue/backoff begins while preserving the exponential window across retries.

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/lkl-wifi-device.cc, line 132:

<comment>`SetPhy` snapshots `CwMin` into `m_cw`, so per-device `CwMin` changes after installation do not affect the first access. Initialize the current window when a new queue/backoff begins while preserving the exponential window across retries.</comment>

<file context>
@@ -0,0 +1,629 @@
+  NS_LOG_FUNCTION (this << phy << carrier);
+  m_phy = phy;
+  m_carrier = carrier;
+  m_cw = m_cwMin;
+  m_phy->SetReceiveOkCallback (MakeCallback (&LklWifiDevice::Receive, this));
+}
</file context>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 10 files (changes from recent commits).

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="utils/lkl-patches/0006-lkl-add-a-mac80211-radio-whose-frames-are-carried-by.patch">

<violation number="1" location="utils/lkl-patches/0006-lkl-add-a-mac80211-radio-whose-frames-are-carried-by.patch:233">
P3: The rate-status fix in `lkl_wifi_tx_status()` and the `FIF_CONTROL` filter change are arch/lkl behavior fixes with no focused test. Add a KUnit test under arch/lkl for the per-rate attempt spreading and the control-frame filter, following `pci_test.c`. Also track both fixes for upstream LKL submission.</violation>
</file>

<file name="example/dce-lkl-wifi-litmus.cc">

<violation number="1" location="example/dce-lkl-wifi-litmus.cc:418">
P2: The address remains configured across `ip link set wlan0 down/up`, so this second `ip addr add` fails with `RTNETLINK answers: File exists`. Use `ip addr replace` or remove the address before adding it.</violation>

<violation number="2" location="example/dce-lkl-wifi-litmus.cc:628">
P3: An invalid `--standard` value silently selects AX, so a typo runs the wrong PHY scenario. Validate that the value is exactly `n`, `ac`, or `ax` before selecting `wifiStd`.</violation>

<violation number="3" location="example/dce-lkl-wifi-litmus.cc:805">
P2: The default `rate` run stops at 20 seconds, but its downlink and `lkl-sniff` run through 21 seconds. Increase the rate scenario’s stop time or derive these workloads from `stop` so the advertised transfer completes.</violation>
</file>

<file name="example/lkl-sniff.cc">

<violation number="1" location="example/lkl-sniff.cc:102">
P2: A failed pcap open leaves `pcap` null, then the next `fwrite` uses it and can crash. Check `fopen` and report the error before writing the header.</violation>

<violation number="2" location="example/lkl-sniff.cc:123">
P2: The monitor capture buffer cannot hold a maximum-size LKL frame plus its radiotap header, and the pcap path hides truncation by using `n` for both lengths. Increase the buffer or allocate it on the heap and preserve the original length when truncation occurs.</violation>

<violation number="3" location="example/lkl-sniff.cc:143">
P2: The pcap timestamps use `CLOCK_MONOTONIC`, so readers interpret time-since-boot as a Unix timestamp and show the capture at the wrong date. Use `CLOCK_REALTIME` for record timestamps while keeping monotonic time for the capture deadline.</violation>
</file>

Tip: Get a Slack DM when cubic finishes reviewing your PRs. Connect Slack

View guided diff | Re-trigger cubic

Run (l1, Seconds (7.1), "ip", "link set wlan0 address 02:00:00:00:02:02");
Run (l1, Seconds (7.2), "ip", "link set wlan0 up");
Run (l1, Seconds (7.3), "iw", "dev wlan0 connect litmus 5180");
Run (l1, Seconds (7.4), "ip", "addr add 10.1.1.2/24 dev wlan0");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The address remains configured across ip link set wlan0 down/up, so this second ip addr add fails with RTNETLINK answers: File exists. Use ip addr replace or remove the address before adding it.

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 example/dce-lkl-wifi-litmus.cc, line 418:

<comment>The address remains configured across `ip link set wlan0 down/up`, so this second `ip addr add` fails with `RTNETLINK answers: File exists`. Use `ip addr replace` or remove the address before adding it.</comment>

<file context>
@@ -0,0 +1,811 @@
+          Run (l1, Seconds (7.1), "ip", "link set wlan0 address 02:00:00:00:02:02");
+          Run (l1, Seconds (7.2), "ip", "link set wlan0 up");
+          Run (l1, Seconds (7.3), "iw", "dev wlan0 connect litmus 5180");
+          Run (l1, Seconds (7.4), "ip", "addr add 10.1.1.2/24 dev wlan0");
+          Run (l1, Seconds (9.0), "iw", "dev wlan0 link");
+          Run (l1, Seconds (9.5), "ping", "-c 10 -i 0.2 10.1.1.100");
</file context>
Suggested change
Run (l1, Seconds (7.4), "ip", "addr add 10.1.1.2/24 dev wlan0");
Run (l1, Seconds (7.4), "ip", "addr replace 10.1.1.2/24 dev wlan0");

NS_FATAL_ERROR ("Unknown scenario " << scenario);
}

Simulator::Stop (Seconds (stop));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The default rate run stops at 20 seconds, but its downlink and lkl-sniff run through 21 seconds. Increase the rate scenario’s stop time or derive these workloads from stop so the advertised transfer completes.

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 example/dce-lkl-wifi-litmus.cc, line 805:

<comment>The default `rate` run stops at 20 seconds, but its downlink and `lkl-sniff` run through 21 seconds. Increase the rate scenario’s stop time or derive these workloads from `stop` so the advertised transfer completes.</comment>

<file context>
@@ -0,0 +1,811 @@
+      NS_FATAL_ERROR ("Unknown scenario " << scenario);
+    }
+
+  Simulator::Stop (Seconds (stop));
+  Simulator::Run ();
+  std::cout << "litmus " << scenario << " completed at " << Simulator::Now ().GetSeconds () << "s"
</file context>

Comment thread example/lkl-sniff.cc
// type, source, destination, protocol -> frames, bytes
std::map<std::tuple<int, std::string, std::string, int>, std::pair<uint64_t, uint64_t> > counts;
uint64_t frames = 0;
uint8_t buf[4096];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The monitor capture buffer cannot hold a maximum-size LKL frame plus its radiotap header, and the pcap path hides truncation by using n for both lengths. Increase the buffer or allocate it on the heap and preserve the original length when truncation occurs.

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 example/lkl-sniff.cc, line 123:

<comment>The monitor capture buffer cannot hold a maximum-size LKL frame plus its radiotap header, and the pcap path hides truncation by using `n` for both lengths. Increase the buffer or allocate it on the heap and preserve the original length when truncation occurs.</comment>

<file context>
@@ -0,0 +1,174 @@
+  // type, source, destination, protocol -> frames, bytes
+  std::map<std::tuple<int, std::string, std::string, int>, std::pair<uint64_t, uint64_t> > counts;
+  uint64_t frames = 0;
+  uint8_t buf[4096];
+  double end = Now () + duration;
+  while (Now () < end)
</file context>

Comment thread example/lkl-sniff.cc
frames++;
if (pcap)
{
double t = Now ();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The pcap timestamps use CLOCK_MONOTONIC, so readers interpret time-since-boot as a Unix timestamp and show the capture at the wrong date. Use CLOCK_REALTIME for record timestamps while keeping monotonic time for the capture deadline.

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 example/lkl-sniff.cc, line 143:

<comment>The pcap timestamps use `CLOCK_MONOTONIC`, so readers interpret time-since-boot as a Unix timestamp and show the capture at the wrong date. Use `CLOCK_REALTIME` for record timestamps while keeping monotonic time for the capture deadline.</comment>

<file context>
@@ -0,0 +1,174 @@
+      frames++;
+      if (pcap)
+        {
+          double t = Now ();
+          uint32_t record[4] = {(uint32_t)t, (uint32_t)((t - (uint32_t)t) * 1e6),
+                                (uint32_t)n, (uint32_t)n};
</file context>

Comment thread example/lkl-sniff.cc
FILE *pcap = 0;
if (argc > 3)
{
pcap = fopen (argv[3], "w");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A failed pcap open leaves pcap null, then the next fwrite uses it and can crash. Check fopen and report the error before writing the header.

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 example/lkl-sniff.cc, line 102:

<comment>A failed pcap open leaves `pcap` null, then the next `fwrite` uses it and can crash. Check `fopen` and report the error before writing the header.</comment>

<file context>
@@ -0,0 +1,174 @@
+  FILE *pcap = 0;
+  if (argc > 3)
+    {
+      pcap = fopen (argv[3], "w");
+      // Global header: radiotap (127), the link type of monitor interfaces.
+      uint32_t header[6] = {0xa1b2c3d4, 0x00040002, 0, 0, 65535, 127};
</file context>

+ rate->count = 0;
+ continue;
+ }
+ rate->count = counts[i];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The rate-status fix in lkl_wifi_tx_status() and the FIF_CONTROL filter change are arch/lkl behavior fixes with no focused test. Add a KUnit test under arch/lkl for the per-rate attempt spreading and the control-frame filter, following pci_test.c. Also track both fixes for upstream LKL submission.

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/lkl-patches/0006-lkl-add-a-mac80211-radio-whose-frames-are-carried-by.patch, line 233:

<comment>The rate-status fix in `lkl_wifi_tx_status()` and the `FIF_CONTROL` filter change are arch/lkl behavior fixes with no focused test. Add a KUnit test under arch/lkl for the per-rate attempt spreading and the control-frame filter, following `pci_test.c`. Also track both fixes for upstream LKL submission.</comment>

<file context>
@@ -226,6 +230,7 @@ index 000000000..bc45e09d9
 +			rate->count = 0;
 +			continue;
 +		}
++		rate->count = counts[i];
 +		if (rate->count > attempts || i == IEEE80211_TX_MAX_RATES - 1)
 +			rate->count = attempts;
</file context>

{
// An ns-3 AP and a Linux STA of the same standard, on a spectrum
// channel (per-20 MHz CCA, for the secondary channel).
WifiStandard wifiStd = standard == "n" ? WIFI_STANDARD_80211n

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: An invalid --standard value silently selects AX, so a typo runs the wrong PHY scenario. Validate that the value is exactly n, ac, or ax before selecting wifiStd.

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 example/dce-lkl-wifi-litmus.cc, line 628:

<comment>An invalid `--standard` value silently selects AX, so a typo runs the wrong PHY scenario. Validate that the value is exactly `n`, `ac`, or `ax` before selecting `wifiStd`.</comment>

<file context>
@@ -0,0 +1,811 @@
+    {
+      // An ns-3 AP and a Linux STA of the same standard, on a spectrum
+      // channel (per-20 MHz CCA, for the secondary channel).
+      WifiStandard wifiStd = standard == "n" ? WIFI_STANDARD_80211n
+        : standard == "ac" ? WIFI_STANDARD_80211ac : WIFI_STANDARD_80211ax;
+      std::string settings = standard == "n" ? "{38, 40, BAND_5GHZ, 0}" : "{42, 80, BAND_5GHZ, 0}";
</file context>

Gabrielcarvfer and others added 15 commits November 5, 2026 09:57
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>
The ns-pango subproject of the pinned revision is no longer published,
so the build failed. Newer Northstar replaced it with web-text-lib and
dropped the version script that localised the program's symbols: main()
stays exported through -rdynamic, and -shared now goes on the program's
own link arguments. WebAssembly and GStreamer are left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the host raised the interrupt of a radio while the CPU of the kernel was taken by a task waiting for the simulator, the interrupt only became pending and the interrupt task raised it again and again, without letting the simulator run: simulated time stopped.

When the device still has events after raising the interrupt, sleep until its next event or for 10 us of simulated time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The radio cleared the status of a transmitted frame, which zeroes the attempts of each entry of its rate series, before spreading the attempts the host reported over those entries: every status reached the rate control (minstrel_ht) with no attempt at any rate, so it never learned and kept its lowest rate.

Keep the attempts of each entry before clearing the status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The radio consumed the control frames it received (ACKs, CTS, CF-End...), and its filter did not pass FIF_CONTROL, so the monitor interfaces of Linux showed none, unlike those of a card that passes them.

Pass every control frame to the kernel after handling it, without acknowledging control frames, and keep FIF_CONTROL in the filter of the radio. A Linux monitor interface and an ns-3 one next to it now capture the same frames, control frames included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ns-3 receivers add an SnrTag, with the SNR of the frame they acknowledge, to their ACKs, which the rate managers of ns-3 transmitters that use it (e.g. IdealWifiManager) read: without it, they sent to Linux at their lowest rate.

Add the tag to the ACKs of the radio, as ns-3 receivers do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dce-lkl-wifi-litmus puts Linux (mac80211 in LKL, on LklWifiDevice radios) next to ns-3 wifi devices in scenarios that show what a real stack does, or where its frames exercise ns-3 code paths: promiscuous reception, 2.4 GHz on spectrum channels, rate control, dense mesh with frame capture, pcap traces next to a Linux monitor interface, and runtime address changes.

lkl-sniff prints what the kernel delivers on an interface, as a packet socket sees it, or writes what a monitor interface receives to a pcap file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Acks

The kernel radio (LKL patch 0007) offers the HT (20/40 MHz), VHT (up to 80 MHz) and HE single user capabilities of the PHY it is given, with two spatial streams, and A-MPDU aggregation; the host is told the format, MCS, number of streams, width and guard interval of each entry of the rate series, and tells the kernel those of the frames it receives. As HE devices choose their rates in firmware (minstrel_ht has no HE rates), the frames of HE peers are sent as HE SU PPDUs at the MCS and number of streams minstrel_ht chose.

As a card does, LklWifiDevice aggregates the MPDUs the kernel flags for A-MPDU (up to 64 MPDUs, 65535 octets and 5.484 ms), answers A-MPDUs and Block Ack Requests with compressed Block Acks from a scoreboard per originator and TID, reports the outcome of each MPDU from the Block Acks it receives, and answers RTS frames with CTS frames of their width. An opt-in attribute (WmmFromEdca) copies the EDCA Parameter Set of ns-3 APs into a WMM element, which ns-3 APs do not send and without which mac80211 limits itself to legacy rates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ht: an ns-3 AP and a Linux STA of a given standard (--standard=n|ac|ax), with UDP both ways. frag-ba: fragmentation under a Block Ack agreement (#1199), in either direction. thompson-width: ThompsonSamplingWifiManager on a 40 MHz VHT AP using RTS/CTS while its secondary channel is busy (#1194).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant