From a8e463e78490935f2548db543fadcd92b7fd84ad Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 10 Jul 2026 23:47:05 -0500 Subject: [PATCH] Emulator: kernel BPF RX filter for the pcap NIC + NE2000 accept/drop diagnostics Real-NE2000 semantics: accept only own-MAC unicast + broadcast (multicast excluded -- the guest stack is unicast+ARP only), and drop npcap's loopback of our own injected frames. Without it, a live-LAN host download floods the emulated NIC at wire rate (65k+ frames/min) and wedges netnub before boot ("discarding..."). Filter failure is non-fatal: logs and runs unfiltered. Also log the first 12 unicast accepts/drops with the live PAR registers. Co-Authored-By: Claude Fable 5 --- emulator/vpx-device/ethernet_pcap.cpp | 62 +++++++++++++++++++++++++++ emulator/vpx-device/ne2000.cpp | 14 ++++++ 2 files changed, 76 insertions(+) diff --git a/emulator/vpx-device/ethernet_pcap.cpp b/emulator/vpx-device/ethernet_pcap.cpp index 607aca8..af860a8 100644 --- a/emulator/vpx-device/ethernet_pcap.cpp +++ b/emulator/vpx-device/ethernet_pcap.cpp @@ -24,6 +24,7 @@ #include "dosbox.h" #include "logging.h" #include "support.h" /* strcasecmp */ +#include "control.h" /* [ne2000] macaddr for the RX filter */ #include extern std::string niclist; @@ -40,6 +41,9 @@ extern std::string niclist; #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; @@ -48,6 +52,9 @@ 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; @@ -105,6 +112,19 @@ bool LoadPcapLibrary() { 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 @@ -252,6 +272,48 @@ bool PcapEthernetConnection::Initialize(Section* config) #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(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"); + return true; } diff --git a/emulator/vpx-device/ne2000.cpp b/emulator/vpx-device/ne2000.cpp index 84d1435..908d6d9 100644 --- a/emulator/vpx-device/ne2000.cpp +++ b/emulator/vpx-device/ne2000.cpp @@ -1412,8 +1412,22 @@ bx_ne2k_c::rx_frame(const void *buf, unsigned io_len) return; } } else if (0 != memcmp(buf, BX_NE2K_THIS s.physaddr, 6)) { + static unsigned drop_n = 0; + if (drop_n < 12) { drop_n++; + LOG_MSG("NE2000 rx DROP unicast dst=%02x:%02x:%02x:%02x:%02x:%02x != PAR=%02x:%02x:%02x:%02x:%02x:%02x", + pktbuf[0],pktbuf[1],pktbuf[2],pktbuf[3],pktbuf[4],pktbuf[5], + BX_NE2K_THIS s.physaddr[0],BX_NE2K_THIS s.physaddr[1],BX_NE2K_THIS s.physaddr[2], + BX_NE2K_THIS s.physaddr[3],BX_NE2K_THIS s.physaddr[4],BX_NE2K_THIS s.physaddr[5]); + } return; } + else { + static unsigned uni_n = 0; + if (uni_n < 12) { uni_n++; + LOG_MSG("NE2000 rx ACCEPT unicast len=%d dst=%02x:%02x:%02x:%02x:%02x:%02x", + (int)io_len, pktbuf[0],pktbuf[1],pktbuf[2],pktbuf[3],pktbuf[4],pktbuf[5]); + } + } } else { BX_DEBUG(("rx_frame promiscuous receive")); }