Files
CydandClaude Fable 5 a8e463e784 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 <noreply@anthropic.com>
2026-07-10 23:47:05 -05:00

348 lines
12 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>
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
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");
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",
txn, len, packet[0],packet[1],packet[2], packet[6],packet[7],packet[8]);
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;
//#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",
rxn, header->len, pkt_data[0],pkt_data[1],pkt_data[2],
pkt_data[6],pkt_data[7],pkt_data[8]);
callback(pkt_data, header->len);
}
//#endif
}
#endif