Files
TeslaRel410/emulator/vpx-device/ethernet_pcap.cpp
T
CydandClaude Fable 5 f2687a3146 BT410 5.3.72: the fault caught in the act; the inside view gets its cockpit
VPX_PF_WATCH (fork, cpu/paging.cpp): on a guest page fault at the watched
linear address, dump guest registers, the last 64 serial RX deliveries with
guest cs:eip at each, the code bytes at the faulting EIP and the stack top.
serialnamedpipe's doReceive feeds the ring.  Armed in podrun.sh and
launch_pod.ps1.

The first catch decoded the residual fault completely: CS:EIP 00FF:000066D4
in the DPMI host, EBX = F000CA60 -- an IVT entry read as a dword, segment
F000 offset CA60, a BIOS default interrupt handler -- and the faulting access
is [EBX+0x3004], whose 0x80000000 segment-base wrap gives exactly cr2
7000FA64.  The host probes a word 0x3004 bytes past a real-mode vector value
treated as a flat pointer: harmless for its own low-memory handlers, a fault
for BIOS F000:xxxx defaults.  The serial ring shows a steady 1-byte/1-3ms
vRIO stream with nothing special at the fault -- the stream determines which
vectors get walked, not the crash itself.  The 0x3004 appears nowhere in
DPMI32VM.OVL or 32RTM.EXE as an immediate, so the probe now also dumps code
bytes at EIP; faulthunt.sh loops runs until the next catch.

Shipped baseline streak: 4/4 clean -- consistent with exposure, not yet
discriminating.

The inside view now loads the COCKPIT skeleton: the fleet-wide X-variant
naming convention (MAD->MAX etc., all 64 skeletons present) selects the same
25-joint chain with a single object -- max_cop.bgf, the MAX_COP canopy shell
with the PUNCH-texel windows from the capture forensics.  The donor names the
same mechanism from the decomp side (inside = SkeletonType_A with '_cop'
selection).  Fallback to the body skeleton when no X file exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 18:09:22 -05:00

546 lines
21 KiB
C++

