mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
452 lines
12 KiB
C++
452 lines
12 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "common/Assertions.h"
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
#ifdef _WIN32
|
|
#include "common/RedtapeWindows.h"
|
|
#include "common/StringUtil.h"
|
|
#include <WinSock2.h>
|
|
#include <iphlpapi.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include "pcap_io.h"
|
|
#include "DEV9.h"
|
|
#include "AdapterUtils.h"
|
|
#include "net.h"
|
|
#include "PacketReader/EthernetFrame.h"
|
|
#include "PacketReader/EthernetFrameEditor.h"
|
|
#include "PacketReader/ARP/ARP_PacketEditor.h"
|
|
#ifndef PCAP_NETMASK_UNKNOWN
|
|
#define PCAP_NETMASK_UNKNOWN 0xffffffff
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#define PCAPPREFIX "\\Device\\NPF_"
|
|
#endif
|
|
|
|
using namespace PacketReader;
|
|
using namespace PacketReader::ARP;
|
|
using namespace PacketReader::IP;
|
|
|
|
PCAPAdapter::PCAPAdapter()
|
|
: NetAdapter()
|
|
{
|
|
if (!EmuConfig.DEV9.EthEnable)
|
|
return;
|
|
#ifdef _WIN32
|
|
if (!load_pcap())
|
|
return;
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
std::string pcapAdapter = PCAPPREFIX + EmuConfig.DEV9.EthDevice;
|
|
#else
|
|
std::string pcapAdapter = EmuConfig.DEV9.EthDevice;
|
|
#endif
|
|
|
|
switched = EmuConfig.DEV9.EthApi == Pcsx2Config::DEV9Options::NetApi::PCAP_Switched;
|
|
|
|
if (!InitPCAP(pcapAdapter, switched))
|
|
{
|
|
Console.Error("DEV9: Can't open Device '%s'", EmuConfig.DEV9.EthDevice.c_str());
|
|
return;
|
|
}
|
|
|
|
AdapterUtils::Adapter adapter;
|
|
AdapterUtils::AdapterBuffer buffer;
|
|
std::optional<MAC_Address> adMAC = std::nullopt;
|
|
const bool foundAdapter = AdapterUtils::GetAdapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer);
|
|
if (foundAdapter)
|
|
adMAC = AdapterUtils::GetAdapterMAC(&adapter);
|
|
else
|
|
Console.Error("DEV9: Failed to get adapter information");
|
|
|
|
if (adMAC.has_value())
|
|
{
|
|
hostMAC = adMAC.value();
|
|
MAC_Address newMAC = ps2MAC;
|
|
|
|
//Lets take the hosts last 2 bytes to make it unique on Xlink
|
|
newMAC.bytes[5] = hostMAC.bytes[4];
|
|
newMAC.bytes[4] = hostMAC.bytes[5];
|
|
|
|
SetMACAddress(&newMAC);
|
|
}
|
|
else
|
|
{
|
|
Console.Error("DEV9: Failed to get MAC address for adapter");
|
|
pcap_close(hpcap);
|
|
hpcap = nullptr;
|
|
return;
|
|
}
|
|
|
|
if (switched && !SetMACSwitchedFilter(ps2MAC))
|
|
{
|
|
pcap_close(hpcap);
|
|
hpcap = nullptr;
|
|
Console.Error("DEV9: Can't open Device '%s'", EmuConfig.DEV9.EthDevice.c_str());
|
|
return;
|
|
}
|
|
|
|
if (foundAdapter)
|
|
InitInternalServer(&adapter);
|
|
else
|
|
InitInternalServer(nullptr);
|
|
}
|
|
AdapterOptions PCAPAdapter::GetAdapterOptions()
|
|
{
|
|
return AdapterOptions::None;
|
|
}
|
|
bool PCAPAdapter::blocks()
|
|
{
|
|
pxAssert(hpcap);
|
|
return blocking;
|
|
}
|
|
bool PCAPAdapter::isInitialised()
|
|
{
|
|
return hpcap != nullptr;
|
|
}
|
|
//gets a packet.rv :true success
|
|
bool PCAPAdapter::recv(NetPacket* pkt)
|
|
{
|
|
pxAssert(hpcap);
|
|
|
|
if (!blocking && NetAdapter::recv(pkt))
|
|
return true;
|
|
|
|
pcap_pkthdr* header;
|
|
const u_char* pkt_data;
|
|
|
|
// pcap bridged will pick up packets not intended for us, returning false on those packets will incur a 1ms wait.
|
|
// This delays getting packets we need, so instead loop untill a valid packet, or no packet, is returned from pcap_next_ex.
|
|
while (pcap_next_ex(hpcap, &header, &pkt_data) > 0)
|
|
{
|
|
// 1518 is the largest Ethernet frame we can get using an MTU of 1500 (assuming no VLAN tagging).
|
|
// This includes the FCS, which should be trimmed (PS2 SDK dosn't allow extra space for this).
|
|
if (header->len > 1518)
|
|
{
|
|
Console.Error("DEV9: Dropped jumbo frame of size: %u", header->len);
|
|
continue;
|
|
}
|
|
|
|
pxAssert(header->len == header->caplen);
|
|
|
|
memcpy(pkt->buffer, pkt_data, header->len);
|
|
pkt->size = static_cast<int>(header->len);
|
|
|
|
if (!switched)
|
|
SetMACBridgedRecv(pkt);
|
|
|
|
if (VerifyPkt(pkt, header->len))
|
|
{
|
|
HandleFrameCheckSequence(pkt);
|
|
|
|
// FCS (if present) has been removed, apply correct limit
|
|
if (pkt->size > 1514)
|
|
{
|
|
Console.Error("DEV9: Dropped jumbo frame of size: %u", pkt->size);
|
|
continue;
|
|
}
|
|
|
|
InspectRecv(pkt);
|
|
return true;
|
|
}
|
|
// continue.
|
|
}
|
|
|
|
return false;
|
|
}
|
|
//sends the packet .rv :true success
|
|
bool PCAPAdapter::send(NetPacket* pkt)
|
|
{
|
|
pxAssert(hpcap);
|
|
|
|
InspectSend(pkt);
|
|
if (NetAdapter::send(pkt))
|
|
return true;
|
|
|
|
// TODO: loopback broadcast packets to host pc in switched mode.
|
|
if (!switched)
|
|
SetMACBridgedSend(pkt);
|
|
|
|
if (pcap_sendpacket(hpcap, (u_char*)pkt->buffer, pkt->size))
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
void PCAPAdapter::reloadSettings()
|
|
{
|
|
AdapterUtils::Adapter adapter;
|
|
AdapterUtils::AdapterBuffer buffer;
|
|
if (AdapterUtils::GetAdapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer))
|
|
ReloadInternalServer(&adapter);
|
|
else
|
|
ReloadInternalServer(nullptr);
|
|
}
|
|
|
|
PCAPAdapter::~PCAPAdapter()
|
|
{
|
|
if (hpcap)
|
|
{
|
|
pcap_close(hpcap);
|
|
hpcap = nullptr;
|
|
}
|
|
}
|
|
|
|
std::vector<AdapterEntry> PCAPAdapter::GetAdapters()
|
|
{
|
|
std::vector<AdapterEntry> nic;
|
|
|
|
#ifdef _WIN32
|
|
if (!load_pcap())
|
|
return nic;
|
|
#endif
|
|
|
|
char errbuf[PCAP_ERRBUF_SIZE];
|
|
pcap_if_t* alldevs;
|
|
pcap_if_t* d;
|
|
|
|
if (pcap_findalldevs(&alldevs, errbuf) == -1)
|
|
return nic;
|
|
|
|
d = alldevs;
|
|
while (d != NULL)
|
|
{
|
|
AdapterEntry entry;
|
|
entry.type = Pcsx2Config::DEV9Options::NetApi::PCAP_Switched;
|
|
#ifdef _WIN32
|
|
//guid
|
|
if (!std::string_view(d->name).starts_with(PCAPPREFIX))
|
|
{
|
|
Console.Error("PCAP: Unexpected Device: ", d->name);
|
|
d = d->next;
|
|
continue;
|
|
}
|
|
|
|
entry.guid = std::string(&d->name[strlen(PCAPPREFIX)]);
|
|
|
|
IP_ADAPTER_ADDRESSES adapterInfo;
|
|
AdapterUtils::AdapterBuffer buffer;
|
|
|
|
if (AdapterUtils::GetAdapter(entry.guid, &adapterInfo, &buffer))
|
|
entry.name = StringUtil::WideStringToUTF8String(std::wstring(adapterInfo.FriendlyName));
|
|
else
|
|
{
|
|
//have to use description
|
|
//NPCAP 1.10 is using a version of pcap that doesn't
|
|
//allow us to set it to use UTF8
|
|
//see https://github.com/nmap/npcap/issues/276
|
|
//We have to convert from ANSI to wstring, to then convert to UTF8
|
|
const int len_desc = strlen(d->description) + 1;
|
|
const int len_buf = MultiByteToWideChar(CP_ACP, 0, d->description, len_desc, nullptr, 0);
|
|
|
|
std::unique_ptr<wchar_t[]> buf = std::make_unique<wchar_t[]>(len_buf);
|
|
MultiByteToWideChar(CP_ACP, 0, d->description, len_desc, buf.get(), len_buf);
|
|
|
|
entry.name = StringUtil::WideStringToUTF8String(std::wstring(buf.get()));
|
|
}
|
|
#else
|
|
entry.name = std::string(d->name);
|
|
entry.guid = std::string(d->name);
|
|
#endif
|
|
|
|
nic.push_back(entry);
|
|
entry.type = Pcsx2Config::DEV9Options::NetApi::PCAP_Bridged;
|
|
nic.push_back(entry);
|
|
d = d->next;
|
|
}
|
|
|
|
return nic;
|
|
}
|
|
|
|
// Opens device for capture and sets non-blocking.
|
|
bool PCAPAdapter::InitPCAP(const std::string& adapter, bool promiscuous)
|
|
{
|
|
char errbuf[PCAP_ERRBUF_SIZE];
|
|
Console.WriteLn("DEV9: Opening adapter '%s'...", adapter.c_str());
|
|
|
|
// Open the adapter.
|
|
if ((hpcap = pcap_open_live(adapter.c_str(), // Name of the device.
|
|
65536, // portion of the packet to capture.
|
|
// 65536 grants that the whole packet will be captured on all the MACs.
|
|
promiscuous ? 1 : 0,
|
|
1, // Read timeout.
|
|
errbuf // Error buffer.
|
|
)) == nullptr)
|
|
{
|
|
Console.Error("DEV9: %s", errbuf);
|
|
Console.Error("DEV9: Unable to open the adapter. %s is not supported by pcap", adapter.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (pcap_setnonblock(hpcap, 1, errbuf) == -1)
|
|
{
|
|
Console.Error("DEV9: Error setting non-blocking: %s", pcap_geterr(hpcap));
|
|
Console.Error("DEV9: Continuing in blocking mode");
|
|
blocking = true;
|
|
}
|
|
else
|
|
blocking = false;
|
|
|
|
// Validate.
|
|
const int dlt = pcap_datalink(hpcap);
|
|
const char* dlt_name = pcap_datalink_val_to_name(dlt);
|
|
|
|
Console.WriteLn("DEV9: Device uses DLT %d: %s", dlt, dlt_name);
|
|
switch (dlt)
|
|
{
|
|
case DLT_EN10MB:
|
|
//case DLT_IEEE802_11:
|
|
break;
|
|
default:
|
|
Console.Error("ERROR: Unsupported DataLink Type (%d): %s", dlt, dlt_name);
|
|
pcap_close(hpcap);
|
|
hpcap = nullptr;
|
|
return false;
|
|
}
|
|
|
|
Console.WriteLn("DEV9: Adapter Ok.");
|
|
return true;
|
|
}
|
|
|
|
bool PCAPAdapter::SetMACSwitchedFilter(MAC_Address mac)
|
|
{
|
|
bpf_program fp;
|
|
|
|
char filter[128];
|
|
std::snprintf(filter, std::size(filter), "ether broadcast or ether dst %.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
|
|
mac.bytes[0], mac.bytes[1], mac.bytes[2], mac.bytes[3], mac.bytes[4], mac.bytes[5]);
|
|
|
|
if (pcap_compile(hpcap, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) == -1)
|
|
{
|
|
Console.Error("DEV9: Error calling pcap_compile: %s", pcap_geterr(hpcap));
|
|
return false;
|
|
}
|
|
|
|
int setFilterRet;
|
|
if ((setFilterRet = pcap_setfilter(hpcap, &fp)) == -1)
|
|
Console.Error("DEV9: Error setting filter: %s", pcap_geterr(hpcap));
|
|
|
|
pcap_freecode(&fp);
|
|
return setFilterRet != -1;
|
|
}
|
|
|
|
void PCAPAdapter::SetMACBridgedRecv(NetPacket* pkt)
|
|
{
|
|
EthernetFrameEditor frame(pkt);
|
|
if (frame.GetProtocol() == static_cast<u16>(EtherType::IPv4)) // IP
|
|
{
|
|
// Compare DEST IP in IP with the PS2's IP, if they match, change DEST MAC to ps2MAC.
|
|
PayloadPtrEditor* payload = frame.GetPayload();
|
|
IP_Packet ippkt(payload->data, payload->GetLength());
|
|
if (ippkt.destinationIP == ps2IP)
|
|
frame.SetDestinationMAC(ps2MAC);
|
|
}
|
|
if (frame.GetProtocol() == static_cast<u16>(EtherType::ARP)) // ARP
|
|
{
|
|
// Compare DEST IP in ARP with the PS2's IP, if they match, DEST MAC to ps2MAC on both ARP and ETH Packet headers.
|
|
ARP_PacketEditor arpPkt(frame.GetPayload());
|
|
if (*(IP_Address*)arpPkt.TargetProtocolAddress() == ps2IP)
|
|
{
|
|
frame.SetDestinationMAC(ps2MAC);
|
|
*(MAC_Address*)arpPkt.TargetHardwareAddress() = ps2MAC;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PCAPAdapter::SetMACBridgedSend(NetPacket* pkt)
|
|
{
|
|
EthernetFrameEditor frame(pkt);
|
|
if (frame.GetProtocol() == static_cast<u16>(EtherType::IPv4)) // IP
|
|
{
|
|
PayloadPtrEditor* payload = frame.GetPayload();
|
|
IP_Packet ippkt(payload->data, payload->GetLength());
|
|
ps2IP = ippkt.sourceIP;
|
|
}
|
|
if (frame.GetProtocol() == static_cast<u16>(EtherType::ARP)) // ARP
|
|
{
|
|
ARP_PacketEditor arpPkt(frame.GetPayload());
|
|
ps2IP = *(IP_Address*)arpPkt.SenderProtocolAddress();
|
|
*(MAC_Address*)arpPkt.SenderHardwareAddress() = hostMAC;
|
|
}
|
|
frame.SetSourceMAC(hostMAC);
|
|
}
|
|
|
|
/*
|
|
* Strips the Frame Check Sequence if we manage to capture it.
|
|
*
|
|
* On Windows, (some?) Intel NICs can be configured to capture FCS.
|
|
*
|
|
* Linux can be configure to capture FCS, using `ethtool -K <interface> rx-fcs on` on supported devices.
|
|
* Support for capturing FCS can be checked with `ethtool -k <interface> | grep rx-fcs`.
|
|
* if it's `off [Fixed]`, then the interface/driver dosn't support capturing FCS.
|
|
*
|
|
* BSD based systems might capture FCS by default.
|
|
*
|
|
* Packets sent by host won't have FCS, We identify these packets by checking the source MAC address.
|
|
* Packets sent by another application via packet injection also won't have FCS and may not match the adapter MAC.
|
|
*/
|
|
void PCAPAdapter::HandleFrameCheckSequence(NetPacket* pkt)
|
|
{
|
|
EthernetFrameEditor frame(pkt);
|
|
if (frame.GetSourceMAC() == hostMAC)
|
|
return;
|
|
|
|
// There is a (very) low chance of the last 4 bytes of payload somehow acting as a valid checksum for the whole Ethernet frame.
|
|
// For EtherTypes we already can parse, trim the Ethernet frame based on the payload length.
|
|
|
|
int payloadSize = -1;
|
|
if (frame.GetProtocol() == static_cast<u16>(EtherType::IPv4)) // IP
|
|
{
|
|
PayloadPtrEditor* payload = frame.GetPayload();
|
|
IP_Packet ippkt(payload->data, payload->GetLength());
|
|
payloadSize = ippkt.GetLength();
|
|
}
|
|
if (frame.GetProtocol() == static_cast<u16>(EtherType::ARP)) // ARP
|
|
{
|
|
ARP_PacketEditor arpPkt(frame.GetPayload());
|
|
payloadSize = arpPkt.GetLength();
|
|
}
|
|
|
|
if (payloadSize != -1)
|
|
{
|
|
// Minumum frame size is 60 + 4 byte FCS.
|
|
// Virtual NICs may omit this padding, so check we arn't increasing pkt size.
|
|
payloadSize = std::min(std::max(payloadSize, 60 - frame.headerLength), pkt->size);
|
|
|
|
pkt->size = payloadSize + frame.headerLength;
|
|
return;
|
|
}
|
|
|
|
// Ethertype unknown, rely on checking for a FCS.
|
|
if (ValidateEtherFrame(pkt))
|
|
pkt->size -= 4;
|
|
}
|
|
|
|
bool PCAPAdapter::ValidateEtherFrame(NetPacket* pkt)
|
|
{
|
|
u32 crc = 0xFFFFFFFF;
|
|
|
|
for (int i = 0; i < pkt->size; i++)
|
|
{
|
|
// Neads unsigned value
|
|
crc = crc ^ static_cast<u8>(pkt->buffer[i]);
|
|
for (int bit = 0; bit < 8; bit++)
|
|
{
|
|
if ((crc & 1) != 0)
|
|
crc = (crc >> 1) ^ 0xEDB88320;
|
|
else
|
|
crc = (crc >> 1);
|
|
}
|
|
}
|
|
|
|
crc = ~crc;
|
|
|
|
return crc == 0x2144DF1C;
|
|
}
|