diff --git a/nim-test-node/regression/Dockerfile_shadow b/nim-test-node/regression/Dockerfile_shadow index 2de995ad..9ed37dcd 100644 --- a/nim-test-node/regression/Dockerfile_shadow +++ b/nim-test-node/regression/Dockerfile_shadow @@ -1,62 +1,55 @@ # Dynamic-linked nim test-node, for the Shadow simulator. # -# Mirrors Dockerfile but DROPS the static linker flags (`--passL:"-static -# -mmusl"` and `--passL:"-static-libgcc -static-libstdc++"`). +# Mirrors Dockerfile (same base, nimble and lockfile, amd64 only), plus the lsquic patch +# and -d:lsquic_no_sendmmsg. Shadow's syscall interposer only hooks dynamically linked +# ELFs, and it does not implement sendmmsg. # -# Why: Shadow's syscall interposer is LD_PRELOAD-based and only hooks -# dynamically linked ELFs. A statically linked nim binary fails at startup with -# "Failed to verify plugin path '...': not a dynamically linked ELF". -# -# Build via kaniko on a lab worker (see shadow/kaniko-build-test-node-job.yaml). -# Pushes to radiken/dst-test-node-shadow:latest. - -FROM debian:bookworm-slim AS builder +# Build via kaniko on a lab worker (see shadow/kaniko-build-test-node-job.yaml), once per +# NODE_BINARY. -ARG NODE_BINARY=node -ARG NIM_VERSION=2.2.6 +FROM nimlang/nim:2.2.10 AS builder WORKDIR /node RUN apt-get update && apt-get install -y --no-install-recommends \ - curl git build-essential ca-certificates \ - libssl3 iproute2 procps xz-utils \ - && rm -rf /var/lib/apt/lists/* \ - && apt-get clean + git \ + build-essential \ + ca-certificates \ + curl \ + libssl3 \ + iproute2 \ + procps \ + && rm -rf /var/lib/apt/lists/* -RUN git config --global http.sslVerify false +RUN git config --global http.version HTTP/1.1 -RUN curl -fsSL "https://nim-lang.org/download/nim-${NIM_VERSION}-linux_x64.tar.xz" -o /tmp/nim.tar.xz \ - && tar -xf /tmp/nim.tar.xz -C /tmp \ - && cp -r "/tmp/nim-${NIM_VERSION}/"* /usr/local \ - && rm -rf /usr/local/nim* \ - && mv "/tmp/nim-${NIM_VERSION}" "/usr/local/nim-${NIM_VERSION}" \ - && ln -sf "/usr/local/nim-${NIM_VERSION}/bin/nim" /usr/local/bin/nim \ - && ln -sf "/usr/local/nim-${NIM_VERSION}/bin/nimble" /usr/local/bin/nimble +# fix #1837: honor locked package URLs during installation (#1838) +RUN nimble install -y 'nimble@#07caee397d628c9e93d81048268365c4c2414a80' +ENV PATH="/root/.nimble/bin:${PATH}" -COPY . . +COPY test_node.nimble nimble.lock config.nims ./ +RUN nimble refresh && nimble setup -RUN nimble refresh -RUN nimble install -dy --verbose +COPY . . -# Floor lsquic's engine tick re-arms so Shadow can advance simulated time. -# Gated by LSQUIC_TICK_FLOOR_US (unset/0 = stock). See README.md. -COPY lsquic-tick-floor.patch /tmp/lsquic-tick-floor.patch +# Floor lsquic's engine tick re-arms so Shadow can advance simulated time, and let the +# build opt out of sendmmsg. Gated by LSQUIC_TICK_FLOOR_US (unset/0 = stock). See README.md. RUN set -eu; \ patched=0; \ for d in /root/.nimble/pkgcache/*lsquic*/ /root/.nimble/pkgs2/lsquic-*/; do \ [ -f "${d}lsquic/context/context.nim" ] || continue; \ if grep -q tickFloorUs "${d}lsquic/context/context.nim"; then echo "already patched $d"; continue; fi; \ - ( cd "$d" && git apply -p1 /tmp/lsquic-tick-floor.patch ); \ + ( cd "$d" && git apply -p1 /node/lsquic-tick-floor.patch ); \ echo "patched $d"; patched=$((patched+1)); \ done; \ [ "$patched" -gt 0 ] || { echo "no lsquic package found to patch"; exit 1; } -# Dynamic link: no `-static`, no `-mmusl`, no `-static-libgcc/-libstdc++`. +ARG NODE_BINARY=node RUN test "${NODE_BINARY}" = "node" -o "${NODE_BINARY}" = "bootstrap" RUN nimble c \ - --os:linux --cpu:amd64 \ -d:chronicles_colors=None --threads:on --mm:refc \ -d:metrics -d:libp2p_network_protocols_metrics -d:release \ + -d:lsquic_no_sendmmsg \ --out:/node/main \ "${NODE_BINARY}/main.nim" \ && chmod +x /node/main \ @@ -65,9 +58,7 @@ RUN nimble c \ && grep -qa LSQUIC_TICK_FLOOR_US /node/main \ && echo "TICK-FLOOR VERIFIED IN BINARY" -# Tiny final stage: the binary + its runtime deps. The image's role is to hand -# off /node/main to an init container, not to run anything itself. We still -# include glibc/libssl/libstdc++ in case someone wants to `docker run` it. +# The binary and its runtime deps; an init container copies /node/main out for Shadow. FROM debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/nim-test-node/regression/README.md b/nim-test-node/regression/README.md index 0190fc9f..5e07768a 100644 --- a/nim-test-node/regression/README.md +++ b/nim-test-node/regression/README.md @@ -38,7 +38,8 @@ message delay and expose metrics on port `8008`. - `Dockerfile`: simple normal Docker build for the current Docker target platform. - `Dockerfile_shadow`: Shadow-specific Docker build. It creates a dynamically - linked amd64 binary and applies the lsquic tick-floor patch. + linked amd64 binary, applies the lsquic tick-floor patch and builds without + `sendmmsg`, which Shadow does not implement. ## Building the normal Docker images @@ -106,7 +107,9 @@ exposing both upstream in nim-lsquic. ## Maintaining the patch -It targets lsquic 0.5.4's layout, pinned in `test_node.nimble`. `nimble c` +It targets lsquic 0.8.1's layout, pinned in `nimble.lock`. It also guards +lsquic's `sendmmsg` path behind `-d:lsquic_no_sendmmsg`, which only +`Dockerfile_shadow` sets. `nimble c` re-resolves deps before compiling, so the Dockerfile patches the package cache (`~/.nimble/pkgcache`, what nimble copies from) rather than the installed copy. The build asserts it patched something and greps the binary for diff --git a/nim-test-node/regression/env.nim b/nim-test-node/regression/env.nim index 6727f987..a0c753ea 100644 --- a/nim-test-node/regression/env.nim +++ b/nim-test-node/regression/env.nim @@ -1,7 +1,7 @@ import strutils, os, osproc from std/net import getPrimaryIPAddr, IpAddress, `$` import chronos, metrics/chronos_httpserver, chronicles -from nativesockets import getHostname +from nativesockets import getHostname, getHostByName logScope: topics = "dst" @@ -27,6 +27,14 @@ let proc listenHost*(): string = ## The interface the pod routes out of; 0.0.0.0 would announce loopback too. + if inShadow: + # Shadow has no default route to probe; the host's own name resolves to its IP. + try: + for ip in getHostByName(getHostname()).addrList: + if not ip.startsWith("127.") and ip != "0.0.0.0": + return ip + except CatchableError as e: + warn "Could not resolve own hostname in Shadow", error = e.msg try: $getPrimaryIPAddr() except CatchableError: diff --git a/nim-test-node/regression/lsquic-tick-floor.patch b/nim-test-node/regression/lsquic-tick-floor.patch index 2a63859c..c458d370 100644 --- a/nim-test-node/regression/lsquic-tick-floor.patch +++ b/nim-test-node/regression/lsquic-tick-floor.patch @@ -79,3 +79,15 @@ diff -ruN orig/lsquic/context/server.nim patched/lsquic/context/server.nim ctx.stream_if = struct_lsquic_stream_if( on_new_conn: onNewConn, +diff -ruN orig/lsquic/context/io.nim patched/lsquic/context/io.nim +--- orig/lsquic/context/io.nim 2026-09-16 11:11:51 ++++ patched/lsquic/context/io.nim 2026-09-16 11:11:51 +@@ -137,7 +137,7 @@ + + let specsArr = cast[ptr UncheckedArray[struct_lsquic_out_spec]](specs) + +- when defined(linux): ++ when defined(linux) and not defined(lsquic_no_sendmmsg): + var + destStorages {.noinit.}: array[SendmmsgBatchSize, Sockaddr_storage] + msgs {.noinit.}: array[SendmmsgBatchSize, MMsgHdr] diff --git a/nim-test-node/regression/shutdown_utils.nim b/nim-test-node/regression/shutdown_utils.nim index 81614d3c..c6719914 100644 --- a/nim-test-node/regression/shutdown_utils.nim +++ b/nim-test-node/regression/shutdown_utils.nim @@ -1,6 +1,11 @@ import chronos +import ./env proc waitShutdownSignal*() {.async.} = + if inShadow: + # Shadow cannot register signal handlers; it stops the process at sim end. + await newFuture[void]("waitShutdownSignal.shadow") + return let sigIntFut = waitSignal(SIGINT) sigTermFut = waitSignal(SIGTERM) diff --git a/shadow/kaniko-build-test-node-job.yaml b/shadow/kaniko-build-test-node-job.yaml index a2ce7ce6..b34b20ba 100644 --- a/shadow/kaniko-build-test-node-job.yaml +++ b/shadow/kaniko-build-test-node-job.yaml @@ -7,7 +7,8 @@ # kubectl -n zerotesting-shadow wait --for=condition=Complete \ # --for=condition=Failed --timeout=900s job/test-node-shadow-build # -# Builds the regression test-node (nim-test-node/regression) straight from master. +# Builds the regression test-node (nim-test-node/regression) straight from master. For the +# bootstrap, set NODE_BINARY=bootstrap and push to radiken/dst-test-node-shadow-bootstrap. # Prereq: dockerhub-creds secret in this namespace (see shadow/kaniko-build-job.yaml). apiVersion: batch/v1 kind: Job @@ -36,9 +37,10 @@ spec: - name: kaniko image: gcr.io/kaniko-project/executor:v1.23.2 args: - - "--context=git://github.com/vacp2p/dst-libp2p-test-node.git#refs/heads/master" + - "--context=git://github.com/iftech/dst-libp2p-test-node.git#refs/heads/master" - "--context-sub-path=nim-test-node/regression" - - "--dockerfile=Dockerfile_amd64_shadow" + - "--dockerfile=Dockerfile_shadow" + - "--build-arg=NODE_BINARY=node" - "--destination=radiken/dst-test-node-shadow:latest" - "--cache=true" - "--cache-repo=radiken/dst-test-node-shadow-cache"