/*
* Copyright (C) 2021 The DOSBox Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "config.h"
#if C_PCAP
#include "ethernet_pcap.h"
#include "dosbox.h"
#include "logging.h"
#include "support.h" /* strcasecmp */
#include "control.h" /* [ne2000] macaddr for the RX filter */
#include <cstring>
#include <cstdlib> /* getenv/strtoul -- the latency shim's knobs */
#include <chrono>
#include <deque>
extern std::string niclist;
#ifdef WIN32
#include <windows.h>
// DLL loading
#define pcap_sendpacket(A,B,C) PacketSendPacket(A,B,C)
#define pcap_close(A) PacketClose(A)
#define pcap_freealldevs(A) PacketFreealldevs(A)
#define pcap_open(A,B,C,D,E,F) PacketOpen(A,B,C,D,E,F)
#define pcap_next_ex(A,B,C) PacketNextEx(A,B,C)
#define pcap_findalldevs_ex(A,B,C,D) PacketFindALlDevsEx(A,B,C,D)
#define pcap_geterr(A) PacketGetError(A)
#define pcap_compile(A,B,C,D,E) PacketCompile(A,B,C,D,E)
#define pcap_setfilter(A,B) PacketSetFilter(A,B)
#define pcap_freecode(A) PacketFreecode(A)
int (*PacketSendPacket)(pcap_t *, const u_char *, int) = 0;
void (*PacketClose)(pcap_t *) = 0;
void (*PacketFreealldevs)(pcap_if_t *) = 0;
pcap_t* (*PacketOpen)(char const *,int,int,int,struct pcap_rmtauth *,char *) = 0;
int (*PacketNextEx)(pcap_t *, struct pcap_pkthdr **, const u_char **) = 0;
int (*PacketFindALlDevsEx)(char *, struct pcap_rmtauth *, pcap_if_t **, char *) = 0;
char* (*PacketGetError)(pcap_t *) = nullptr;
int (*PacketCompile)(pcap_t *, struct bpf_program *, const char *, int, bpf_u_int32) = 0;
int (*PacketSetFilter)(pcap_t *, struct bpf_program *) = 0;
void (*PacketFreecode)(struct bpf_program *) = 0;
char pcap_src_if_string[] = PCAP_SRC_IF_STRING;
bool LoadPcapLibrary() {
// remember if we've already initialized the library
static HINSTANCE pcapinst = (HINSTANCE)-1;
if(pcapinst!=(HINSTANCE)-1) {
return (pcapinst!=NULL);
}
// init the library
pcapinst = LoadLibrary("WPCAP.DLL");
if(pcapinst==NULL) {
niclist = "WinPcap has to be installed for the NE2000 to work.";
LOG_MSG(niclist.c_str());
return false;
}
FARPROC psp;
#ifdef __MINGW32__
// C++ defines function and data pointers as separate types to reflect
// Harvard architecture machines (like the Arduino). As such, casting
// between them isn't portable and GCC will helpfully warn us about it.
// We're only running this code on Windows which explicitly allows this
// behaviour, so silence the warning to avoid confusion.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
psp = GetProcAddress(pcapinst,"pcap_sendpacket");
if(!PacketSendPacket) PacketSendPacket =
(int (__cdecl *)(pcap_t *,const u_char *,int))psp;
psp = GetProcAddress(pcapinst,"pcap_close");
if(!PacketClose) PacketClose =
(void (__cdecl *)(pcap_t *)) psp;
psp = GetProcAddress(pcapinst,"pcap_freealldevs");
if(!PacketFreealldevs) PacketFreealldevs =
(void (__cdecl *)(pcap_if_t *)) psp;
psp = GetProcAddress(pcapinst,"pcap_open");
if(!PacketOpen) PacketOpen =
(pcap_t* (__cdecl *)(char const *,int,int,int,struct pcap_rmtauth *,char *)) psp;
psp = GetProcAddress(pcapinst,"pcap_next_ex");
if(!PacketNextEx) PacketNextEx =
(int (__cdecl *)(pcap_t *, struct pcap_pkthdr **, const u_char **)) psp;
psp = GetProcAddress(pcapinst,"pcap_findalldevs_ex");
if(!PacketFindALlDevsEx) PacketFindALlDevsEx =
(int (__cdecl *)(char *, struct pcap_rmtauth *, pcap_if_t **, char *)) psp;
psp = GetProcAddress(pcapinst,"pcap_geterr");
if(!PacketGetError) PacketGetError =
(char* (__cdecl *)(pcap_t *)) psp;
/* RX-filter trio: optional -- if absent we just run unfiltered */
psp = GetProcAddress(pcapinst,"pcap_compile");
if(!PacketCompile) PacketCompile =
(int (__cdecl *)(pcap_t *, struct bpf_program *, const char *, int, bpf_u_int32)) psp;
psp = GetProcAddress(pcapinst,"pcap_setfilter");
if(!PacketSetFilter) PacketSetFilter =
(int (__cdecl *)(pcap_t *, struct bpf_program *)) psp;
psp = GetProcAddress(pcapinst,"pcap_freecode");
if(!PacketFreecode) PacketFreecode =
(void (__cdecl *)(struct bpf_program *)) psp;
#ifdef __MINGW32__
#pragma GCC diagnostic pop
#endif
if(PacketFindALlDevsEx==0 || PacketNextEx==0 || PacketOpen==0 ||
PacketFreealldevs==0 || PacketClose==0 || PacketSendPacket==0 ||
PacketGetError==0) {
niclist = "Incorrect or non-functional WinPcap version.";
LOG_MSG(niclist.c_str());
pcapinst = NULL;
return false;
}
return true;
}
#endif
/* ---- Phase 0: the latency-tolerance gate --------------------------------
*
* The riskiest assumption in the whole internet-play plan is that the 1995
* stack -- WATTCP + netnub + L4NetworkManager, every one of them tuned for a
* same-switch LAN with sub-millisecond RTT -- still functions at 30-150ms RTT
* with jitter. They are unpatchable 30-year-old binaries, so the answer has to
* be MEASURED on the existing LAN rigs, with zero Steam code, before any
* transport work is committed to. External shapers (clumsy, WinDivert) hook
* the host IP stack and never see Npcap-injected frames at all, so the delay
* has to live inside the backend. Three env knobs, read once at Initialize():
*
* VWE_NET_DELAY_MS fixed one-way delay applied to BOTH TX and RX, so one
* shimmed pod adds 2x delay to every RTT through it.
* VWE_NET_JITTER_MS uniform 0..N ms extra per frame, with release times
* clamped monotonic per queue -- frames NEVER reorder.
* The real transport is Steam's reliable ORDERED channel;
* a reordering shim would be measuring a transport we are
* not going to build.
* VWE_NET_LOSS_PCT drop percentage, default 0. Reliable delivery surfaces
* loss as a delay spike rather than a drop, so this is
* for characterization only and is not part of the gate.
*
* All unset = the shim does not exist: SendPacket/GetPackets run byte-for-byte
* the code they ran before it was added, and not a single extra log line is
* emitted (same convention as VPXLOG). If any of the three IS set, exactly one
* NETSHIM line is always logged -- ACTIVE or INERT -- because a silent run is
* ambiguous with a binary that was built without this file at all.
*
* Timebase is WALL clock, not emulated time -- network latency is wall clock
* and the guest's own timers must stay free to disagree with it. GetPackets()
* is called from NE2000_Poller, a TIMER_AddTickHandler tick, i.e. once per
* emulated millisecond, and that is the drain quantum: no thread and no timer
* is added anywhere. steady_clock rather than GetTickCount64 because the
* latter advances in ~15.6ms steps, which would quantize the 15ms knobs into
* all-or-nothing jumps.
*
* State is file-static rather than class members because ethernet_pcap.h is
* stock DOSBox-X we would rather not fork, and there is only ever one NE2000.
* Test matrix, two-rig topology and the pass/fail gate: emulator/NET-NOTES.md.
*/
enum { SHIM_FRAMEBUF = 1600 }; /* larger than any frame the NE2000 can make */
struct DelayedFrame {
uint64_t release_ms;
int len;
uint8_t data[SHIM_FRAMEBUF];
};
static bool shim_on = false;
static unsigned shim_delay_ms = 0, shim_jitter_ms = 0, shim_loss_pct = 0;
static std::deque<DelayedFrame> shim_txq, shim_rxq;
static uint64_t shim_tx_release = 0, shim_rx_release = 0; /* monotonic clamp */
static unsigned long shim_lost = 0;
static uint32_t shim_rng = 0;
static bool shim_oversize_warned = false;
static uint64_t shim_now_ms() {
using namespace std::chrono;
return (uint64_t)duration_cast<milliseconds>(
steady_clock::now().time_since_epoch()).count();
}
/* Own xorshift instead of rand(): the shim must not perturb the global rand()
* sequence the rest of the emulator draws from. */
static uint32_t shim_rand() {
shim_rng ^= shim_rng << 13;
shim_rng ^= shim_rng >> 17;
shim_rng ^= shim_rng << 5;
return shim_rng;
}
/* Park a frame on `q`. True = the caller must NOT act on the frame now (it was
* queued, or the loss knob ate it). A frame too big for the buffer is passed
* through undelayed instead of truncated -- a truncated frame would be a
* corruption bug wearing a latency finding's clothes. */
static bool shim_hold(std::deque<DelayedFrame>& q, uint64_t& last_release,
const uint8_t* frame, int len)
{
if (len < 0 || len > (int)SHIM_FRAMEBUF) {
if (!shim_oversize_warned) {
shim_oversize_warned = true;
LOG_MSG("NETSHIM: len=%d exceeds the %d-byte queue buffer -- oversize frames pass through undelayed",
len, (int)SHIM_FRAMEBUF);
}
return false;
}
if (shim_loss_pct && (shim_rand() % 100u) < shim_loss_pct) {
shim_lost++;
return true;
}
uint64_t rel = shim_now_ms() + shim_delay_ms;
if (shim_jitter_ms) rel += shim_rand() % (shim_jitter_ms + 1u);
if (rel < last_release) rel = last_release; /* ordered, never reordered */
last_release = rel;
q.push_back(DelayedFrame());
DelayedFrame& f = q.back();
f.release_ms = rel;
f.len = len;
memcpy(f.data, frame, (size_t)len);
return true;
}
/* Read one knob. `present` is set if the variable EXISTS, whatever its value,
* so that (a) an all-zero control run still announces itself and (b) a typo'd
* or non-numeric value is reported instead of quietly measuring nothing --
* strtoul("abc") is 0, which is indistinguishable from "shim not built in".
* Trailing whitespace is tolerated: `set VWE_NET_DELAY_MS=50 ` in a .bat keeps
* the space, and rejecting that would be a trap rather than a check. */
static unsigned shim_knob(const char* name, bool& present) {
const char* e = getenv(name);
if (!e) return 0;
present = true;
const char* p = e;
while (*p == ' ' || *p == '\t') p++;
char* end = NULL;
unsigned long v = strtoul(p, &end, 10);
if (end) while (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n') end++;
if (end == p || (end && *end) || *p == '-') {
LOG_MSG("NETSHIM: %s=\"%s\" is not a plain non-negative number -- IGNORED (treated as 0)",
name, e);
return 0;
}
if (v > 100000ul) { /* a 100s one-way delay is a typo, not a test point */
LOG_MSG("NETSHIM: %s=%lu is absurd -- clamped to 100000", name, v);
v = 100000ul;
}
return (unsigned)v;
}
/* Queue-depth tail for the throttled TX/RX log lines; empty when off, so an
* unshimmed run's logs are unchanged. */
static const char* shim_qtail() {
static char t[64];
if (!shim_on) return "";
if (shim_loss_pct)
snprintf(t, sizeof(t), " q=%u/%u lost=%lu",
(unsigned)shim_txq.size(), (unsigned)shim_rxq.size(), shim_lost);
else
snprintf(t, sizeof(t), " q=%u/%u",
(unsigned)shim_txq.size(), (unsigned)shim_rxq.size());
return t;
}
PcapEthernetConnection::PcapEthernetConnection()
: EthernetConnection()
{
}
PcapEthernetConnection::~PcapEthernetConnection()
{
if(adhandle) pcap_close(adhandle);
}
bool PcapEthernetConnection::Initialize(Section* config)
{
Section_prop *section = static_cast<Section_prop*>(config);
const char* realnicstring = section->Get_string("realnic");
#ifdef WIN32
if(!LoadPcapLibrary()) {
return false;
}
#endif
// find out which pcap device to use
pcap_if_t *alldevs;
pcap_if_t *currentdev = NULL;
char errbuf[PCAP_ERRBUF_SIZE];
unsigned int userdev;
#ifdef WIN32
if (pcap_findalldevs_ex(pcap_src_if_string, NULL, &alldevs, errbuf) == -1)
#else
if (pcap_findalldevs(&alldevs, errbuf) == -1)
#endif
{
niclist = "Cannot enumerate network interfaces: "+std::string(errbuf);
LOG_MSG("%s", niclist.c_str());
return false;
}
{
Bitu i = 0;
niclist = "Network Interface List\n-------------------------------------------------------------\n";
for(currentdev=alldevs; currentdev!=NULL; currentdev=currentdev->next) {
const char* desc = "no description";
if(currentdev->description) desc=currentdev->description;
i++;
niclist+=(i<10?" ":"")+std::to_string(i)+" "+currentdev->name+"\n ("+desc+")\n";
}
}
if (!strcasecmp(realnicstring,"list")) {
// print list and quit
std::istringstream in(("\n"+niclist+"\n").c_str());
if (in) for (std::string line; std::getline(in, line); )
LOG_MSG("%s", line.c_str());
LOG_MSG("NOTE: No interface was chosen because the realnic setting is set to 'list', set realnic to the number of the interface you want to use.");
pcap_freealldevs(alldevs);
return false;
} else if(1==sscanf(realnicstring,"%u",&userdev)) {
// user passed us a number
Bitu i = 0;
currentdev=alldevs;
while(currentdev!=NULL) {
i++;
if(i==userdev) break;
else currentdev=currentdev->next;
}
} else {
// user might have passed a piece of name
for(currentdev=alldevs; currentdev!=NULL; currentdev=currentdev->next) {
if(strstr(currentdev->name,realnicstring)) {
break;
}else if(currentdev->description!=NULL &&
strstr(currentdev->description,realnicstring)) {
break;
}
}
}
if(currentdev==NULL) {
LOG_MSG("Unable to find network interface - check realnic parameter\n");
pcap_freealldevs(alldevs);
return false;
}
// print out which interface we are going to use
const char* desc = "no description";
if(currentdev->description) desc=currentdev->description;
LOG_MSG("Using Network interface:\n%s\n(%s)\n",currentdev->name,desc);
const char *timeoutstr = section->Get_string("timeout");
char *end;
int timeout = -1;
if (!strlen(timeoutstr)||(timeoutstr[0]!='-'&&!isdigit(timeoutstr[0]))) { // Default timeout values
#ifdef MACOSX
timeout = 3000; // For macOS, use 3000ms as it does not appear to support -1
#else
timeout = -1; // For other platforms, use -1 which should mean "non-blocking mode"
#endif
} else
timeout = strtoul(timeoutstr,&end,10);
// attempt to open it
#ifdef WIN32
if ( (adhandle= pcap_open(
currentdev->name, // name of the device
65536, // portion of the packet to capture
// 65536 = whole packet
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
timeout, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
#else
/*pcap_t *pcap_open_live(const char *device, int snaplen,
int promisc, int to_ms, char *errbuf)*/
if ( (adhandle= pcap_open_live(
currentdev->name, // name of the device
65536, // portion of the packet to capture
// 65536 = whole packet
true, // promiscuous mode
timeout, // read timeout
errbuf // error buffer
) ) == NULL)
#endif
{
LOG_MSG("\nUnable to open the interface: %s.", errbuf);
pcap_freealldevs(alldevs);
return false;
}
pcap_freealldevs(alldevs);
#ifndef WIN32
pcap_setnonblock(adhandle,1,errbuf);
#endif
// A real NE2000 in non-promiscuous mode hears only its own MAC plus
// broadcast; this promiscuous tap otherwise hands the guest EVERY frame
// on the host NIC. On a live LAN the host's own bulk traffic wedges the
// DOS-era stack before it boots (netnub sits at "discarding..."), so
// filter kernel-side and the flood never reaches the emulation. The
// guest stack is unicast+ARP only: multicast is deliberately excluded,
// and "not ether src" drops npcap's loopback of our own injected frames
// (a real NIC never hears its own TX). Any failure = run unfiltered,
// i.e. the old behavior.
#ifdef WIN32
const bool have_filter_api = (PacketCompile != 0 && PacketSetFilter != 0);
#else
const bool have_filter_api = true;
#endif
const char *macstr = NULL;
Section_prop *ne2ksec = control ? static_cast<Section_prop*>(control->GetSection("ne2000")) : NULL;
if (ne2ksec) macstr = ne2ksec->Get_string("macaddr");
unsigned int m[6];
if (have_filter_api && macstr &&
sscanf(macstr, "%x:%x:%x:%x:%x:%x", &m[0],&m[1],&m[2],&m[3],&m[4],&m[5]) == 6) {
char fexp[160];
snprintf(fexp, sizeof(fexp),
"(ether dst %02x:%02x:%02x:%02x:%02x:%02x or ether broadcast)"
" and not ether src %02x:%02x:%02x:%02x:%02x:%02x",
m[0],m[1],m[2],m[3],m[4],m[5], m[0],m[1],m[2],m[3],m[4],m[5]);
struct bpf_program fprog;
if (pcap_compile(adhandle, &fprog, fexp, 1, (bpf_u_int32)0xffffffffu) == 0) {
if (pcap_setfilter(adhandle, &fprog) == 0)
LOG_MSG("PCAP RX filter active: %s", fexp);
else
LOG_MSG("PCAP setfilter failed (%s) -- running unfiltered", pcap_geterr(adhandle));
#ifdef WIN32
if (PacketFreecode)
#endif
pcap_freecode(&fprog);
} else
LOG_MSG("PCAP filter compile failed (%s) -- running unfiltered", pcap_geterr(adhandle));
} else
LOG_MSG("PCAP RX filter NOT installed (%s) -- promiscuous, expect LAN noise",
have_filter_api ? "no ne2000 macaddr" : "wpcap lacks pcap_compile");
/* Latency-tolerance gate: read the knobs once, and say out loud what was
* applied so a test run's logs are self-describing months later. */
shim_txq.clear(); shim_rxq.clear();
shim_tx_release = shim_rx_release = 0;
shim_lost = 0;
shim_oversize_warned = false;
shim_delay_ms = shim_jitter_ms = shim_loss_pct = 0;
bool shim_env_seen = false;
shim_delay_ms = shim_knob("VWE_NET_DELAY_MS", shim_env_seen);
shim_jitter_ms = shim_knob("VWE_NET_JITTER_MS", shim_env_seen);
shim_loss_pct = shim_knob("VWE_NET_LOSS_PCT", shim_env_seen);
if (shim_loss_pct > 100) shim_loss_pct = 100;
shim_on = (shim_delay_ms || shim_jitter_ms || shim_loss_pct);
if (shim_on) {
shim_rng = (uint32_t)shim_now_ms() | 1u; /* xorshift is stuck at 0 */
LOG_MSG("NETSHIM ACTIVE: delay=%ums each way (+%ums on every RTT through this pod), "
"jitter=0..%ums monotonic, loss=%u%% -- Phase 0 latency gate, see NET-NOTES.md",
shim_delay_ms, shim_delay_ms * 2, shim_jitter_ms, shim_loss_pct);
} else if (shim_env_seen) {
/* All knobs zero is a legitimate control point in the matrix -- but it
* is ALSO exactly what a dosbox-x.exe built WITHOUT this file looks
* like, and vpx-device/ is only the version-controlled copy (it has to
* be copied into src/src/misc/ before `make`). Announcing the inert
* case is what tells the operator the binary really does contain the
* shim: VWE_NET_* set and NO "NETSHIM" line at all means it does not,
* and the whole matrix would otherwise read as "0ms, no effect". */
LOG_MSG("NETSHIM INERT: VWE_NET_* present but every knob is 0 "
"-- baseline/control run, no delay applied (shim IS compiled in)");
}
return true;
}
void PcapEthernetConnection::SendPacket(const uint8_t* packet, int len)
{
static unsigned long txn = 0;
if (((++txn) & (txn - 1)) == 0) /* powers of 2, avoid spam */
LOG_MSG("PCAP TX #%lu len=%d dst=%02x:%02x:%02x src=%02x:%02x:%02x%s",
txn, len, packet[0],packet[1],packet[2], packet[6],packet[7],packet[8],
shim_qtail());
if (shim_on && shim_hold(shim_txq, shim_tx_release, packet, len)) return;
int ret = pcap_sendpacket(adhandle, packet, len);
if(ret) LOG_MSG("PCAP error: %s", pcap_geterr(adhandle));
}
void PcapEthernetConnection::GetPackets(std::function<void(const uint8_t*, int)> callback)
{
int res;
struct pcap_pkthdr *header;
u_char *pkt_data;
static unsigned long rxn = 0;
/* Shim TX first: SendPacket parked these, and this handler is the only
* thing that runs often enough to release them on time. */
if (shim_on) {
uint64_t now = shim_now_ms();
while (!shim_txq.empty() && shim_txq.front().release_ms <= now) {
const DelayedFrame& f = shim_txq.front();
if (pcap_sendpacket(adhandle, f.data, f.len))
LOG_MSG("PCAP error: %s", pcap_geterr(adhandle));
shim_txq.pop_front();
}
}
//#if 0
while((res = pcap_next_ex( adhandle, &header, (const u_char **)&pkt_data)) > 0) {
if (((++rxn) & (rxn - 1)) == 0)
LOG_MSG("PCAP RX #%lu len=%d dst=%02x:%02x:%02x src=%02x:%02x:%02x%s",
rxn, header->len, pkt_data[0],pkt_data[1],pkt_data[2],
pkt_data[6],pkt_data[7],pkt_data[8], shim_qtail());
if (shim_on && shim_hold(shim_rxq, shim_rx_release, pkt_data, (int)header->len)) continue;
callback(pkt_data, header->len);
}
//#endif
if (shim_on) {
uint64_t now = shim_now_ms();
while (!shim_rxq.empty() && shim_rxq.front().release_ms <= now) {
const DelayedFrame& f = shim_rxq.front();
callback(f.data, f.len);
shim_rxq.pop_front();
}
}
}
#endif