From c82c28e3ebc004039e7e72f2f5943dfbc232dd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindel=C3=B6w?= Date: Fri, 23 Oct 2026 16:06:53 +0200 Subject: [PATCH 1/4] Add raw packet tests for all plattforms --- tests/tests.cpp | 391 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 258 insertions(+), 133 deletions(-) diff --git a/tests/tests.cpp b/tests/tests.cpp index 41087aa..6720ada 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -68,6 +68,42 @@ static inline double log10(unsigned int x) static constexpr size_t kInterfaceListCapacity = 128; static constexpr size_t kAddressListCapacity = 128; +// Packet sockets need Linux with CAP_NET_RAW or Windows with Npcap. The packet tests call this after tcs_lib_init() and +// return when it is false, which leaves a doctest warning that the test was not run. The interface is the first one +// listed, which is lo on Linux and the first adapter with a MAC address on Windows. +static bool packet_sockets_available(struct TcsInterfaceAddress* out_interface) +{ + struct TcsAddress packet_address = TCS_ADDRESS_NONE; + packet_address.family = TCS_FAMILY_PACKET; + bool supported = tcs_address_is_supported(&packet_address); + WARN_MESSAGE(supported, "packet sockets are not supported on this platform, the test was not run"); + if (!supported) + return false; + + static struct TcsInterfaceAddress addrs[kAddressListCapacity]; + size_t addresses_found = 0; + CHECK(tcs_address_list(0, TCS_FAMILY_PACKET, addrs, kAddressListCapacity, &addresses_found) == TCS_SUCCESS); + WARN_MESSAGE(addresses_found > 0, "no packet interface was found, the test was not run"); + if (addresses_found == 0) + return false; + + // Binding is where the permission check happens, on Linux at socket() and on Windows when Npcap opens the adapter + packet_address.data.packet.interface_id = addrs[0].iface.id; + TcsSocket probe = TCS_SOCKET_INVALID; + TcsResult probe_res = tcs_socket_packet(&probe, &packet_address, TCS_SOCKET_DGRAM); + WARN_MESSAGE(probe_res != TCS_ERROR_PERMISSION_DENIED, + "packet sockets need root or CAP_NET_RAW, the test was not run"); + if (probe_res == TCS_ERROR_PERMISSION_DENIED) + return false; + CHECK(probe_res == TCS_SUCCESS); + if (probe_res != TCS_SUCCESS) + return false; + CHECK(tcs_close(&probe) == TCS_SUCCESS); + + *out_interface = addrs[0]; + return true; +} + int main(int argc, char** argv) { #ifdef _MSC_VER @@ -1742,23 +1778,6 @@ TEST_CASE("tcs_opt_membership_drop_str") REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); } -#if defined(__linux__) -TEST_CASE("Simple AVTP talker") -{ - // Setup - REQUIRE(tcs_lib_init() == TCS_SUCCESS); - - TcsSocket socket = TCS_SOCKET_INVALID; - CHECK(tcs_socket(&socket, TCS_FAMILY_PACKET, TCS_SOCKET_RAW, TCS_PROTOCOL_ETH_ALL) == TCS_SUCCESS); - TcsAddress address = TCS_ADDRESS_NONE; - tcs_address_parse("", &address); - - CHECK(tcs_close(&socket) == TCS_SUCCESS); - - REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); -} -#endif - // ************* TINY DATA STRUCTURE (TDS) UNIT TESTS ************* TEST_CASE("TdsList capacity hysteresis") @@ -1976,13 +1995,18 @@ TEST_CASE("TdsMap_int_double add and remove") CHECK(tds_map_mymap_destroy(&map) == 0); } -#if defined(__linux__) const uint8_t AVTP_DEST_ADDR[6] = {0x91, 0xe0, 0xf0, 0x00, 0xfe, 0x00}; TEST_CASE("Create packet socket") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given TcsSocket socket = TCS_SOCKET_INVALID; @@ -2002,6 +2026,12 @@ TEST_CASE("Create AVTP socket") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given TcsSocket socket = TCS_SOCKET_INVALID; @@ -2021,6 +2051,12 @@ TEST_CASE("AVTP Create talker socket sendto") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given TcsSocket socket = TCS_SOCKET_INVALID; @@ -2028,16 +2064,15 @@ TEST_CASE("AVTP Create talker socket sendto") // When CHECK(tcs_socket(&socket, TCS_FAMILY_PACKET, TCS_SOCKET_DGRAM, 0x22F0) == TCS_SUCCESS); - CHECK(tcs_opt_priority_set(socket, 6) == TCS_SUCCESS); // Set priority to 6 (VLAN priority 6) - static struct TcsInterfaceAddress addrs[kAddressListCapacity]; - size_t addresses_found = 0; - CHECK(tcs_address_list(0, TCS_FAMILY_PACKET, addrs, kAddressListCapacity, &addresses_found) == TCS_SUCCESS); - CHECK(addresses_found > 0); + CHECK_POSIX(tcs_opt_priority_set(socket, 6) == TCS_SUCCESS); // VLAN priority 6, SO_PRIORITY is Linux only + // Linux can send on an unbound packet socket, Windows opens the adapter in bind TcsAddress address = TCS_ADDRESS_NONE; address.family = TCS_FAMILY_PACKET; - address.data.packet.interface_id = addrs[0].iface.id; + address.data.packet.interface_id = iface.iface.id; address.data.packet.protocol = 0x22F0; + CHECK(tcs_bind(socket, &address) == TCS_SUCCESS); + memcpy(address.data.packet.mac, AVTP_DEST_ADDR, sizeof(AVTP_DEST_ADDR)); uint8_t msg[] = "hello world\n"; @@ -2056,6 +2091,12 @@ TEST_CASE("TSN Create talker socket bind") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given TcsSocket socket = TCS_SOCKET_INVALID; @@ -2063,15 +2104,11 @@ TEST_CASE("TSN Create talker socket bind") // When CHECK(tcs_socket(&socket, TCS_FAMILY_PACKET, TCS_SOCKET_RAW, TCS_PROTOCOL_ETH_ALL) == TCS_SUCCESS); - CHECK(tcs_opt_priority_set(socket, 6) == TCS_SUCCESS); // Set priority to 6 (VLAN priority 6) - static struct TcsInterfaceAddress addr[kAddressListCapacity]; - size_t addresses_found = 0; - CHECK(tcs_address_list(0, TCS_FAMILY_PACKET, addr, kAddressListCapacity, &addresses_found) == TCS_SUCCESS); - CHECK(addresses_found > 0); + CHECK_POSIX(tcs_opt_priority_set(socket, 6) == TCS_SUCCESS); // VLAN priority 6, SO_PRIORITY is Linux only TcsAddress address = TCS_ADDRESS_NONE; address.family = TCS_FAMILY_PACKET; - address.data.packet.interface_id = addr[0].iface.id; + address.data.packet.interface_id = iface.iface.id; address.data.packet.protocol = 0x22F0; // Ethertype for AVTP memcpy(address.data.packet.mac, AVTP_DEST_ADDR, sizeof(AVTP_DEST_ADDR)); @@ -2079,11 +2116,11 @@ TEST_CASE("TSN Create talker socket bind") // SOCK_RAW requires a complete Ethernet frame: [6 dst MAC][6 src MAC][2 EtherType][payload] uint8_t frame[64] = {0}; - memcpy(&frame[0], AVTP_DEST_ADDR, 6); // Destination MAC - memcpy(&frame[6], addr[0].address.data.packet.mac, 6); // Source MAC - frame[12] = 0x22; // EtherType high byte (AVTP) - frame[13] = 0xF0; // EtherType low byte - memcpy(&frame[14], "hello world\n", 12); // Payload + memcpy(&frame[0], AVTP_DEST_ADDR, 6); // Destination MAC + memcpy(&frame[6], iface.address.data.packet.mac, 6); // Source MAC + frame[12] = 0x22; // EtherType high byte (AVTP) + frame[13] = 0xF0; // EtherType low byte + memcpy(&frame[14], "hello world\n", 12); // Payload size_t sent_size = 0; CHECK(tcs_send_to(socket, frame, sizeof(frame), TCS_FLAG_NONE, &address, &sent_size) == TCS_SUCCESS); @@ -2100,6 +2137,12 @@ TEST_CASE("TSN Create listener") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given TcsSocket socket = TCS_SOCKET_INVALID; @@ -2107,14 +2150,9 @@ TEST_CASE("TSN Create listener") // When CHECK(tcs_socket(&socket, TCS_FAMILY_PACKET, TCS_SOCKET_RAW, TCS_PROTOCOL_ETH_ALL) == TCS_SUCCESS); - static struct TcsInterfaceAddress addr[kAddressListCapacity]; - size_t addresses_found = 0; - CHECK(tcs_address_list(0, TCS_FAMILY_PACKET, addr, kAddressListCapacity, &addresses_found) == TCS_SUCCESS); - CHECK(addresses_found > 0); - TcsAddress address = TCS_ADDRESS_NONE; address.family = TCS_FAMILY_PACKET; - address.data.packet.interface_id = addr[0].iface.id; + address.data.packet.interface_id = iface.iface.id; memcpy(address.data.packet.mac, AVTP_DEST_ADDR, sizeof(AVTP_DEST_ADDR)); address.data.packet.protocol = 0x22F0; // Ethertype for TSN @@ -2134,6 +2172,12 @@ TEST_CASE("Create DGRAM packet socket with preset") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given TcsSocket socket = TCS_SOCKET_INVALID; @@ -2149,49 +2193,23 @@ TEST_CASE("Create DGRAM packet socket with preset") REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); } -TEST_CASE("tcs_socket_packet bind") -{ - // Setup - REQUIRE(tcs_lib_init() == TCS_SUCCESS); - - // Given - TcsSocket socket = TCS_SOCKET_INVALID; - static struct TcsInterfaceAddress addrs[kAddressListCapacity]; - size_t addresses_found = 0; - CHECK(tcs_address_list(0, TCS_FAMILY_PACKET, addrs, kAddressListCapacity, &addresses_found) == TCS_SUCCESS); - CHECK(addresses_found > 0); - - struct TcsAddress bind_address = TCS_ADDRESS_NONE; - bind_address.family = TCS_FAMILY_PACKET; - bind_address.data.packet.interface_id = addrs[0].iface.id; - bind_address.data.packet.protocol = 0x22F0; - - // When - CHECK(tcs_socket_packet(&socket, &bind_address, TCS_SOCKET_DGRAM) == TCS_SUCCESS); - - // Then - CHECK(socket != TCS_SOCKET_INVALID); - CHECK(tcs_close(&socket) == TCS_SUCCESS); - - // Clean up - REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); -} - TEST_CASE("tcs_socket_packet sendto") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given TcsSocket socket = TCS_SOCKET_INVALID; - static struct TcsInterfaceAddress addrs[kAddressListCapacity]; - size_t addresses_found = 0; - CHECK(tcs_address_list(0, TCS_FAMILY_PACKET, addrs, kAddressListCapacity, &addresses_found) == TCS_SUCCESS); - CHECK(addresses_found > 0); struct TcsAddress bind_address = TCS_ADDRESS_NONE; bind_address.family = TCS_FAMILY_PACKET; - bind_address.data.packet.interface_id = addrs[0].iface.id; + bind_address.data.packet.interface_id = iface.iface.id; bind_address.data.packet.protocol = 0x22F0; CHECK(tcs_socket_packet(&socket, &bind_address, TCS_SOCKET_DGRAM) == TCS_SUCCESS); @@ -2199,7 +2217,7 @@ TEST_CASE("tcs_socket_packet sendto") // When - sendto with explicit destination struct TcsAddress dest_address = TCS_ADDRESS_NONE; dest_address.family = TCS_FAMILY_PACKET; - dest_address.data.packet.interface_id = addrs[0].iface.id; + dest_address.data.packet.interface_id = iface.iface.id; dest_address.data.packet.protocol = 0x22F0; memcpy(dest_address.data.packet.mac, AVTP_DEST_ADDR, sizeof(AVTP_DEST_ADDR)); @@ -2215,45 +2233,6 @@ TEST_CASE("tcs_socket_packet sendto") REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); } -TEST_CASE("tcs_socket_packet_str bind") -{ - // Setup - REQUIRE(tcs_lib_init() == TCS_SUCCESS); - - // Given - TcsSocket socket = TCS_SOCKET_INVALID; - static struct TcsInterface interfaces[kInterfaceListCapacity]; - size_t iface_count = 0; - CHECK(tcs_interface_list(interfaces, kInterfaceListCapacity, &iface_count) == TCS_SUCCESS); - CHECK(iface_count > 0); - - const char* iface_name = NULL; - for (size_t i = 0; i < iface_count; ++i) - { - if (strcmp(interfaces[i].name, "lo") != 0) - { - iface_name = interfaces[i].name; - break; - } - } - if (iface_name == NULL) - { - REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); - return; - } - - // When - CHECK(tcs_socket_packet_str(&socket, iface_name, 0x22F0, TCS_SOCKET_DGRAM) == TCS_SUCCESS); - - // Then - CHECK(socket != TCS_SOCKET_INVALID); - CHECK(tcs_close(&socket) == TCS_SUCCESS); - - // Clean up - REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); -} -#endif - TEST_CASE("tcs_socket_packet invalid arguments old") { // Setup @@ -2272,16 +2251,21 @@ TEST_CASE("tcs_socket_packet invalid arguments old") REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); } -#if defined(__linux__) TEST_CASE("tcs_socket_packet DGRAM") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given struct TcsAddress bind = TCS_ADDRESS_NONE; bind.family = TCS_FAMILY_PACKET; - bind.data.packet.interface_id = 1; // lo + bind.data.packet.interface_id = iface.iface.id; bind.data.packet.protocol = 0x22F0; TcsSocket socket = TCS_SOCKET_INVALID; @@ -2301,11 +2285,17 @@ TEST_CASE("tcs_socket_packet RAW") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given struct TcsAddress bind = TCS_ADDRESS_NONE; bind.family = TCS_FAMILY_PACKET; - bind.data.packet.interface_id = 1; // lo + bind.data.packet.interface_id = iface.iface.id; bind.data.packet.protocol = 0x22F0; TcsSocket socket = TCS_SOCKET_INVALID; @@ -2325,12 +2315,18 @@ TEST_CASE("tcs_socket_packet_str DGRAM") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } // Given TcsSocket socket = TCS_SOCKET_INVALID; // When - CHECK(tcs_socket_packet_str(&socket, "lo", 0x22F0, TCS_SOCKET_DGRAM) == TCS_SUCCESS); + CHECK(tcs_socket_packet_str(&socket, iface.iface.name, 0x22F0, TCS_SOCKET_DGRAM) == TCS_SUCCESS); // Then CHECK(socket != TCS_SOCKET_INVALID); @@ -2344,10 +2340,16 @@ TEST_CASE("tcs_socket_packet multicast add and drop str") { // Setup REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } - // Given - create and bind a DGRAM packet socket on lo + // Given - create and bind a DGRAM packet socket TcsSocket socket = TCS_SOCKET_INVALID; - CHECK(tcs_socket_packet_str(&socket, "lo", 0x22F0, TCS_SOCKET_DGRAM) == TCS_SUCCESS); + CHECK(tcs_socket_packet_str(&socket, iface.iface.name, 0x22F0, TCS_SOCKET_DGRAM) == TCS_SUCCESS); // When/Then - join and leave a multicast MAC group via str CHECK(tcs_opt_membership_add_str(socket, "91:e0:f0:00:fe:00") == TCS_SUCCESS); @@ -2357,7 +2359,6 @@ TEST_CASE("tcs_socket_packet multicast add and drop str") CHECK(tcs_close(&socket) == TCS_SUCCESS); REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); } -#endif TEST_CASE("tcs_socket_packet invalid arguments") { @@ -2407,43 +2408,55 @@ TEST_CASE("tcs_socket_packet_str invalid interface") TEST_CASE("Sentinel handling: TCS_FAMILY_PACKET on unsupported platform") { - // On platforms without AF_PACKET (Windows, macOS, BSDs), TCS_FAMILY_PACKET is a sentinel. - // All entry points that take a family must reject it consistently with TCS_ERROR_NOT_SUPPORTED - // instead of leaking the sentinel into OS calls. + // On platforms without AF_PACKET (macOS, BSDs), TCS_FAMILY_PACKET is a sentinel. All entry points that take a + // family must reject it consistently with TCS_ERROR_NOT_SUPPORTED instead of leaking the sentinel into OS calls. + // On Windows the family is real but needs Npcap at runtime, and without it the contract is the same. REQUIRE(tcs_lib_init() == TCS_SUCCESS); - // Probe whether this platform supports AF_PACKET by trying to create one. + struct TcsAddress packet_addr = TCS_ADDRESS_NONE; + packet_addr.family = TCS_FAMILY_PACKET; + + // Probe whether packet sockets are available by trying to create one. TcsSocket probe = TCS_SOCKET_INVALID; TcsResult probe_res = tcs_socket(&probe, TCS_FAMILY_PACKET, TCS_SOCKET_RAW, TCS_PROTOCOL_ETH_ALL); if (probe_res == TCS_ERROR_NOT_SUPPORTED) { - // Unsupported platform: verify all paths agree on the contract. + // Unsupported: verify all paths agree on the contract. CHECK(probe == TCS_SOCKET_INVALID); + CHECK_FALSE(tcs_address_is_supported(&packet_addr)); - // tcs_address_resolve() must reject the sentinel filter before calling getaddrinfo() + // The preset must fail the same way and leave the socket untouched + struct TcsAddress bind_addr = packet_addr; + bind_addr.data.packet.interface_id = 1; + bind_addr.data.packet.protocol = 0x22F0; + CHECK(tcs_socket_packet(&probe, &bind_addr, TCS_SOCKET_DGRAM) == TCS_ERROR_NOT_SUPPORTED); + CHECK(probe == TCS_SOCKET_INVALID); + + // tcs_address_resolve() must reject the family before calling getaddrinfo() struct TcsAddress resolved = TCS_ADDRESS_NONE; size_t resolved_count = 0; CHECK(tcs_address_resolve("127.0.0.1", TCS_FAMILY_PACKET, &resolved, 1, &resolved_count) == TCS_ERROR_NOT_SUPPORTED); - // tcs_address_list() must skip the unsupported filter (no entries, no error) + // tcs_address_list() must not fail. A sentinel platform lists nothing, Windows lists the adapters' MAC + // addresses since that needs no Npcap. static struct TcsInterfaceAddress ifaddrs[kAddressListCapacity]; size_t ifaddr_count = 0; CHECK(tcs_address_list(0, TCS_FAMILY_PACKET, ifaddrs, kAddressListCapacity, &ifaddr_count) == TCS_SUCCESS); - CHECK(ifaddr_count == 0); + if (TCS_FAMILY_PACKET.native == -1) + CHECK(ifaddr_count == 0); - // sockaddr2native (via tcs_bind) must reject a sentinel address + // sockaddr2native (via tcs_bind) must reject a packet address on an IP socket TcsSocket udp_socket = TCS_SOCKET_INVALID; REQUIRE(tcs_socket(&udp_socket, TCS_FAMILY_IPV4, TCS_SOCKET_DGRAM, TCS_PROTOCOL_IP_UDP) == TCS_SUCCESS); - struct TcsAddress packet_addr = TCS_ADDRESS_NONE; - packet_addr.family = TCS_FAMILY_PACKET; CHECK(tcs_bind(udp_socket, &packet_addr) == TCS_ERROR_NOT_SUPPORTED); CHECK(tcs_close(&udp_socket) == TCS_SUCCESS); } else if (probe_res == TCS_SUCCESS) { - // Supported (Linux with CAP_NET_RAW): clean up and skip the rest. + // Supported (Linux with CAP_NET_RAW, Windows with Npcap): clean up and skip the rest. + CHECK(tcs_address_is_supported(&packet_addr)); CHECK(tcs_close(&probe) == TCS_SUCCESS); } // Other results (e.g. TCS_ERROR_PERMISSION_DENIED on Linux without CAP_NET_RAW) are valid @@ -2452,6 +2465,118 @@ TEST_CASE("Sentinel handling: TCS_FAMILY_PACKET on unsupported platform") REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); } +TEST_CASE("tcs_address_socket_local on packet socket") +{ + // Setup + REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } + + // Given + struct TcsAddress bind_address = TCS_ADDRESS_NONE; + bind_address.family = TCS_FAMILY_PACKET; + bind_address.data.packet.interface_id = iface.iface.id; + bind_address.data.packet.protocol = 0x22F0; + TcsSocket socket = TCS_SOCKET_INVALID; + REQUIRE(tcs_socket_packet(&socket, &bind_address, TCS_SOCKET_DGRAM) == TCS_SUCCESS); + + // When + struct TcsAddress local_address = TCS_ADDRESS_NONE; + CHECK(tcs_address_socket_local(socket, &local_address) == TCS_SUCCESS); + + // Then + CHECK(local_address.family.native == TCS_FAMILY_PACKET.native); + CHECK(local_address.data.packet.interface_id == iface.iface.id); + CHECK(local_address.data.packet.protocol == 0x22F0); + CHECK(memcmp(local_address.data.packet.mac, iface.address.data.packet.mac, 6) == 0); + + TcsSocketType type = {0}; + CHECK(tcs_opt_type_get(socket, &type) == TCS_SUCCESS); + CHECK(type.native == TCS_SOCKET_DGRAM.native); + + // Clean up + CHECK(tcs_close(&socket) == TCS_SUCCESS); + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); +} + +TEST_CASE("Packet socket receive timeout and nonblocking") +{ + // Setup + REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } + + // Given + struct TcsAddress bind_address = TCS_ADDRESS_NONE; + bind_address.family = TCS_FAMILY_PACKET; + bind_address.data.packet.interface_id = iface.iface.id; + bind_address.data.packet.protocol = 0x22F0; + TcsSocket socket = TCS_SOCKET_INVALID; + REQUIRE(tcs_socket_packet(&socket, &bind_address, TCS_SOCKET_DGRAM) == TCS_SUCCESS); + + // When/Then - nothing is expected to talk AVTP here, but a stray frame is not an error + static uint8_t buffer[1500]; + size_t received_size = 0; + CHECK(tcs_opt_receive_timeout_set(socket, 20) == TCS_SUCCESS); + TcsResult receive_res = tcs_receive(socket, buffer, sizeof(buffer), TCS_FLAG_NONE, &received_size); + CHECK((receive_res == TCS_ERROR_TIMED_OUT || receive_res == TCS_SUCCESS)); + + CHECK(tcs_opt_nonblocking_set(socket, true) == TCS_SUCCESS); + receive_res = tcs_receive(socket, buffer, sizeof(buffer), TCS_FLAG_NONE, &received_size); + CHECK((receive_res == TCS_ERROR_WOULD_BLOCK || receive_res == TCS_SUCCESS)); + + // Clean up + CHECK(tcs_close(&socket) == TCS_SUCCESS); + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); +} + +TEST_CASE("Packet socket RAW send") +{ + // Setup + REQUIRE(tcs_lib_init() == TCS_SUCCESS); + struct TcsInterfaceAddress iface; + if (!packet_sockets_available(&iface)) + { + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); + return; + } + + // Given - a bound RAW socket sends whole frames without a destination address + struct TcsAddress bind_address = TCS_ADDRESS_NONE; + bind_address.family = TCS_FAMILY_PACKET; + bind_address.data.packet.interface_id = iface.iface.id; + bind_address.data.packet.protocol = 0x22F0; + TcsSocket socket = TCS_SOCKET_INVALID; + REQUIRE(tcs_socket_packet(&socket, &bind_address, TCS_SOCKET_RAW) == TCS_SUCCESS); + + uint8_t msg[] = "hello world\n"; + uint8_t frame[14 + sizeof(msg)]; + memcpy(frame, AVTP_DEST_ADDR, 6); + memcpy(frame + 6, iface.address.data.packet.mac, 6); + frame[12] = 0x22; + frame[13] = 0xF0; + memcpy(frame + 14, msg, sizeof(msg)); + + // When + size_t sent_size = 0; + CHECK(tcs_send(socket, frame, sizeof(frame), TCS_FLAG_NONE, &sent_size) == TCS_SUCCESS); + + // Then + CHECK(sent_size == sizeof(frame)); + + // Clean up + CHECK(tcs_close(&socket) == TCS_SUCCESS); + REQUIRE(tcs_lib_cleanup() == TCS_SUCCESS); +} + TEST_CASE("tcs_address_socket_local on TCP") { REQUIRE(tcs_lib_init() == TCS_SUCCESS); From bb5a84abdee83e5c34fd03e6cffa81b2add44c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindel=C3=B6w?= Date: Mon, 26 Oct 2026 12:17:18 +0100 Subject: [PATCH 2/4] Add npcap implementation --- README.md | 5 + include/tinycsocket.h | 988 +++++++++++++++++++++++++++++++++---- src/tinycsocket_common.c | 15 +- src/tinycsocket_internal.h | 33 +- src/tinycsocket_posix.c | 15 + src/tinycsocket_win32.c | 925 ++++++++++++++++++++++++++++++---- 6 files changed, 1763 insertions(+), 218 deletions(-) diff --git a/README.md b/README.md index d1a9363..c3c94c3 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,11 @@ Currently supported platforms: - POSIX.1-2001 compliant systems (Linux, FreeBSD, OpenBSD, MacOS, Solaris etc.) - Android (API level 24+ for full IPv6 interface support) +Layer 2 packet sockets (`TCS_FAMILY_PACKET`) are available on Linux, and on Windows when +[Npcap](https://npcap.com) 1.00 or later is installed. Npcap is loaded at runtime by `tcs_lib_init()`, so +nothing extra is needed to build or link. Receiving 802.1Q VLAN tags needs Npcap 1.81 or later, older +versions deliver tagged frames with the tag stripped. + # Installation instructions The library supports both header-only and the cmake build system. When using cmake diff --git a/include/tinycsocket.h b/include/tinycsocket.h index cacbce1..e24b6cf 100644 --- a/include/tinycsocket.h +++ b/include/tinycsocket.h @@ -29,7 +29,7 @@ #ifndef TINYCSOCKET_INTERNAL_H_ #define TINYCSOCKET_INTERNAL_H_ -static const char* const TCS_VERSION_TXT = "v0.3.83"; +static const char* const TCS_VERSION_TXT = "v0.3.84"; extern const char* const TCS_LICENSE_TXT; /* @@ -377,7 +377,7 @@ struct TcsPollEvent extern const TcsFamily TCS_FAMILY_ANY; /**< Layer 4 agnostic (AF_UNSPEC) */ extern const TcsFamily TCS_FAMILY_IPV4; /**< INET IPv4 interface (AF_INET) */ extern const TcsFamily TCS_FAMILY_IPV6; /**< INET IPv6 interface (AF_INET6) */ -extern const TcsFamily TCS_FAMILY_PACKET; /**< Layer 2 interface (AF_PACKET on Linux; unsupported elsewhere) */ +extern const TcsFamily TCS_FAMILY_PACKET; /**< Layer 2 interface (AF_PACKET on Linux, Npcap on Windows) */ // gcc may trigger bug #53119 #ifdef __GNUC__ @@ -462,7 +462,11 @@ static const struct TcsPollEvent TCS_POLL_EVENT_EMPTY = {0, 0, false, false, TCS * * This function needs to be called before using any other function in the library. * - * On Windows, it will initialize Winsock, otherwise it does nothing and will always return #TCS_SUCCESS. + * On Windows, it will initialize Winsock and load Npcap (wpcap.dll) if it is installed, which is what makes + * #TCS_FAMILY_PACKET sockets available there. Npcap 1.00 or later is supported. Npcap is optional: without it, + * packet sockets fail with #TCS_ERROR_NOT_SUPPORTED and tcs_address_is_supported() returns false for packet + * addresses. Receiving 802.1Q VLAN tags needs Npcap 1.81 or later, older versions deliver tagged frames with the + * tag stripped. On other platforms it does nothing and will always return #TCS_SUCCESS. * * You can call this multiple times, it will keep a counter of how many times you have called it (RAII friendly). * You should call tcs_lib_cleanup() after you are done with the library (before program exit), atleast the number of times you have called tcs_lib_init(). @@ -493,7 +497,9 @@ TcsResult tcs_lib_init(void); * @brief De-initialize tinycsocket library. * * You need to call this the same amount of times as you have called ::tcs_lib_init(). - * + * + * On Windows, the last call closes any packet socket still open and unloads Npcap. + * * @return #TCS_SUCCESS if successful, otherwise the error code. * * @retval #TCS_ERROR_LIBRARY_NOT_INITIALIZED if you have not called tcs_lib_init() before calling this function. @@ -759,7 +765,7 @@ TcsResult tcs_socket_udp_str(TcsSocket* out_socket, const char* local_address, c /** * @brief Create a packet socket bound to a network interface. * -* Creates an AF_PACKET socket for sending and receiving raw L2 frames. +* Creates a socket for sending and receiving raw L2 frames: AF_PACKET on Linux, Npcap on Windows (see tcs_lib_init()). * The socket is bound to the interface and protocol specified in @p bind_address. * On failure, *out_socket is always set back to #TCS_SOCKET_INVALID. * @@ -795,6 +801,8 @@ TcsResult tcs_socket_udp_str(TcsSocket* out_socket, const char* local_address, c * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p out_socket is NULL, if *out_socket is not #TCS_SOCKET_INVALID, or if @p bind_address is NULL or not TCS_FAMILY_PACKET. +* @retval #TCS_ERROR_NOT_SUPPORTED if the platform has no packet sockets, or on Windows if Npcap is not installed or @p bind_address has interface_id 0. +* @retval #TCS_ERROR_PERMISSION_DENIED if the process may not open packet sockets, e.g. without CAP_NET_RAW on Linux. * * @see tcs_socket_packet_str() * @see tcs_close() @@ -805,6 +813,7 @@ TcsResult tcs_socket_packet(TcsSocket* out_socket, const struct TcsAddress* bind * @brief Create a packet socket bound to a named network interface. * * Looks up the interface by name using ::tcs_interface_list() and delegates to ::tcs_socket_packet(). +* On Windows the names are the adapter names shown in the network settings, e.g. "Ethernet". * On failure, *out_socket is always set back to #TCS_SOCKET_INVALID. * * @code @@ -835,6 +844,7 @@ TcsResult tcs_socket_packet(TcsSocket* out_socket, const struct TcsAddress* bind * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p out_socket is NULL, if *out_socket is not #TCS_SOCKET_INVALID, or if @p interface_name is NULL or not found. +* @retval #TCS_ERROR_NOT_SUPPORTED if the platform has no packet sockets, or on Windows if Npcap is not installed. * * @see tcs_socket_packet() * @see tcs_close() @@ -1325,6 +1335,7 @@ TcsResult tcs_poll_destroy(struct TcsPoll** poll); * @param[in] user_data is a pointer of your choice that is associated with the socket. Use NULL if not used. * @param[in] flags is a bitmask of ::TcsPollFlags (e.g. TCS_POLL_READ | TCS_POLL_WRITE). Errors are always reported. * @return #TCS_SUCCESS if successful, otherwise the error code. +* @retval #TCS_ERROR_NOT_SUPPORTED on Windows if @p socket is a packet socket, an Npcap handle cannot be polled together with Winsock sockets. * @see tcs_poll_remove() */ TcsResult tcs_poll_add(struct TcsPoll* poll, TcsSocket socket, void* user_data, uint32_t flags); @@ -1683,6 +1694,10 @@ TcsResult tcs_opt_priority_get(TcsSocket socket, int* out_priority); /** * @brief Join a multicast group on a specific local interface. * +* For packet sockets, @p multicast_address is a MAC address and @p local_address is ignored. On Windows, a membership +* puts the adapter in promiscuous mode and Npcap filters out everything but the adapter's own address, broadcast and +* the joined groups. The first and the last membership reopen the capture handle, frames queued in it are lost. +* * @param[in] socket socket to configure. * @param[in] local_address local interface address to use. * @param[in] multicast_address multicast group address to join. @@ -1822,7 +1837,9 @@ TcsResult tcs_interface_list(struct TcsInterface out_interfaces[], size_t interf * @param[in] addresses_length number of elements in the @p out_addresses array. * @param[out] out_length pointer to receive the number of addresses found. * @return #TCS_SUCCESS if successful, otherwise the error code. -* @retval #TCS_ERROR_NOT_SUPPORTED if @p address_family is not supported on this platform (e.g. ::TCS_FAMILY_PACKET on non-Linux). +* @retval #TCS_ERROR_INVALID_ARGUMENT if the port is not a number in the range 0-65535. +* @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the hostname could not be resolved. +* @retval #TCS_ERROR_NOT_SUPPORTED if @p address_family is not supported on this platform, or is ::TCS_FAMILY_PACKET and @p hostname is not a MAC address. */ TcsResult tcs_address_resolve(const char* hostname, TcsFamily address_family, @@ -1839,7 +1856,7 @@ TcsResult tcs_address_resolve(const char* hostname, * @param[in] interface_addresses_length number of elements in the @p out_interface_addresses array. * @param[out] out_length pointer to receive the total number of results available, which may exceed @p interface_addresses_length. * @return #TCS_SUCCESS if successful, otherwise the error code. -* @note If @p address_family_filter is not supported on this platform (e.g. ::TCS_FAMILY_PACKET on non-Linux), no entries match and *out_length is 0. +* @note If @p address_family_filter is not supported on this platform (e.g. ::TCS_FAMILY_PACKET on macOS), no entries match and *out_length is 0. On Windows, packet addresses are listed with or without Npcap. */ TcsResult tcs_address_list(TcsInterfaceId interface_id_filter, TcsFamily address_family_filter, @@ -1931,7 +1948,7 @@ bool tcs_address_is_multicast(const struct TcsAddress* addr); /** @brief Check if the address is a broadcast address. Returns false for NULL, unknown, or unsupported address families. */ bool tcs_address_is_broadcast(const struct TcsAddress* addr); -/** @brief Check if the address family is known and supported by this platform. Returns false for NULL or unknown families. */ +/** @brief Check if the address family is known and supported by this platform. Returns false for NULL or unknown families. On Windows, packet addresses are supported once tcs_lib_init() has found Npcap. */ bool tcs_address_is_supported(const struct TcsAddress* addr); #ifdef __cplusplus @@ -4339,6 +4356,21 @@ TcsResult tcs_address_socket_family(TcsSocket socket, TcsFamily* out_family) // tcs_address_is_multicast() is defined in tinycsocket_common.c // tcs_address_is_broadcast() is defined in tinycsocket_common.c +bool tcs_address_is_supported(const struct TcsAddress* addr) +{ + if (addr == NULL) + return false; + if (addr->family.native == TCS_FAMILY_ANY.native) + return true; + if (addr->family.native == TCS_FAMILY_IPV4.native) + return true; + if (addr->family.native == TCS_FAMILY_IPV6.native) + return true; + if (addr->family.native == TCS_FAMILY_PACKET.native) + return addr->family.native != -1; // -1 is the sentinel on platforms without AF_PACKET + return false; +} + #endif /**********************************/ @@ -4389,6 +4421,7 @@ TcsResult tcs_address_socket_family(TcsSocket socket, TcsFamily* out_family) #include // GetAdaptersAddresses #include // getaddrinfo +#include // INT_MAX #include // fprintf (debug diagnostics) #include // Malloc for GetAdaptersAddresses #include // memset @@ -4458,7 +4491,9 @@ const struct TcsAddressIpv6 TCS_ADDRESS_IPV6_LOOPBACK = {{0, 0, 0, 0, 0, 0, 0, 0 const TcsFamily TCS_FAMILY_ANY = {AF_UNSPEC}; const TcsFamily TCS_FAMILY_IPV4 = {AF_INET}; const TcsFamily TCS_FAMILY_IPV6 = {AF_INET6}; -const TcsFamily TCS_FAMILY_PACKET = {-1}; /* AF_PACKET unsupported on Windows */ +// Windows has no AF_PACKET. Packet sockets are emulated with Npcap, see tcs_lib_init(). The value never reaches +// Winsock, it only needs to differ from every AF_* value. +const TcsFamily TCS_FAMILY_PACKET = {1000}; // Type const TcsSocketType TCS_SOCKET_STREAM = {SOCK_STREAM}; @@ -4564,14 +4599,50 @@ static TcsResult socketstatus2retcode(int status) } } +// Lists the adapters with GetAdaptersAddresses(). *out_adapters is NULL when there are none. Free it with free(). +static TcsResult adapters_get(PIP_ADAPTER_ADDRESSES* out_adapters) +{ + const int MAX_TRIES = 5; + ULONG buffer_size = 15000; // From msdn recommendation + PIP_ADAPTER_ADDRESSES adapters = NULL; + ULONG adapter_sts = ERROR_NO_DATA; + for (int i = 0; i < MAX_TRIES; ++i) + { + adapters = (PIP_ADAPTER_ADDRESSES)malloc(buffer_size); + if (adapters == NULL) + return TCS_ERROR_MEMORY; + adapter_sts = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, + adapters, + &buffer_size); + if (adapter_sts != ERROR_BUFFER_OVERFLOW) + break; + free(adapters); + adapters = NULL; + } + if (adapter_sts == ERROR_NO_DATA) + { + free(adapters); + *out_adapters = NULL; + return TCS_SUCCESS; + } + if (adapter_sts != NO_ERROR) + { + // Debug: log the actual Windows error code for diagnostics + fprintf(stderr, "GetAdaptersAddresses failed with error: %lu (0x%lX)\n", adapter_sts, adapter_sts); + free(adapters); + return TCS_ERROR_UNKNOWN; + } + *out_adapters = adapters; + return TCS_SUCCESS; +} + static TcsResult sockaddr2native(const struct TcsAddress* in_addr, PSOCKADDR out_addr, int* out_addrlen) { if (in_addr == NULL || out_addr == NULL) return TCS_ERROR_INVALID_ARGUMENT; - if (in_addr->family.native == -1) // sentinel for unsupported families (e.g. TCS_FAMILY_PACKET on Windows) - return TCS_ERROR_NOT_SUPPORTED; - if (in_addr->family.native == TCS_FAMILY_ANY.native) { return TCS_ERROR_NOT_SUPPORTED; @@ -4601,7 +4672,7 @@ static TcsResult sockaddr2native(const struct TcsAddress* in_addr, PSOCKADDR out } else if (in_addr->family.native == TCS_FAMILY_PACKET.native) { - return TCS_ERROR_NOT_SUPPORTED; + return TCS_ERROR_NOT_SUPPORTED; // Packet addresses have no sockaddr on Windows, only packet sockets take them } return TCS_ERROR_NOT_SUPPORTED; } @@ -4638,16 +4709,667 @@ static TcsResult native2sockaddr(const PSOCKADDR in_addr, struct TcsAddress* out return TCS_SUCCESS; } +// ######## Packet Sockets ######## +// +// Windows has no AF_PACKET. A packet socket is an index into a table with the top bit set, which can never collide +// with a kernel SOCKET handle. The public functions test that bit with tcs_internal_is_socket_packet() and hand +// packet sockets to the tcs_internal_*_impl_npcap() functions below, everything else goes to Winsock. Nothing +// outside the Npcap section touches Npcap. + +#define TCS_PACKET_SOCKET_TAG ((TcsSocket)1 << (sizeof(TcsSocket) * 8 - 1)) + +static bool tcs_internal_is_socket_packet(TcsSocket socket) +{ + return socket != TCS_SOCKET_INVALID && (socket & TCS_PACKET_SOCKET_TAG) != 0; +} + +// ######## Packet Sockets: Npcap ######## +// +// Packet sockets are emulated on top of Npcap, whose wpcap.dll is loaded at runtime by tcs_lib_init(). Nothing from +// the Npcap SDK is needed at build time: the few pcap types and functions used are declared here, and their ABI has +// been frozen since WinPcap. +// +// Supported: Npcap 1.00 (2020) or later, the first stable release. Every function resolved by +// tcs_internal_lib_init_impl_npcap() exists in all Npcap releases, since Npcap has always shipped a libpcap newer than 1.5. +// pcap_set_immediate_mode() is what rules out WinPcap and Win10Pcap, which stop at libpcap 1.0; a fallback to +// pcap_setmintocopy(p, 0) would admit them. Behaviour that depends on the version: 802.1Q tags are restored on +// receive from Npcap 1.81 (fixed for TCI 0 in 1.82 and for stacked tags in 1.88). Before that, NDIS has already +// stripped the tag, so tagged frames arrive untagged. The filter in tcs_internal_npcap_socket_filter() matches both cases. + +typedef struct pcap pcap_t; + +struct pcap_pkthdr +{ + struct timeval ts; + unsigned int caplen; + unsigned int len; +}; + +struct bpf_program +{ + unsigned int bf_len; + struct bpf_insn* bf_insns; +}; + +#define TCS_PCAP_ERRBUF_SIZE 256 +#define TCS_PCAP_NETMASK_UNKNOWN 0xffffffffu +#define TCS_PCAP_DLT_EN10MB 1 +#define TCS_PCAP_ERROR_NO_SUCH_DEVICE (-5) +#define TCS_PCAP_ERROR_PERM_DENIED (-8) +#define TCS_PCAP_ERROR_IFACE_NOT_UP (-9) +#define TCS_PCAP_ERROR_PROMISC_PERM_DENIED (-11) + +typedef pcap_t*(__cdecl* TcsPcapCreate)(const char*, char*); +typedef int(__cdecl* TcsPcapSetInt)(pcap_t*, int); +typedef int(__cdecl* TcsPcapGetInt)(pcap_t*); +typedef int(__cdecl* TcsPcapSetNonblock)(pcap_t*, int, char*); +typedef HANDLE(__cdecl* TcsPcapGetEvent)(pcap_t*); +typedef int(__cdecl* TcsPcapCompile)(pcap_t*, struct bpf_program*, const char*, int, unsigned int); +typedef int(__cdecl* TcsPcapSetFilter)(pcap_t*, struct bpf_program*); +typedef void(__cdecl* TcsPcapFreeCode)(struct bpf_program*); +typedef int(__cdecl* TcsPcapNextEx)(pcap_t*, struct pcap_pkthdr**, const unsigned char**); +typedef int(__cdecl* TcsPcapSendPacket)(pcap_t*, const unsigned char*, int); +typedef void(__cdecl* TcsPcapClose)(pcap_t*); + +struct TcsInternalNpcapSocket +{ + pcap_t* pcap; // NULL until bound + int type; // SOCK_DGRAM or SOCK_RAW + uint16_t protocol; + TcsInterfaceId interface_id; + uint8_t mac[6]; // The adapter's own address, source of DGRAM frames + int receive_timeout_ms; // 0 waits forever + bool nonblocking; + uint8_t* groups; // Joined multicast addresses, 6 bytes each + size_t group_count; +}; + +#ifndef TCS_CFG_PACKET_SOCKETS_MAX +#define TCS_CFG_PACKET_SOCKETS_MAX 64 +#endif + +static struct +{ + HMODULE module; // NULL when Npcap is not installed or the library is not initialized + int init_count; // tcs_lib_init() calls not yet matched by tcs_lib_cleanup() + TcsPcapCreate create; + TcsPcapSetInt set_snaplen; + TcsPcapSetInt set_promisc; + TcsPcapSetInt set_immediate_mode; + TcsPcapGetInt activate; + TcsPcapGetInt datalink; + TcsPcapSetNonblock setnonblock; + TcsPcapGetEvent getevent; + TcsPcapCompile compile; + TcsPcapSetFilter setfilter; + TcsPcapFreeCode freecode; + TcsPcapNextEx next_ex; + TcsPcapSendPacket sendpacket; + TcsPcapClose close; + struct TcsInternalNpcapSocket* sockets[TCS_CFG_PACKET_SOCKETS_MAX]; // Indexed by the handle without its tag bit +} g_npcap; + +// The state behind a socket for which tcs_internal_is_socket_packet() is true, NULL once it has been closed +static struct TcsInternalNpcapSocket* tcs_internal_npcap_socket_get(TcsSocket socket) +{ + TcsSocket index = socket & ~TCS_PACKET_SOCKET_TAG; + if (index >= TCS_CFG_PACKET_SOCKETS_MAX) + return NULL; + return g_npcap.sockets[index]; +} + +// Installs a BPF filter giving the socket the frames a Linux packet socket with the same protocol and memberships +// would see. Npcap cannot program the adapter's multicast filter, so memberships need promiscuous mode and the filter +// then also limits the destinations to the adapter's own address, broadcast and the joined groups. +static TcsResult tcs_internal_npcap_socket_filter(const struct TcsInternalNpcapSocket* ps, pcap_t* pcap) +{ + bool filter_protocol = ps->protocol != TCS_PROTOCOL_ETH_ALL; + if (!filter_protocol && ps->group_count == 0) + return TCS_SUCCESS; + + size_t expression_size = 128 + 36 * (ps->group_count + 1); + char* expression = (char*)malloc(expression_size); + if (expression == NULL) + return TCS_ERROR_MEMORY; + size_t pos = 0; + if (ps->group_count > 0) + { + struct TcsAddress mac = TCS_ADDRESS_NONE; + mac.family = TCS_FAMILY_PACKET; + char mac_str[18]; + memcpy(mac.data.packet.mac, ps->mac, 6); + tcs_address_to_str(&mac, mac_str, sizeof mac_str, NULL); + pos += (size_t)snprintf(expression + pos, expression_size - pos, "(ether dst %s or ether broadcast", mac_str); + for (size_t i = 0; i < ps->group_count; ++i) + { + memcpy(mac.data.packet.mac, ps->groups + i * 6, 6); + tcs_address_to_str(&mac, mac_str, sizeof mac_str, NULL); + pos += (size_t)snprintf(expression + pos, expression_size - pos, " or ether dst %s", mac_str); + } + pos += (size_t)snprintf(expression + pos, expression_size - pos, "%s", filter_protocol ? ") and " : ")"); + } + if (filter_protocol) + { + // Tagged frames are matched too, Linux delivers them untagged with the inner protocol + snprintf(expression + pos, + expression_size - pos, + "(ether proto 0x%04x or (vlan and ether proto 0x%04x))", + (unsigned int)ps->protocol, + (unsigned int)ps->protocol); + } + + struct bpf_program program; + memset(&program, 0, sizeof program); + int compile_sts = g_npcap.compile(pcap, &program, expression, 1, TCS_PCAP_NETMASK_UNKNOWN); + free(expression); + if (compile_sts != 0) + return TCS_ERROR_SYSTEM; + int setfilter_sts = g_npcap.setfilter(pcap, &program); + g_npcap.freecode(&program); + return setfilter_sts == 0 ? TCS_SUCCESS : TCS_ERROR_SYSTEM; +} + +// Opens the Npcap handle of a packet socket from its current state, replacing the previous handle on success +static TcsResult tcs_internal_npcap_socket_open(struct TcsInternalNpcapSocket* ps) +{ + PIP_ADAPTER_ADDRESSES adapters = NULL; + TcsResult adapters_sts = adapters_get(&adapters); + if (adapters_sts != TCS_SUCCESS) + return adapters_sts; + + // Npcap names its devices after the adapter GUID + const char prefix[] = "\\Device\\NPF_"; + char device[sizeof prefix + MAX_ADAPTER_NAME_LENGTH + 4]; + bool found = false; + for (PIP_ADAPTER_ADDRESSES iter = adapters; iter != NULL && !found; iter = iter->Next) + { + size_t name_size = strlen(iter->AdapterName) + 1; + if (iter->IfIndex != ps->interface_id || iter->PhysicalAddressLength != 6 || + sizeof prefix - 1 + name_size > sizeof device) + continue; + memcpy(device, prefix, sizeof prefix - 1); + memcpy(device + sizeof prefix - 1, iter->AdapterName, name_size); + memcpy(ps->mac, iter->PhysicalAddress, 6); + found = true; + } + free(adapters); + if (!found) + return TCS_ERROR_INVALID_ARGUMENT; + + char errbuf[TCS_PCAP_ERRBUF_SIZE]; + pcap_t* pcap = g_npcap.create(device, errbuf); + if (pcap == NULL) + return TCS_ERROR_SYSTEM; + g_npcap.set_snaplen(pcap, 65535); + g_npcap.set_promisc(pcap, ps->group_count > 0); + g_npcap.set_immediate_mode(pcap, 1); // Deliver every frame at once instead of batching them in the driver + int activate_sts = g_npcap.activate(pcap); + if (activate_sts < 0) + { + g_npcap.close(pcap); + if (activate_sts == TCS_PCAP_ERROR_PERM_DENIED || activate_sts == TCS_PCAP_ERROR_PROMISC_PERM_DENIED) + return TCS_ERROR_PERMISSION_DENIED; + if (activate_sts == TCS_PCAP_ERROR_NO_SUCH_DEVICE) + return TCS_ERROR_INVALID_ARGUMENT; + if (activate_sts == TCS_PCAP_ERROR_IFACE_NOT_UP) + return TCS_ERROR_NETWORK_UNREACHABLE; + return TCS_ERROR_SYSTEM; + } + // Receiving waits on the handle's event instead, so the socket's timeout and blocking mode can change later + if (g_npcap.datalink(pcap) != TCS_PCAP_DLT_EN10MB || g_npcap.setnonblock(pcap, 1, errbuf) != 0) + { + g_npcap.close(pcap); + return TCS_ERROR_NOT_SUPPORTED; + } + TcsResult filter_sts = tcs_internal_npcap_socket_filter(ps, pcap); + if (filter_sts != TCS_SUCCESS) + { + g_npcap.close(pcap); + return filter_sts; + } + if (ps->pcap != NULL) + g_npcap.close(ps->pcap); + ps->pcap = pcap; + return TCS_SUCCESS; +} + +static void tcs_internal_lib_init_impl_npcap(void) +{ + if (g_npcap.init_count++ != 0) + return; + // Npcap installs to System32\Npcap. LOAD_WITH_ALTERED_SEARCH_PATH makes the loader find Packet.dll next to + // wpcap.dll without SetDllDirectory(), which would affect the whole process. The plain name is the fallback + // recommended by Npcap, for installations in WinPcap compatible mode. + const char suffix[] = "\\Npcap\\wpcap.dll"; + char path[MAX_PATH]; + UINT system_dir_size = GetSystemDirectoryA(path, MAX_PATH); + HMODULE m = NULL; + if (system_dir_size != 0 && system_dir_size + sizeof suffix <= MAX_PATH) + { + memcpy(path + system_dir_size, suffix, sizeof suffix); + m = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + } + if (m == NULL) + m = LoadLibraryA("wpcap.dll"); + if (m == NULL) + return; + + // FARPROC is cast through void (*)(void), which gcc treats as compatible with every function pointer type + g_npcap.create = (TcsPcapCreate)(void (*)(void))GetProcAddress(m, "pcap_create"); + g_npcap.set_snaplen = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_snaplen"); + g_npcap.set_promisc = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_promisc"); + g_npcap.set_immediate_mode = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_immediate_mode"); + g_npcap.activate = (TcsPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_activate"); + g_npcap.datalink = (TcsPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_datalink"); + g_npcap.setnonblock = (TcsPcapSetNonblock)(void (*)(void))GetProcAddress(m, "pcap_setnonblock"); + g_npcap.getevent = (TcsPcapGetEvent)(void (*)(void))GetProcAddress(m, "pcap_getevent"); + g_npcap.compile = (TcsPcapCompile)(void (*)(void))GetProcAddress(m, "pcap_compile"); + g_npcap.setfilter = (TcsPcapSetFilter)(void (*)(void))GetProcAddress(m, "pcap_setfilter"); + g_npcap.freecode = (TcsPcapFreeCode)(void (*)(void))GetProcAddress(m, "pcap_freecode"); + g_npcap.next_ex = (TcsPcapNextEx)(void (*)(void))GetProcAddress(m, "pcap_next_ex"); + g_npcap.sendpacket = (TcsPcapSendPacket)(void (*)(void))GetProcAddress(m, "pcap_sendpacket"); + g_npcap.close = (TcsPcapClose)(void (*)(void))GetProcAddress(m, "pcap_close"); + + if (g_npcap.create == NULL || g_npcap.set_snaplen == NULL || g_npcap.set_promisc == NULL || + g_npcap.set_immediate_mode == NULL || g_npcap.activate == NULL || g_npcap.datalink == NULL || + g_npcap.setnonblock == NULL || g_npcap.getevent == NULL || g_npcap.compile == NULL || + g_npcap.setfilter == NULL || g_npcap.freecode == NULL || g_npcap.next_ex == NULL || + g_npcap.sendpacket == NULL || g_npcap.close == NULL) + { + FreeLibrary(m); + return; + } + g_npcap.module = m; +} + +static void tcs_internal_lib_cleanup_impl_npcap(void) +{ + if (g_npcap.init_count == 0 || --g_npcap.init_count != 0 || g_npcap.module == NULL) + return; + // Like WSACleanup(), the last cleanup closes every socket still open + for (size_t i = 0; i < TCS_CFG_PACKET_SOCKETS_MAX; ++i) + { + TcsSocket socket = TCS_PACKET_SOCKET_TAG | (TcsSocket)i; + if (g_npcap.sockets[i] != NULL) + tcs_close(&socket); + } + FreeLibrary(g_npcap.module); + memset(&g_npcap, 0, sizeof g_npcap); +} + +static TcsResult tcs_internal_socket_impl_npcap(TcsSocket* out_socket, + TcsFamily family, + TcsSocketType type, + TcsProtocol protocol) +{ + (void)family; // Always TCS_FAMILY_PACKET + if (g_npcap.module == NULL) + return TCS_ERROR_NOT_SUPPORTED; + if (type.native != SOCK_DGRAM && type.native != SOCK_RAW) + return TCS_ERROR_NOT_SUPPORTED; + size_t index = 0; + while (index < TCS_CFG_PACKET_SOCKETS_MAX && g_npcap.sockets[index] != NULL) + ++index; + if (index == TCS_CFG_PACKET_SOCKETS_MAX) + return TCS_ERROR_MEMORY; + struct TcsInternalNpcapSocket* ps = (struct TcsInternalNpcapSocket*)malloc(sizeof *ps); + if (ps == NULL) + return TCS_ERROR_MEMORY; + memset(ps, 0, sizeof *ps); + ps->type = type.native; + ps->protocol = protocol; + g_npcap.sockets[index] = ps; + *out_socket = TCS_PACKET_SOCKET_TAG | (TcsSocket)index; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_close_impl_npcap(TcsSocket* socket) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(*socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (ps->pcap != NULL) + g_npcap.close(ps->pcap); + free(ps->groups); + free(ps); + g_npcap.sockets[*socket & ~TCS_PACKET_SOCKET_TAG] = NULL; + *socket = TCS_SOCKET_INVALID; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_bind_impl_npcap(TcsSocket socket, const struct TcsAddress* address) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (address->family.native != TCS_FAMILY_PACKET.native) + return TCS_ERROR_INVALID_ARGUMENT; + if (address->data.packet.interface_id == 0) + return TCS_ERROR_NOT_SUPPORTED; // An Npcap handle belongs to one adapter, there is no "all interfaces" + ps->interface_id = address->data.packet.interface_id; + if (address->data.packet.protocol != 0) + ps->protocol = address->data.packet.protocol; + return tcs_internal_npcap_socket_open(ps); +} + +static TcsResult tcs_internal_send_impl_npcap(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + size_t* sent_size) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + // Only whole frames can be sent without a destination + if (ps->type != SOCK_RAW) + return TCS_ERROR_INVALID_ARGUMENT; + struct TcsAddress self = TCS_ADDRESS_NONE; + self.family = TCS_FAMILY_PACKET; + self.data.packet.interface_id = ps->interface_id; + return tcs_send_to(socket, buffer, buffer_size, flags & ~TCS_MSG_SENDALL, &self, sent_size); +} + +static TcsResult tcs_internal_send_to_impl_npcap(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + const struct TcsAddress* destination_address, + size_t* sent_size) +{ + (void)flags; // No send flag applies to a frame + if (sent_size != NULL) + *sent_size = 0; + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (ps->pcap == NULL) + return TCS_ERROR_INVALID_ARGUMENT; // Not bound + if (destination_address->family.native != TCS_FAMILY_PACKET.native) + return TCS_ERROR_INVALID_ARGUMENT; + if (destination_address->data.packet.interface_id != 0 && + destination_address->data.packet.interface_id != ps->interface_id) + return TCS_ERROR_INVALID_ARGUMENT; + if (buffer_size > INT_MAX - 14) + return TCS_ERROR_INVALID_ARGUMENT; + + const uint8_t* frame = buffer; + size_t frame_size = buffer_size; + uint8_t* dgram_frame = NULL; + if (ps->type == SOCK_DGRAM) + { + // Ethernet header: destination, source, EtherType. Like Linux, the socket's protocol is the fallback. + uint16_t protocol = + destination_address->data.packet.protocol != 0 ? destination_address->data.packet.protocol : ps->protocol; + frame_size = buffer_size + 14; + dgram_frame = (uint8_t*)malloc(frame_size); + if (dgram_frame == NULL) + return TCS_ERROR_MEMORY; + memcpy(dgram_frame, destination_address->data.packet.mac, 6); + memcpy(dgram_frame + 6, ps->mac, 6); + dgram_frame[12] = (uint8_t)(protocol >> 8); + dgram_frame[13] = (uint8_t)(protocol & 0xFF); + if (buffer_size > 0) + memcpy(dgram_frame + 14, buffer, buffer_size); + frame = dgram_frame; + } + int send_sts = g_npcap.sendpacket(ps->pcap, frame, (int)frame_size); + free(dgram_frame); + if (send_sts != 0) + return TCS_ERROR_SYSTEM; + if (sent_size != NULL) + *sent_size = buffer_size; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_sendv_impl_npcap(TcsSocket socket, + const struct TcsIoVec* iov, + size_t iov_length, + uint32_t flags, + size_t* sent_size) +{ + // A frame is sent in one piece, so the buffers are gathered first + size_t total_size = 0; + for (size_t i = 0; i < iov_length; ++i) + { + if (iov[i].buffer == NULL && iov[i].buffer_size > 0) + return TCS_ERROR_INVALID_ARGUMENT; + total_size += iov[i].buffer_size; + } + uint8_t* gathered = (uint8_t*)malloc(total_size > 0 ? total_size : 1); + if (gathered == NULL) + return TCS_ERROR_MEMORY; + size_t pos = 0; + for (size_t i = 0; i < iov_length; ++i) + { + if (iov[i].buffer_size > 0) + memcpy(gathered + pos, iov[i].buffer, iov[i].buffer_size); + pos += iov[i].buffer_size; + } + TcsResult send_sts = tcs_send(socket, gathered, total_size, flags, sent_size); + free(gathered); + return send_sts; +} + +static TcsResult tcs_internal_receive_from_impl_npcap(TcsSocket socket, + uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + struct TcsAddress* source_address, + size_t* received_size) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (ps->pcap == NULL) + return TCS_ERROR_INVALID_ARGUMENT; // Not bound + if (flags & TCS_MSG_PEEK) + return TCS_ERROR_NOT_SUPPORTED; + + DWORD start = GetTickCount(); + for (;;) + { + struct pcap_pkthdr* header = NULL; + const unsigned char* frame = NULL; + int next_sts = g_npcap.next_ex(ps->pcap, &header, &frame); + if (next_sts < 0) + return TCS_ERROR_SYSTEM; + if (next_sts == 1) + { + size_t frame_size = header->caplen; + size_t offset = 0; + uint16_t protocol = frame_size >= 14 ? (uint16_t)((frame[12] << 8) | frame[13]) : 0; + if (ps->type == SOCK_DGRAM) + { + if (frame_size < 14) + continue; + offset = 14; + if (protocol == 0x8100 && frame_size >= 18) // 802.1Q tag, stripped like Linux does + { + protocol = (uint16_t)((frame[16] << 8) | frame[17]); + offset = 18; + } + } + size_t payload_size = frame_size - offset; + size_t copy_size = payload_size < buffer_size ? payload_size : buffer_size; + if (copy_size > 0) + memcpy(buffer, frame + offset, copy_size); + if (received_size != NULL) + *received_size = copy_size; + if (source_address != NULL) + { + *source_address = TCS_ADDRESS_NONE; + source_address->family = TCS_FAMILY_PACKET; + source_address->data.packet.interface_id = ps->interface_id; + source_address->data.packet.protocol = protocol; + if (frame_size >= 12) + memcpy(source_address->data.packet.mac, frame + 6, 6); + } + return TCS_SUCCESS; + } + + // Nothing queued. The event is signaled by the driver when a frame arrives. + if (ps->nonblocking) + return TCS_ERROR_WOULD_BLOCK; + DWORD wait_ms = INFINITE; + if (ps->receive_timeout_ms > 0) + { + DWORD elapsed_ms = GetTickCount() - start; + if (elapsed_ms >= (DWORD)ps->receive_timeout_ms) + return TCS_ERROR_TIMED_OUT; + wait_ms = (DWORD)ps->receive_timeout_ms - elapsed_ms; + } + DWORD wait_sts = WaitForSingleObject(g_npcap.getevent(ps->pcap), wait_ms); + if (wait_sts == WAIT_TIMEOUT) + return TCS_ERROR_TIMED_OUT; + if (wait_sts != WAIT_OBJECT_0) + return TCS_ERROR_SYSTEM; + } +} + +static TcsResult tcs_internal_opt_get_impl_npcap(TcsSocket socket, + int32_t level, + int32_t option_name, + void* out_option_value, + size_t* option_size) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (level != TCS_SOL_SOCKET || option_name != TCS_SO_TYPE || *option_size < sizeof ps->type) + return TCS_ERROR_NOT_SUPPORTED; + memcpy(out_option_value, &ps->type, sizeof ps->type); + *option_size = sizeof ps->type; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_receive_timeout_set_impl_npcap(TcsSocket socket, int timeout_ms) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + ps->receive_timeout_ms = timeout_ms; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_receive_timeout_get_impl_npcap(TcsSocket socket, int* timeout_ms) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + *timeout_ms = ps->receive_timeout_ms; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_nonblocking_set_impl_npcap(TcsSocket socket, bool do_non_blocking) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + ps->nonblocking = do_non_blocking; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_nonblocking_get_impl_npcap(TcsSocket socket, bool* is_non_blocking) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + *is_non_blocking = ps->nonblocking; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_membership_add_to_impl_npcap(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) +{ + (void)local_address; // A packet socket is bound to one interface + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (multicast_address->family.native != TCS_FAMILY_PACKET.native) + return TCS_ERROR_INVALID_ARGUMENT; + uint8_t* groups = (uint8_t*)realloc(ps->groups, (ps->group_count + 1) * 6); + if (groups == NULL) + return TCS_ERROR_MEMORY; + memcpy(groups + ps->group_count * 6, multicast_address->data.packet.mac, 6); + ps->groups = groups; + ps->group_count++; + if (ps->pcap == NULL) + return TCS_SUCCESS; // Applied by tcs_bind() + // Promiscuous mode is fixed when a handle is opened, so the first group reopens it. Queued frames are lost. + TcsResult sts = + ps->group_count == 1 ? tcs_internal_npcap_socket_open(ps) : tcs_internal_npcap_socket_filter(ps, ps->pcap); + if (sts != TCS_SUCCESS) + ps->group_count--; + return sts; +} + +static TcsResult tcs_internal_opt_membership_drop_from_impl_npcap(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) +{ + (void)local_address; // A packet socket is bound to one interface + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (multicast_address->family.native != TCS_FAMILY_PACKET.native) + return TCS_ERROR_INVALID_ARGUMENT; + size_t i = 0; + while (i < ps->group_count && memcmp(ps->groups + i * 6, multicast_address->data.packet.mac, 6) != 0) + ++i; + if (i == ps->group_count) + return TCS_ERROR_INVALID_ARGUMENT; + // The group is swapped to the end, so a failed update is undone by restoring the count + size_t last = ps->group_count - 1; + if (i != last) + { + uint8_t dropped[6]; + memcpy(dropped, ps->groups + i * 6, 6); + memcpy(ps->groups + i * 6, ps->groups + last * 6, 6); + memcpy(ps->groups + last * 6, dropped, 6); + } + ps->group_count--; + if (ps->pcap == NULL) + return TCS_SUCCESS; + TcsResult sts = + ps->group_count == 0 ? tcs_internal_npcap_socket_open(ps) : tcs_internal_npcap_socket_filter(ps, ps->pcap); + if (sts != TCS_SUCCESS) + ps->group_count++; + return sts; +} + +static TcsResult tcs_internal_address_socket_local_impl_npcap(TcsSocket socket, struct TcsAddress* local_address) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + *local_address = TCS_ADDRESS_NONE; + local_address->family = TCS_FAMILY_PACKET; + local_address->data.packet.interface_id = ps->interface_id; + local_address->data.packet.protocol = ps->protocol; + memcpy(local_address->data.packet.mac, ps->mac, 6); + return TCS_SUCCESS; +} + +static bool tcs_internal_address_is_supported_impl_npcap(const struct TcsAddress* addr) +{ + // Npcap is loaded by tcs_lib_init() when it is installed + return addr->family.native == TCS_FAMILY_PACKET.native && g_npcap.module != NULL; +} + TcsResult tcs_lib_init(void) { WSADATA wsa_data; if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) return TCS_ERROR_SYSTEM; + tcs_internal_lib_init_impl_npcap(); // Npcap is optional, without it packet sockets are not supported return TCS_SUCCESS; } TcsResult tcs_lib_cleanup(void) { + tcs_internal_lib_cleanup_impl_npcap(); WSACleanup(); return TCS_SUCCESS; } @@ -4656,8 +5378,9 @@ TcsResult tcs_socket(TcsSocket* out_socket, TcsFamily family, TcsSocketType type { if (out_socket == NULL || *out_socket != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; - if (family.native == -1) // sentinel for unsupported families (e.g. TCS_FAMILY_PACKET on Windows) - return TCS_ERROR_NOT_SUPPORTED; + + if (family.native == TCS_FAMILY_PACKET.native) + return tcs_internal_socket_impl_npcap(out_socket, family, type, protocol); TcsSocket new_socket = socket(family.native, type.native, (int)protocol); @@ -4685,6 +5408,9 @@ TcsResult tcs_close(TcsSocket* socket) if (socket == NULL || *socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(*socket)) + return tcs_internal_close_impl_npcap(socket); + int close_status = closesocket(*socket); if (close_status != SOCKET_ERROR) { @@ -4706,6 +5432,9 @@ TcsResult tcs_bind(TcsSocket socket, const struct TcsAddress* address) if (address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_bind_impl_npcap(socket, address); + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; @@ -4724,6 +5453,9 @@ TcsResult tcs_connect(TcsSocket socket, const struct TcsAddress* address) if (address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; @@ -4749,6 +5481,8 @@ TcsResult tcs_listen(TcsSocket socket, int backlog) { if (socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; int status = listen(socket, (int)backlog); return socketstatus2retcode(status); @@ -4758,6 +5492,8 @@ TcsResult tcs_accept(TcsSocket listener, TcsSocket* out_socket, struct TcsAddres { if (listener == TCS_SOCKET_INVALID || out_socket == NULL || *out_socket != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(listener)) + return TCS_ERROR_NOT_SUPPORTED; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -4789,6 +5525,8 @@ TcsResult tcs_shutdown(TcsSocket socket, TcsShutdownDirection direction) return TCS_ERROR_INVALID_ARGUMENT; if (direction < 0 || direction > 2) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; const int how = LUT[direction]; int shutdown_status = shutdown(socket, how); @@ -4808,6 +5546,9 @@ TcsResult tcs_send(TcsSocket socket, const uint8_t* buffer, size_t buffer_size, if (buffer == NULL || buffer_size == 0) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_send_impl_npcap(socket, buffer, buffer_size, flags, sent_size); + // Send all if (flags & TCS_MSG_SENDALL) { @@ -4864,6 +5605,9 @@ TcsResult tcs_send_to(TcsSocket socket, if (flags & TCS_MSG_SENDALL) return TCS_ERROR_NOT_IMPLEMENTED; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_send_to_impl_npcap(socket, buffer, buffer_size, flags, destination_address, sent_size); + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; @@ -4897,6 +5641,9 @@ TcsResult tcs_sendv(TcsSocket socket, const struct TcsIoVec* iov, size_t iov_len if (flags & TCS_MSG_SENDALL) return TCS_ERROR_NOT_IMPLEMENTED; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_sendv_impl_npcap(socket, iov, iov_length, flags, sent_size); + WSABUF stack_buffers[TCS_CFG_SENDV_STACK_MAX]; WSABUF* native_buffers = stack_buffers; WSABUF* heap_buffers = NULL; @@ -4954,6 +5701,9 @@ TcsResult tcs_receive(TcsSocket socket, uint8_t* buffer, size_t buffer_size, uin if (buffer == NULL && buffer_size > 0) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_receive_from(socket, buffer, buffer_size, flags, NULL, received_size); + #if WINVER <= 0x501 if (flags & TCS_MSG_WAITALL) { @@ -5019,6 +5769,9 @@ TcsResult tcs_receive_from(TcsSocket socket, if (received_size != NULL) *received_size = 0; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_receive_from_impl_npcap(socket, buffer, buffer_size, flags, source_address, received_size); + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = sizeof(native_sockaddr); @@ -5104,6 +5857,8 @@ TcsResult tcs_poll_add(struct TcsPoll* poll, TcsSocket socket, void* user_data, { if (poll == NULL || socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; // select() cannot wait on an Npcap handle int map_sts = tds_map_socket_user_add(&poll->user_data, socket, user_data); if (map_sts != 0) @@ -5457,6 +6212,8 @@ TcsResult tcs_opt_set(TcsSocket socket, if (option_name == -1) return TCS_ERROR_NOT_IMPLEMENTED; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; int sockopt_status = setsockopt(socket, (int)level, (int)option_name, (const char*)option_value, (int)option_size); return socketstatus2retcode(sockopt_status); @@ -5470,6 +6227,9 @@ TcsResult tcs_opt_get(TcsSocket socket, int32_t level, int32_t option_name, void if (option_name == -1) return TCS_ERROR_NOT_IMPLEMENTED; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_get_impl_npcap(socket, level, option_name, out_option_value, option_size); + int optlen = (int)*option_size; int sockopt_status = getsockopt(socket, (int)level, (int)option_name, (char*)out_option_value, &optlen); if (sockopt_status == 0) @@ -5535,6 +6295,9 @@ TcsResult tcs_opt_receive_timeout_set(TcsSocket socket, int timeout_ms) if (socket == TCS_SOCKET_INVALID || timeout_ms < 0) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_receive_timeout_set_impl_npcap(socket, timeout_ms); + return tcs_opt_set(socket, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &timeout_ms, sizeof(timeout_ms)); } @@ -5543,6 +6306,9 @@ TcsResult tcs_opt_receive_timeout_get(TcsSocket socket, int* timeout_ms) if (socket == TCS_SOCKET_INVALID || timeout_ms == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_receive_timeout_get_impl_npcap(socket, timeout_ms); + DWORD t = 0; size_t t_size = sizeof(t); TcsResult sts = tcs_opt_get(socket, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &t, &t_size); @@ -5594,6 +6360,9 @@ TcsResult tcs_opt_nonblocking_set(TcsSocket socket, bool do_non_blocking) if (socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_nonblocking_set_impl_npcap(socket, do_non_blocking); + u_long mode = do_non_blocking ? 1 : 0; int sts = ioctlsocket((SOCKET)socket, (long)FIONBIO, &mode); return socketstatus2retcode(sts); @@ -5601,8 +6370,12 @@ TcsResult tcs_opt_nonblocking_set(TcsSocket socket, bool do_non_blocking) TcsResult tcs_opt_nonblocking_get(TcsSocket socket, bool* is_non_blocking) { - (void)socket; - (void)is_non_blocking; + if (socket == TCS_SOCKET_INVALID || is_non_blocking == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_nonblocking_get_impl_npcap(socket, is_non_blocking); + // Win32 does not provide an API to query the non-blocking state of a socket return TCS_ERROR_NOT_SUPPORTED; } @@ -5617,6 +6390,9 @@ TcsResult tcs_opt_membership_add(TcsSocket socket, const struct TcsAddress* mult if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_opt_membership_add_to(socket, NULL, multicast_address); + SOCKADDR_STORAGE address_native_local; memset(&address_native_local, 0, sizeof address_native_local); int address_native_local_size = sizeof address_native_local; @@ -5643,6 +6419,9 @@ TcsResult tcs_opt_membership_add_to(TcsSocket socket, if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_membership_add_to_impl_npcap(socket, local_address, multicast_address); + if (multicast_address->family.native == TCS_FAMILY_IPV4.native) { struct ip_mreq imr; @@ -5674,6 +6453,9 @@ TcsResult tcs_opt_membership_drop(TcsSocket socket, const struct TcsAddress* mul if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_opt_membership_drop_from(socket, NULL, multicast_address); + SOCKADDR_STORAGE address_native_local; memset(&address_native_local, 0, sizeof address_native_local); int address_native_local_size = sizeof address_native_local; @@ -5700,6 +6482,9 @@ TcsResult tcs_opt_membership_drop_from(TcsSocket socket, if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_membership_drop_from_impl_npcap(socket, local_address, multicast_address); + if (multicast_address->family.native == TCS_FAMILY_IPV4.native) { struct ip_mreq imr; @@ -5725,6 +6510,8 @@ TcsResult tcs_opt_multicast_interface_set(TcsSocket socket, const struct TcsAddr { if (socket == TCS_SOCKET_INVALID || local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; if (local_address->family.native == TCS_FAMILY_IPV4.native) { @@ -5744,6 +6531,8 @@ TcsResult tcs_opt_multicast_loop_set(TcsSocket socket, bool do_loopback) { if (socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; DWORD val = do_loopback ? 1 : 0; return tcs_opt_set(socket, TCS_SOL_IP, IP_MULTICAST_LOOP, &val, sizeof(val)); @@ -5753,6 +6542,8 @@ TcsResult tcs_opt_multicast_loop_get(TcsSocket socket, bool* is_loopback) { if (socket == TCS_SOCKET_INVALID || is_loopback == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; DWORD val = 0; size_t s = sizeof(val); @@ -5847,44 +6638,12 @@ TcsResult tcs_interface_list(struct TcsInterface out_interfaces[], size_t interf if (out_length != NULL) *out_length = 0; - const int MAX_TRIES = 5; - ULONG buffer_size = 15000; // From msdn recommendation PIP_ADAPTER_ADDRESSES adapters = NULL; - ULONG adapter_sts = ERROR_NO_DATA; - for (int i = 0; i < MAX_TRIES; ++i) - { - adapters = (PIP_ADAPTER_ADDRESSES)malloc(buffer_size); - if (adapters == NULL) - return TCS_ERROR_MEMORY; - adapter_sts = GetAdaptersAddresses(AF_UNSPEC, - GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, - adapters, - &buffer_size); - if (adapter_sts == ERROR_BUFFER_OVERFLOW) - { - free(adapters); - adapters = NULL; - } - else - { - break; - } - } - if (adapter_sts == ERROR_NO_DATA) - { - if (adapters != NULL) - free(adapters); + TcsResult adapters_sts = adapters_get(&adapters); + if (adapters_sts != TCS_SUCCESS) + return adapters_sts; + if (adapters == NULL) return TCS_SUCCESS; - } - if (adapter_sts != NO_ERROR) - { - // Debug: log the actual Windows error code for diagnostics - fprintf(stderr, "GetAdaptersAddresses failed with error: %lu (0x%lX)\n", adapter_sts, adapter_sts); - if (adapters != NULL) - free(adapters); - return TCS_ERROR_UNKNOWN; - } { size_t i = 0; @@ -5951,7 +6710,7 @@ TcsResult tcs_address_resolve(const char* hostname, } } - if (address_family.native == -1) // sentinel for unsupported families (e.g. TCS_FAMILY_PACKET on Windows) + if (address_family.native == TCS_FAMILY_PACKET.native) // Only the numeric fast path above can give a MAC address return TCS_ERROR_NOT_SUPPORTED; ADDRINFOA native_hints; @@ -6010,42 +6769,12 @@ TcsResult tcs_address_list(unsigned int interface_id_filter, if (out_length != NULL) *out_length = 0; - const int MAX_TRIES = 5; - ULONG buffer_size = 15000; PIP_ADAPTER_ADDRESSES adapters = NULL; - ULONG adapter_sts = ERROR_NO_DATA; - for (int i = 0; i < MAX_TRIES; ++i) - { - adapters = (PIP_ADAPTER_ADDRESSES)malloc(buffer_size); - if (adapters == NULL) - return TCS_ERROR_MEMORY; - adapter_sts = GetAdaptersAddresses(AF_UNSPEC, - GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, - adapters, - &buffer_size); - if (adapter_sts == ERROR_BUFFER_OVERFLOW) - { - free(adapters); - adapters = NULL; - } - else - { - break; - } - } - if (adapter_sts == ERROR_NO_DATA) - { - if (adapters != NULL) - free(adapters); + TcsResult adapters_sts = adapters_get(&adapters); + if (adapters_sts != TCS_SUCCESS) + return adapters_sts; + if (adapters == NULL) return TCS_SUCCESS; - } - if (adapter_sts != NO_ERROR) - { - if (adapters != NULL) - free(adapters); - return TCS_ERROR_UNKNOWN; - } size_t populated = 0; for (PIP_ADAPTER_ADDRESSES iter = adapters; iter != NULL; iter = iter->Next) @@ -6069,13 +6798,9 @@ TcsResult tcs_address_list(unsigned int interface_id_filter, if (address_iter->Address.lpSockaddr == NULL) continue; - if (address_family_filter.native != TCS_FAMILY_ANY.native) - { - if (address_family_filter.native == -1) /* unsupported sentinel */ - continue; - if (address_iter->Address.lpSockaddr->sa_family != address_family_filter.native) - continue; - } + if (address_family_filter.native != TCS_FAMILY_ANY.native && + address_iter->Address.lpSockaddr->sa_family != address_family_filter.native) + continue; struct TcsAddress address = TCS_ADDRESS_NONE; TcsResult convert_sts = native2sockaddr(address_iter->Address.lpSockaddr, &address); @@ -6104,6 +6829,38 @@ TcsResult tcs_address_list(unsigned int interface_id_filter, (*out_length)++; } } + + // The layer 2 address of adapters with an Ethernet MAC. Needs no Npcap, so it is listed even when packet + // sockets are not supported. + if ((address_family_filter.native == TCS_FAMILY_ANY.native || + address_family_filter.native == TCS_FAMILY_PACKET.native) && + iter->PhysicalAddressLength == 6) + { + if (out_interface_addresses != NULL && populated < interface_addresses_length) + { + memset(out_interface_addresses[populated].iface.name, '\0', TCS_CFG_INTERFACE_NAME_SIZE); + TcsResult name_sts = adapter_get_friendly_name( + iter, out_interface_addresses[populated].iface.name, TCS_CFG_INTERFACE_NAME_SIZE - 1); + if (name_sts != TCS_SUCCESS) + { + free(adapters); + return TCS_ERROR_SYSTEM; + } + out_interface_addresses[populated].iface.id = iter->IfIndex; + out_interface_addresses[populated].address = TCS_ADDRESS_NONE; + out_interface_addresses[populated].address.family = TCS_FAMILY_PACKET; + out_interface_addresses[populated].address.data.packet.interface_id = iter->IfIndex; + memcpy(out_interface_addresses[populated].address.data.packet.mac, iter->PhysicalAddress, 6); + populated++; + + if (out_length != NULL) + (*out_length)++; + } + else if (out_interface_addresses == NULL && out_length != NULL) + { + (*out_length)++; + } + } } free(adapters); @@ -6115,6 +6872,9 @@ TcsResult tcs_address_socket_local(TcsSocket socket, struct TcsAddress* local_ad if (socket == TCS_SOCKET_INVALID || local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_address_socket_local_impl_npcap(socket, local_address); + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = sizeof native_sockaddr; @@ -6128,6 +6888,8 @@ TcsResult tcs_address_socket_remote(TcsSocket socket, struct TcsAddress* remote_ { if (socket == TCS_SOCKET_INVALID || remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_CONNECTED; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -6143,6 +6905,12 @@ TcsResult tcs_address_socket_family(TcsSocket socket, TcsFamily* out_family) if (socket == TCS_SOCKET_INVALID || out_family == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + { + *out_family = TCS_FAMILY_PACKET; + return TCS_SUCCESS; + } + WSAPROTOCOL_INFOW info; memset(&info, 0, sizeof info); int info_size = sizeof info; @@ -6162,6 +6930,21 @@ TcsResult tcs_address_socket_family(TcsSocket socket, TcsFamily* out_family) // tcs_address_is_multicast() is defined in tinycsocket_common.c // tcs_address_is_broadcast() is defined in tinycsocket_common.c +bool tcs_address_is_supported(const struct TcsAddress* addr) +{ + if (addr == NULL) + return false; + if (addr->family.native == TCS_FAMILY_ANY.native) + return true; + if (addr->family.native == TCS_FAMILY_IPV4.native) + return true; + if (addr->family.native == TCS_FAMILY_IPV6.native) + return true; + if (addr->family.native == TCS_FAMILY_PACKET.native) + return tcs_internal_address_is_supported_impl_npcap(addr); + return false; +} + #endif /**********************************/ @@ -7692,20 +8475,7 @@ bool tcs_address_is_broadcast(const struct TcsAddress* addr) return false; } -bool tcs_address_is_supported(const struct TcsAddress* addr) -{ - if (addr == NULL) - return false; - if (addr->family.native == TCS_FAMILY_ANY.native) - return true; - if (addr->family.native == TCS_FAMILY_IPV4.native) - return true; - if (addr->family.native == TCS_FAMILY_IPV6.native) - return true; - if (addr->family.native == TCS_FAMILY_PACKET.native) - return addr->family.native != -1; - return false; -} +// tcs_address_is_supported() is defined in tinycsocket_posix.c and tinycsocket_win32.c #endif #endif diff --git a/src/tinycsocket_common.c b/src/tinycsocket_common.c index fede4db..087755c 100644 --- a/src/tinycsocket_common.c +++ b/src/tinycsocket_common.c @@ -1523,17 +1523,4 @@ bool tcs_address_is_broadcast(const struct TcsAddress* addr) return false; } -bool tcs_address_is_supported(const struct TcsAddress* addr) -{ - if (addr == NULL) - return false; - if (addr->family.native == TCS_FAMILY_ANY.native) - return true; - if (addr->family.native == TCS_FAMILY_IPV4.native) - return true; - if (addr->family.native == TCS_FAMILY_IPV6.native) - return true; - if (addr->family.native == TCS_FAMILY_PACKET.native) - return addr->family.native != -1; - return false; -} +// tcs_address_is_supported() is defined in tinycsocket_posix.c and tinycsocket_win32.c diff --git a/src/tinycsocket_internal.h b/src/tinycsocket_internal.h index 1bfb867..2137aff 100644 --- a/src/tinycsocket_internal.h +++ b/src/tinycsocket_internal.h @@ -23,7 +23,7 @@ #ifndef TINYCSOCKET_INTERNAL_H_ #define TINYCSOCKET_INTERNAL_H_ -static const char* const TCS_VERSION_TXT = "v0.3.83"; +static const char* const TCS_VERSION_TXT = "v0.3.84"; extern const char* const TCS_LICENSE_TXT; /* @@ -371,7 +371,7 @@ struct TcsPollEvent extern const TcsFamily TCS_FAMILY_ANY; /**< Layer 4 agnostic (AF_UNSPEC) */ extern const TcsFamily TCS_FAMILY_IPV4; /**< INET IPv4 interface (AF_INET) */ extern const TcsFamily TCS_FAMILY_IPV6; /**< INET IPv6 interface (AF_INET6) */ -extern const TcsFamily TCS_FAMILY_PACKET; /**< Layer 2 interface (AF_PACKET on Linux; unsupported elsewhere) */ +extern const TcsFamily TCS_FAMILY_PACKET; /**< Layer 2 interface (AF_PACKET on Linux, Npcap on Windows) */ // gcc may trigger bug #53119 #ifdef __GNUC__ @@ -456,7 +456,11 @@ static const struct TcsPollEvent TCS_POLL_EVENT_EMPTY = {0, 0, false, false, TCS * * This function needs to be called before using any other function in the library. * - * On Windows, it will initialize Winsock, otherwise it does nothing and will always return #TCS_SUCCESS. + * On Windows, it will initialize Winsock and load Npcap (wpcap.dll) if it is installed, which is what makes + * #TCS_FAMILY_PACKET sockets available there. Npcap 1.00 or later is supported. Npcap is optional: without it, + * packet sockets fail with #TCS_ERROR_NOT_SUPPORTED and tcs_address_is_supported() returns false for packet + * addresses. Receiving 802.1Q VLAN tags needs Npcap 1.81 or later, older versions deliver tagged frames with the + * tag stripped. On other platforms it does nothing and will always return #TCS_SUCCESS. * * You can call this multiple times, it will keep a counter of how many times you have called it (RAII friendly). * You should call tcs_lib_cleanup() after you are done with the library (before program exit), atleast the number of times you have called tcs_lib_init(). @@ -487,7 +491,9 @@ TcsResult tcs_lib_init(void); * @brief De-initialize tinycsocket library. * * You need to call this the same amount of times as you have called ::tcs_lib_init(). - * + * + * On Windows, the last call closes any packet socket still open and unloads Npcap. + * * @return #TCS_SUCCESS if successful, otherwise the error code. * * @retval #TCS_ERROR_LIBRARY_NOT_INITIALIZED if you have not called tcs_lib_init() before calling this function. @@ -753,7 +759,7 @@ TcsResult tcs_socket_udp_str(TcsSocket* out_socket, const char* local_address, c /** * @brief Create a packet socket bound to a network interface. * -* Creates an AF_PACKET socket for sending and receiving raw L2 frames. +* Creates a socket for sending and receiving raw L2 frames: AF_PACKET on Linux, Npcap on Windows (see tcs_lib_init()). * The socket is bound to the interface and protocol specified in @p bind_address. * On failure, *out_socket is always set back to #TCS_SOCKET_INVALID. * @@ -789,6 +795,8 @@ TcsResult tcs_socket_udp_str(TcsSocket* out_socket, const char* local_address, c * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p out_socket is NULL, if *out_socket is not #TCS_SOCKET_INVALID, or if @p bind_address is NULL or not TCS_FAMILY_PACKET. +* @retval #TCS_ERROR_NOT_SUPPORTED if the platform has no packet sockets, or on Windows if Npcap is not installed or @p bind_address has interface_id 0. +* @retval #TCS_ERROR_PERMISSION_DENIED if the process may not open packet sockets, e.g. without CAP_NET_RAW on Linux. * * @see tcs_socket_packet_str() * @see tcs_close() @@ -799,6 +807,7 @@ TcsResult tcs_socket_packet(TcsSocket* out_socket, const struct TcsAddress* bind * @brief Create a packet socket bound to a named network interface. * * Looks up the interface by name using ::tcs_interface_list() and delegates to ::tcs_socket_packet(). +* On Windows the names are the adapter names shown in the network settings, e.g. "Ethernet". * On failure, *out_socket is always set back to #TCS_SOCKET_INVALID. * * @code @@ -829,6 +838,7 @@ TcsResult tcs_socket_packet(TcsSocket* out_socket, const struct TcsAddress* bind * * @return #TCS_SUCCESS if successful, otherwise the error code. * @retval #TCS_ERROR_INVALID_ARGUMENT if @p out_socket is NULL, if *out_socket is not #TCS_SOCKET_INVALID, or if @p interface_name is NULL or not found. +* @retval #TCS_ERROR_NOT_SUPPORTED if the platform has no packet sockets, or on Windows if Npcap is not installed. * * @see tcs_socket_packet() * @see tcs_close() @@ -1319,6 +1329,7 @@ TcsResult tcs_poll_destroy(struct TcsPoll** poll); * @param[in] user_data is a pointer of your choice that is associated with the socket. Use NULL if not used. * @param[in] flags is a bitmask of ::TcsPollFlags (e.g. TCS_POLL_READ | TCS_POLL_WRITE). Errors are always reported. * @return #TCS_SUCCESS if successful, otherwise the error code. +* @retval #TCS_ERROR_NOT_SUPPORTED on Windows if @p socket is a packet socket, an Npcap handle cannot be polled together with Winsock sockets. * @see tcs_poll_remove() */ TcsResult tcs_poll_add(struct TcsPoll* poll, TcsSocket socket, void* user_data, uint32_t flags); @@ -1677,6 +1688,10 @@ TcsResult tcs_opt_priority_get(TcsSocket socket, int* out_priority); /** * @brief Join a multicast group on a specific local interface. * +* For packet sockets, @p multicast_address is a MAC address and @p local_address is ignored. On Windows, a membership +* puts the adapter in promiscuous mode and Npcap filters out everything but the adapter's own address, broadcast and +* the joined groups. The first and the last membership reopen the capture handle, frames queued in it are lost. +* * @param[in] socket socket to configure. * @param[in] local_address local interface address to use. * @param[in] multicast_address multicast group address to join. @@ -1816,7 +1831,9 @@ TcsResult tcs_interface_list(struct TcsInterface out_interfaces[], size_t interf * @param[in] addresses_length number of elements in the @p out_addresses array. * @param[out] out_length pointer to receive the number of addresses found. * @return #TCS_SUCCESS if successful, otherwise the error code. -* @retval #TCS_ERROR_NOT_SUPPORTED if @p address_family is not supported on this platform (e.g. ::TCS_FAMILY_PACKET on non-Linux). +* @retval #TCS_ERROR_INVALID_ARGUMENT if the port is not a number in the range 0-65535. +* @retval #TCS_ERROR_ADDRESS_LOOKUP_FAILED if the hostname could not be resolved. +* @retval #TCS_ERROR_NOT_SUPPORTED if @p address_family is not supported on this platform, or is ::TCS_FAMILY_PACKET and @p hostname is not a MAC address. */ TcsResult tcs_address_resolve(const char* hostname, TcsFamily address_family, @@ -1833,7 +1850,7 @@ TcsResult tcs_address_resolve(const char* hostname, * @param[in] interface_addresses_length number of elements in the @p out_interface_addresses array. * @param[out] out_length pointer to receive the total number of results available, which may exceed @p interface_addresses_length. * @return #TCS_SUCCESS if successful, otherwise the error code. -* @note If @p address_family_filter is not supported on this platform (e.g. ::TCS_FAMILY_PACKET on non-Linux), no entries match and *out_length is 0. +* @note If @p address_family_filter is not supported on this platform (e.g. ::TCS_FAMILY_PACKET on macOS), no entries match and *out_length is 0. On Windows, packet addresses are listed with or without Npcap. */ TcsResult tcs_address_list(TcsInterfaceId interface_id_filter, TcsFamily address_family_filter, @@ -1925,7 +1942,7 @@ bool tcs_address_is_multicast(const struct TcsAddress* addr); /** @brief Check if the address is a broadcast address. Returns false for NULL, unknown, or unsupported address families. */ bool tcs_address_is_broadcast(const struct TcsAddress* addr); -/** @brief Check if the address family is known and supported by this platform. Returns false for NULL or unknown families. */ +/** @brief Check if the address family is known and supported by this platform. Returns false for NULL or unknown families. On Windows, packet addresses are supported once tcs_lib_init() has found Npcap. */ bool tcs_address_is_supported(const struct TcsAddress* addr); #ifdef __cplusplus diff --git a/src/tinycsocket_posix.c b/src/tinycsocket_posix.c index 857b467..c6b089d 100644 --- a/src/tinycsocket_posix.c +++ b/src/tinycsocket_posix.c @@ -2041,4 +2041,19 @@ TcsResult tcs_address_socket_family(TcsSocket socket, TcsFamily* out_family) // tcs_address_is_multicast() is defined in tinycsocket_common.c // tcs_address_is_broadcast() is defined in tinycsocket_common.c +bool tcs_address_is_supported(const struct TcsAddress* addr) +{ + if (addr == NULL) + return false; + if (addr->family.native == TCS_FAMILY_ANY.native) + return true; + if (addr->family.native == TCS_FAMILY_IPV4.native) + return true; + if (addr->family.native == TCS_FAMILY_IPV6.native) + return true; + if (addr->family.native == TCS_FAMILY_PACKET.native) + return addr->family.native != -1; // -1 is the sentinel on platforms without AF_PACKET + return false; +} + #endif diff --git a/src/tinycsocket_win32.c b/src/tinycsocket_win32.c index ffbf633..f5341b8 100644 --- a/src/tinycsocket_win32.c +++ b/src/tinycsocket_win32.c @@ -43,6 +43,7 @@ #include // GetAdaptersAddresses #include // getaddrinfo +#include // INT_MAX #include // fprintf (debug diagnostics) #include // Malloc for GetAdaptersAddresses #include // memset @@ -112,7 +113,9 @@ const struct TcsAddressIpv6 TCS_ADDRESS_IPV6_LOOPBACK = {{0, 0, 0, 0, 0, 0, 0, 0 const TcsFamily TCS_FAMILY_ANY = {AF_UNSPEC}; const TcsFamily TCS_FAMILY_IPV4 = {AF_INET}; const TcsFamily TCS_FAMILY_IPV6 = {AF_INET6}; -const TcsFamily TCS_FAMILY_PACKET = {-1}; /* AF_PACKET unsupported on Windows */ +// Windows has no AF_PACKET. Packet sockets are emulated with Npcap, see tcs_lib_init(). The value never reaches +// Winsock, it only needs to differ from every AF_* value. +const TcsFamily TCS_FAMILY_PACKET = {1000}; // Type const TcsSocketType TCS_SOCKET_STREAM = {SOCK_STREAM}; @@ -218,14 +221,50 @@ static TcsResult socketstatus2retcode(int status) } } +// Lists the adapters with GetAdaptersAddresses(). *out_adapters is NULL when there are none. Free it with free(). +static TcsResult adapters_get(PIP_ADAPTER_ADDRESSES* out_adapters) +{ + const int MAX_TRIES = 5; + ULONG buffer_size = 15000; // From msdn recommendation + PIP_ADAPTER_ADDRESSES adapters = NULL; + ULONG adapter_sts = ERROR_NO_DATA; + for (int i = 0; i < MAX_TRIES; ++i) + { + adapters = (PIP_ADAPTER_ADDRESSES)malloc(buffer_size); + if (adapters == NULL) + return TCS_ERROR_MEMORY; + adapter_sts = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + NULL, + adapters, + &buffer_size); + if (adapter_sts != ERROR_BUFFER_OVERFLOW) + break; + free(adapters); + adapters = NULL; + } + if (adapter_sts == ERROR_NO_DATA) + { + free(adapters); + *out_adapters = NULL; + return TCS_SUCCESS; + } + if (adapter_sts != NO_ERROR) + { + // Debug: log the actual Windows error code for diagnostics + fprintf(stderr, "GetAdaptersAddresses failed with error: %lu (0x%lX)\n", adapter_sts, adapter_sts); + free(adapters); + return TCS_ERROR_UNKNOWN; + } + *out_adapters = adapters; + return TCS_SUCCESS; +} + static TcsResult sockaddr2native(const struct TcsAddress* in_addr, PSOCKADDR out_addr, int* out_addrlen) { if (in_addr == NULL || out_addr == NULL) return TCS_ERROR_INVALID_ARGUMENT; - if (in_addr->family.native == -1) // sentinel for unsupported families (e.g. TCS_FAMILY_PACKET on Windows) - return TCS_ERROR_NOT_SUPPORTED; - if (in_addr->family.native == TCS_FAMILY_ANY.native) { return TCS_ERROR_NOT_SUPPORTED; @@ -255,7 +294,7 @@ static TcsResult sockaddr2native(const struct TcsAddress* in_addr, PSOCKADDR out } else if (in_addr->family.native == TCS_FAMILY_PACKET.native) { - return TCS_ERROR_NOT_SUPPORTED; + return TCS_ERROR_NOT_SUPPORTED; // Packet addresses have no sockaddr on Windows, only packet sockets take them } return TCS_ERROR_NOT_SUPPORTED; } @@ -292,16 +331,667 @@ static TcsResult native2sockaddr(const PSOCKADDR in_addr, struct TcsAddress* out return TCS_SUCCESS; } +// ######## Packet Sockets ######## +// +// Windows has no AF_PACKET. A packet socket is an index into a table with the top bit set, which can never collide +// with a kernel SOCKET handle. The public functions test that bit with tcs_internal_is_socket_packet() and hand +// packet sockets to the tcs_internal_*_impl_npcap() functions below, everything else goes to Winsock. Nothing +// outside the Npcap section touches Npcap. + +#define TCS_PACKET_SOCKET_TAG ((TcsSocket)1 << (sizeof(TcsSocket) * 8 - 1)) + +static bool tcs_internal_is_socket_packet(TcsSocket socket) +{ + return socket != TCS_SOCKET_INVALID && (socket & TCS_PACKET_SOCKET_TAG) != 0; +} + +// ######## Packet Sockets: Npcap ######## +// +// Packet sockets are emulated on top of Npcap, whose wpcap.dll is loaded at runtime by tcs_lib_init(). Nothing from +// the Npcap SDK is needed at build time: the few pcap types and functions used are declared here, and their ABI has +// been frozen since WinPcap. +// +// Supported: Npcap 1.00 (2020) or later, the first stable release. Every function resolved by +// tcs_internal_lib_init_impl_npcap() exists in all Npcap releases, since Npcap has always shipped a libpcap newer than 1.5. +// pcap_set_immediate_mode() is what rules out WinPcap and Win10Pcap, which stop at libpcap 1.0; a fallback to +// pcap_setmintocopy(p, 0) would admit them. Behaviour that depends on the version: 802.1Q tags are restored on +// receive from Npcap 1.81 (fixed for TCI 0 in 1.82 and for stacked tags in 1.88). Before that, NDIS has already +// stripped the tag, so tagged frames arrive untagged. The filter in tcs_internal_npcap_socket_filter() matches both cases. + +typedef struct pcap pcap_t; + +struct pcap_pkthdr +{ + struct timeval ts; + unsigned int caplen; + unsigned int len; +}; + +struct bpf_program +{ + unsigned int bf_len; + struct bpf_insn* bf_insns; +}; + +#define TCS_PCAP_ERRBUF_SIZE 256 +#define TCS_PCAP_NETMASK_UNKNOWN 0xffffffffu +#define TCS_PCAP_DLT_EN10MB 1 +#define TCS_PCAP_ERROR_NO_SUCH_DEVICE (-5) +#define TCS_PCAP_ERROR_PERM_DENIED (-8) +#define TCS_PCAP_ERROR_IFACE_NOT_UP (-9) +#define TCS_PCAP_ERROR_PROMISC_PERM_DENIED (-11) + +typedef pcap_t*(__cdecl* TcsPcapCreate)(const char*, char*); +typedef int(__cdecl* TcsPcapSetInt)(pcap_t*, int); +typedef int(__cdecl* TcsPcapGetInt)(pcap_t*); +typedef int(__cdecl* TcsPcapSetNonblock)(pcap_t*, int, char*); +typedef HANDLE(__cdecl* TcsPcapGetEvent)(pcap_t*); +typedef int(__cdecl* TcsPcapCompile)(pcap_t*, struct bpf_program*, const char*, int, unsigned int); +typedef int(__cdecl* TcsPcapSetFilter)(pcap_t*, struct bpf_program*); +typedef void(__cdecl* TcsPcapFreeCode)(struct bpf_program*); +typedef int(__cdecl* TcsPcapNextEx)(pcap_t*, struct pcap_pkthdr**, const unsigned char**); +typedef int(__cdecl* TcsPcapSendPacket)(pcap_t*, const unsigned char*, int); +typedef void(__cdecl* TcsPcapClose)(pcap_t*); + +struct TcsInternalNpcapSocket +{ + pcap_t* pcap; // NULL until bound + int type; // SOCK_DGRAM or SOCK_RAW + uint16_t protocol; + TcsInterfaceId interface_id; + uint8_t mac[6]; // The adapter's own address, source of DGRAM frames + int receive_timeout_ms; // 0 waits forever + bool nonblocking; + uint8_t* groups; // Joined multicast addresses, 6 bytes each + size_t group_count; +}; + +#ifndef TCS_CFG_PACKET_SOCKETS_MAX +#define TCS_CFG_PACKET_SOCKETS_MAX 64 +#endif + +static struct +{ + HMODULE module; // NULL when Npcap is not installed or the library is not initialized + int init_count; // tcs_lib_init() calls not yet matched by tcs_lib_cleanup() + TcsPcapCreate create; + TcsPcapSetInt set_snaplen; + TcsPcapSetInt set_promisc; + TcsPcapSetInt set_immediate_mode; + TcsPcapGetInt activate; + TcsPcapGetInt datalink; + TcsPcapSetNonblock setnonblock; + TcsPcapGetEvent getevent; + TcsPcapCompile compile; + TcsPcapSetFilter setfilter; + TcsPcapFreeCode freecode; + TcsPcapNextEx next_ex; + TcsPcapSendPacket sendpacket; + TcsPcapClose close; + struct TcsInternalNpcapSocket* sockets[TCS_CFG_PACKET_SOCKETS_MAX]; // Indexed by the handle without its tag bit +} g_npcap; + +// The state behind a socket for which tcs_internal_is_socket_packet() is true, NULL once it has been closed +static struct TcsInternalNpcapSocket* tcs_internal_npcap_socket_get(TcsSocket socket) +{ + TcsSocket index = socket & ~TCS_PACKET_SOCKET_TAG; + if (index >= TCS_CFG_PACKET_SOCKETS_MAX) + return NULL; + return g_npcap.sockets[index]; +} + +// Installs a BPF filter giving the socket the frames a Linux packet socket with the same protocol and memberships +// would see. Npcap cannot program the adapter's multicast filter, so memberships need promiscuous mode and the filter +// then also limits the destinations to the adapter's own address, broadcast and the joined groups. +static TcsResult tcs_internal_npcap_socket_filter(const struct TcsInternalNpcapSocket* ps, pcap_t* pcap) +{ + bool filter_protocol = ps->protocol != TCS_PROTOCOL_ETH_ALL; + if (!filter_protocol && ps->group_count == 0) + return TCS_SUCCESS; + + size_t expression_size = 128 + 36 * (ps->group_count + 1); + char* expression = (char*)malloc(expression_size); + if (expression == NULL) + return TCS_ERROR_MEMORY; + size_t pos = 0; + if (ps->group_count > 0) + { + struct TcsAddress mac = TCS_ADDRESS_NONE; + mac.family = TCS_FAMILY_PACKET; + char mac_str[18]; + memcpy(mac.data.packet.mac, ps->mac, 6); + tcs_address_to_str(&mac, mac_str, sizeof mac_str, NULL); + pos += (size_t)snprintf(expression + pos, expression_size - pos, "(ether dst %s or ether broadcast", mac_str); + for (size_t i = 0; i < ps->group_count; ++i) + { + memcpy(mac.data.packet.mac, ps->groups + i * 6, 6); + tcs_address_to_str(&mac, mac_str, sizeof mac_str, NULL); + pos += (size_t)snprintf(expression + pos, expression_size - pos, " or ether dst %s", mac_str); + } + pos += (size_t)snprintf(expression + pos, expression_size - pos, "%s", filter_protocol ? ") and " : ")"); + } + if (filter_protocol) + { + // Tagged frames are matched too, Linux delivers them untagged with the inner protocol + snprintf(expression + pos, + expression_size - pos, + "(ether proto 0x%04x or (vlan and ether proto 0x%04x))", + (unsigned int)ps->protocol, + (unsigned int)ps->protocol); + } + + struct bpf_program program; + memset(&program, 0, sizeof program); + int compile_sts = g_npcap.compile(pcap, &program, expression, 1, TCS_PCAP_NETMASK_UNKNOWN); + free(expression); + if (compile_sts != 0) + return TCS_ERROR_SYSTEM; + int setfilter_sts = g_npcap.setfilter(pcap, &program); + g_npcap.freecode(&program); + return setfilter_sts == 0 ? TCS_SUCCESS : TCS_ERROR_SYSTEM; +} + +// Opens the Npcap handle of a packet socket from its current state, replacing the previous handle on success +static TcsResult tcs_internal_npcap_socket_open(struct TcsInternalNpcapSocket* ps) +{ + PIP_ADAPTER_ADDRESSES adapters = NULL; + TcsResult adapters_sts = adapters_get(&adapters); + if (adapters_sts != TCS_SUCCESS) + return adapters_sts; + + // Npcap names its devices after the adapter GUID + const char prefix[] = "\\Device\\NPF_"; + char device[sizeof prefix + MAX_ADAPTER_NAME_LENGTH + 4]; + bool found = false; + for (PIP_ADAPTER_ADDRESSES iter = adapters; iter != NULL && !found; iter = iter->Next) + { + size_t name_size = strlen(iter->AdapterName) + 1; + if (iter->IfIndex != ps->interface_id || iter->PhysicalAddressLength != 6 || + sizeof prefix - 1 + name_size > sizeof device) + continue; + memcpy(device, prefix, sizeof prefix - 1); + memcpy(device + sizeof prefix - 1, iter->AdapterName, name_size); + memcpy(ps->mac, iter->PhysicalAddress, 6); + found = true; + } + free(adapters); + if (!found) + return TCS_ERROR_INVALID_ARGUMENT; + + char errbuf[TCS_PCAP_ERRBUF_SIZE]; + pcap_t* pcap = g_npcap.create(device, errbuf); + if (pcap == NULL) + return TCS_ERROR_SYSTEM; + g_npcap.set_snaplen(pcap, 65535); + g_npcap.set_promisc(pcap, ps->group_count > 0); + g_npcap.set_immediate_mode(pcap, 1); // Deliver every frame at once instead of batching them in the driver + int activate_sts = g_npcap.activate(pcap); + if (activate_sts < 0) + { + g_npcap.close(pcap); + if (activate_sts == TCS_PCAP_ERROR_PERM_DENIED || activate_sts == TCS_PCAP_ERROR_PROMISC_PERM_DENIED) + return TCS_ERROR_PERMISSION_DENIED; + if (activate_sts == TCS_PCAP_ERROR_NO_SUCH_DEVICE) + return TCS_ERROR_INVALID_ARGUMENT; + if (activate_sts == TCS_PCAP_ERROR_IFACE_NOT_UP) + return TCS_ERROR_NETWORK_UNREACHABLE; + return TCS_ERROR_SYSTEM; + } + // Receiving waits on the handle's event instead, so the socket's timeout and blocking mode can change later + if (g_npcap.datalink(pcap) != TCS_PCAP_DLT_EN10MB || g_npcap.setnonblock(pcap, 1, errbuf) != 0) + { + g_npcap.close(pcap); + return TCS_ERROR_NOT_SUPPORTED; + } + TcsResult filter_sts = tcs_internal_npcap_socket_filter(ps, pcap); + if (filter_sts != TCS_SUCCESS) + { + g_npcap.close(pcap); + return filter_sts; + } + if (ps->pcap != NULL) + g_npcap.close(ps->pcap); + ps->pcap = pcap; + return TCS_SUCCESS; +} + +static void tcs_internal_lib_init_impl_npcap(void) +{ + if (g_npcap.init_count++ != 0) + return; + // Npcap installs to System32\Npcap. LOAD_WITH_ALTERED_SEARCH_PATH makes the loader find Packet.dll next to + // wpcap.dll without SetDllDirectory(), which would affect the whole process. The plain name is the fallback + // recommended by Npcap, for installations in WinPcap compatible mode. + const char suffix[] = "\\Npcap\\wpcap.dll"; + char path[MAX_PATH]; + UINT system_dir_size = GetSystemDirectoryA(path, MAX_PATH); + HMODULE m = NULL; + if (system_dir_size != 0 && system_dir_size + sizeof suffix <= MAX_PATH) + { + memcpy(path + system_dir_size, suffix, sizeof suffix); + m = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + } + if (m == NULL) + m = LoadLibraryA("wpcap.dll"); + if (m == NULL) + return; + + // FARPROC is cast through void (*)(void), which gcc treats as compatible with every function pointer type + g_npcap.create = (TcsPcapCreate)(void (*)(void))GetProcAddress(m, "pcap_create"); + g_npcap.set_snaplen = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_snaplen"); + g_npcap.set_promisc = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_promisc"); + g_npcap.set_immediate_mode = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_immediate_mode"); + g_npcap.activate = (TcsPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_activate"); + g_npcap.datalink = (TcsPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_datalink"); + g_npcap.setnonblock = (TcsPcapSetNonblock)(void (*)(void))GetProcAddress(m, "pcap_setnonblock"); + g_npcap.getevent = (TcsPcapGetEvent)(void (*)(void))GetProcAddress(m, "pcap_getevent"); + g_npcap.compile = (TcsPcapCompile)(void (*)(void))GetProcAddress(m, "pcap_compile"); + g_npcap.setfilter = (TcsPcapSetFilter)(void (*)(void))GetProcAddress(m, "pcap_setfilter"); + g_npcap.freecode = (TcsPcapFreeCode)(void (*)(void))GetProcAddress(m, "pcap_freecode"); + g_npcap.next_ex = (TcsPcapNextEx)(void (*)(void))GetProcAddress(m, "pcap_next_ex"); + g_npcap.sendpacket = (TcsPcapSendPacket)(void (*)(void))GetProcAddress(m, "pcap_sendpacket"); + g_npcap.close = (TcsPcapClose)(void (*)(void))GetProcAddress(m, "pcap_close"); + + if (g_npcap.create == NULL || g_npcap.set_snaplen == NULL || g_npcap.set_promisc == NULL || + g_npcap.set_immediate_mode == NULL || g_npcap.activate == NULL || g_npcap.datalink == NULL || + g_npcap.setnonblock == NULL || g_npcap.getevent == NULL || g_npcap.compile == NULL || + g_npcap.setfilter == NULL || g_npcap.freecode == NULL || g_npcap.next_ex == NULL || + g_npcap.sendpacket == NULL || g_npcap.close == NULL) + { + FreeLibrary(m); + return; + } + g_npcap.module = m; +} + +static void tcs_internal_lib_cleanup_impl_npcap(void) +{ + if (g_npcap.init_count == 0 || --g_npcap.init_count != 0 || g_npcap.module == NULL) + return; + // Like WSACleanup(), the last cleanup closes every socket still open + for (size_t i = 0; i < TCS_CFG_PACKET_SOCKETS_MAX; ++i) + { + TcsSocket socket = TCS_PACKET_SOCKET_TAG | (TcsSocket)i; + if (g_npcap.sockets[i] != NULL) + tcs_close(&socket); + } + FreeLibrary(g_npcap.module); + memset(&g_npcap, 0, sizeof g_npcap); +} + +static TcsResult tcs_internal_socket_impl_npcap(TcsSocket* out_socket, + TcsFamily family, + TcsSocketType type, + TcsProtocol protocol) +{ + (void)family; // Always TCS_FAMILY_PACKET + if (g_npcap.module == NULL) + return TCS_ERROR_NOT_SUPPORTED; + if (type.native != SOCK_DGRAM && type.native != SOCK_RAW) + return TCS_ERROR_NOT_SUPPORTED; + size_t index = 0; + while (index < TCS_CFG_PACKET_SOCKETS_MAX && g_npcap.sockets[index] != NULL) + ++index; + if (index == TCS_CFG_PACKET_SOCKETS_MAX) + return TCS_ERROR_MEMORY; + struct TcsInternalNpcapSocket* ps = (struct TcsInternalNpcapSocket*)malloc(sizeof *ps); + if (ps == NULL) + return TCS_ERROR_MEMORY; + memset(ps, 0, sizeof *ps); + ps->type = type.native; + ps->protocol = protocol; + g_npcap.sockets[index] = ps; + *out_socket = TCS_PACKET_SOCKET_TAG | (TcsSocket)index; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_close_impl_npcap(TcsSocket* socket) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(*socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (ps->pcap != NULL) + g_npcap.close(ps->pcap); + free(ps->groups); + free(ps); + g_npcap.sockets[*socket & ~TCS_PACKET_SOCKET_TAG] = NULL; + *socket = TCS_SOCKET_INVALID; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_bind_impl_npcap(TcsSocket socket, const struct TcsAddress* address) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (address->family.native != TCS_FAMILY_PACKET.native) + return TCS_ERROR_INVALID_ARGUMENT; + if (address->data.packet.interface_id == 0) + return TCS_ERROR_NOT_SUPPORTED; // An Npcap handle belongs to one adapter, there is no "all interfaces" + ps->interface_id = address->data.packet.interface_id; + if (address->data.packet.protocol != 0) + ps->protocol = address->data.packet.protocol; + return tcs_internal_npcap_socket_open(ps); +} + +static TcsResult tcs_internal_send_impl_npcap(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + size_t* sent_size) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + // Only whole frames can be sent without a destination + if (ps->type != SOCK_RAW) + return TCS_ERROR_INVALID_ARGUMENT; + struct TcsAddress self = TCS_ADDRESS_NONE; + self.family = TCS_FAMILY_PACKET; + self.data.packet.interface_id = ps->interface_id; + return tcs_send_to(socket, buffer, buffer_size, flags & ~TCS_MSG_SENDALL, &self, sent_size); +} + +static TcsResult tcs_internal_send_to_impl_npcap(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + const struct TcsAddress* destination_address, + size_t* sent_size) +{ + (void)flags; // No send flag applies to a frame + if (sent_size != NULL) + *sent_size = 0; + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (ps->pcap == NULL) + return TCS_ERROR_INVALID_ARGUMENT; // Not bound + if (destination_address->family.native != TCS_FAMILY_PACKET.native) + return TCS_ERROR_INVALID_ARGUMENT; + if (destination_address->data.packet.interface_id != 0 && + destination_address->data.packet.interface_id != ps->interface_id) + return TCS_ERROR_INVALID_ARGUMENT; + if (buffer_size > INT_MAX - 14) + return TCS_ERROR_INVALID_ARGUMENT; + + const uint8_t* frame = buffer; + size_t frame_size = buffer_size; + uint8_t* dgram_frame = NULL; + if (ps->type == SOCK_DGRAM) + { + // Ethernet header: destination, source, EtherType. Like Linux, the socket's protocol is the fallback. + uint16_t protocol = + destination_address->data.packet.protocol != 0 ? destination_address->data.packet.protocol : ps->protocol; + frame_size = buffer_size + 14; + dgram_frame = (uint8_t*)malloc(frame_size); + if (dgram_frame == NULL) + return TCS_ERROR_MEMORY; + memcpy(dgram_frame, destination_address->data.packet.mac, 6); + memcpy(dgram_frame + 6, ps->mac, 6); + dgram_frame[12] = (uint8_t)(protocol >> 8); + dgram_frame[13] = (uint8_t)(protocol & 0xFF); + if (buffer_size > 0) + memcpy(dgram_frame + 14, buffer, buffer_size); + frame = dgram_frame; + } + int send_sts = g_npcap.sendpacket(ps->pcap, frame, (int)frame_size); + free(dgram_frame); + if (send_sts != 0) + return TCS_ERROR_SYSTEM; + if (sent_size != NULL) + *sent_size = buffer_size; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_sendv_impl_npcap(TcsSocket socket, + const struct TcsIoVec* iov, + size_t iov_length, + uint32_t flags, + size_t* sent_size) +{ + // A frame is sent in one piece, so the buffers are gathered first + size_t total_size = 0; + for (size_t i = 0; i < iov_length; ++i) + { + if (iov[i].buffer == NULL && iov[i].buffer_size > 0) + return TCS_ERROR_INVALID_ARGUMENT; + total_size += iov[i].buffer_size; + } + uint8_t* gathered = (uint8_t*)malloc(total_size > 0 ? total_size : 1); + if (gathered == NULL) + return TCS_ERROR_MEMORY; + size_t pos = 0; + for (size_t i = 0; i < iov_length; ++i) + { + if (iov[i].buffer_size > 0) + memcpy(gathered + pos, iov[i].buffer, iov[i].buffer_size); + pos += iov[i].buffer_size; + } + TcsResult send_sts = tcs_send(socket, gathered, total_size, flags, sent_size); + free(gathered); + return send_sts; +} + +static TcsResult tcs_internal_receive_from_impl_npcap(TcsSocket socket, + uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + struct TcsAddress* source_address, + size_t* received_size) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (ps->pcap == NULL) + return TCS_ERROR_INVALID_ARGUMENT; // Not bound + if (flags & TCS_MSG_PEEK) + return TCS_ERROR_NOT_SUPPORTED; + + DWORD start = GetTickCount(); + for (;;) + { + struct pcap_pkthdr* header = NULL; + const unsigned char* frame = NULL; + int next_sts = g_npcap.next_ex(ps->pcap, &header, &frame); + if (next_sts < 0) + return TCS_ERROR_SYSTEM; + if (next_sts == 1) + { + size_t frame_size = header->caplen; + size_t offset = 0; + uint16_t protocol = frame_size >= 14 ? (uint16_t)((frame[12] << 8) | frame[13]) : 0; + if (ps->type == SOCK_DGRAM) + { + if (frame_size < 14) + continue; + offset = 14; + if (protocol == 0x8100 && frame_size >= 18) // 802.1Q tag, stripped like Linux does + { + protocol = (uint16_t)((frame[16] << 8) | frame[17]); + offset = 18; + } + } + size_t payload_size = frame_size - offset; + size_t copy_size = payload_size < buffer_size ? payload_size : buffer_size; + if (copy_size > 0) + memcpy(buffer, frame + offset, copy_size); + if (received_size != NULL) + *received_size = copy_size; + if (source_address != NULL) + { + *source_address = TCS_ADDRESS_NONE; + source_address->family = TCS_FAMILY_PACKET; + source_address->data.packet.interface_id = ps->interface_id; + source_address->data.packet.protocol = protocol; + if (frame_size >= 12) + memcpy(source_address->data.packet.mac, frame + 6, 6); + } + return TCS_SUCCESS; + } + + // Nothing queued. The event is signaled by the driver when a frame arrives. + if (ps->nonblocking) + return TCS_ERROR_WOULD_BLOCK; + DWORD wait_ms = INFINITE; + if (ps->receive_timeout_ms > 0) + { + DWORD elapsed_ms = GetTickCount() - start; + if (elapsed_ms >= (DWORD)ps->receive_timeout_ms) + return TCS_ERROR_TIMED_OUT; + wait_ms = (DWORD)ps->receive_timeout_ms - elapsed_ms; + } + DWORD wait_sts = WaitForSingleObject(g_npcap.getevent(ps->pcap), wait_ms); + if (wait_sts == WAIT_TIMEOUT) + return TCS_ERROR_TIMED_OUT; + if (wait_sts != WAIT_OBJECT_0) + return TCS_ERROR_SYSTEM; + } +} + +static TcsResult tcs_internal_opt_get_impl_npcap(TcsSocket socket, + int32_t level, + int32_t option_name, + void* out_option_value, + size_t* option_size) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (level != TCS_SOL_SOCKET || option_name != TCS_SO_TYPE || *option_size < sizeof ps->type) + return TCS_ERROR_NOT_SUPPORTED; + memcpy(out_option_value, &ps->type, sizeof ps->type); + *option_size = sizeof ps->type; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_receive_timeout_set_impl_npcap(TcsSocket socket, int timeout_ms) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + ps->receive_timeout_ms = timeout_ms; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_receive_timeout_get_impl_npcap(TcsSocket socket, int* timeout_ms) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + *timeout_ms = ps->receive_timeout_ms; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_nonblocking_set_impl_npcap(TcsSocket socket, bool do_non_blocking) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + ps->nonblocking = do_non_blocking; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_nonblocking_get_impl_npcap(TcsSocket socket, bool* is_non_blocking) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + *is_non_blocking = ps->nonblocking; + return TCS_SUCCESS; +} + +static TcsResult tcs_internal_opt_membership_add_to_impl_npcap(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) +{ + (void)local_address; // A packet socket is bound to one interface + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (multicast_address->family.native != TCS_FAMILY_PACKET.native) + return TCS_ERROR_INVALID_ARGUMENT; + uint8_t* groups = (uint8_t*)realloc(ps->groups, (ps->group_count + 1) * 6); + if (groups == NULL) + return TCS_ERROR_MEMORY; + memcpy(groups + ps->group_count * 6, multicast_address->data.packet.mac, 6); + ps->groups = groups; + ps->group_count++; + if (ps->pcap == NULL) + return TCS_SUCCESS; // Applied by tcs_bind() + // Promiscuous mode is fixed when a handle is opened, so the first group reopens it. Queued frames are lost. + TcsResult sts = + ps->group_count == 1 ? tcs_internal_npcap_socket_open(ps) : tcs_internal_npcap_socket_filter(ps, ps->pcap); + if (sts != TCS_SUCCESS) + ps->group_count--; + return sts; +} + +static TcsResult tcs_internal_opt_membership_drop_from_impl_npcap(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) +{ + (void)local_address; // A packet socket is bound to one interface + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + if (multicast_address->family.native != TCS_FAMILY_PACKET.native) + return TCS_ERROR_INVALID_ARGUMENT; + size_t i = 0; + while (i < ps->group_count && memcmp(ps->groups + i * 6, multicast_address->data.packet.mac, 6) != 0) + ++i; + if (i == ps->group_count) + return TCS_ERROR_INVALID_ARGUMENT; + // The group is swapped to the end, so a failed update is undone by restoring the count + size_t last = ps->group_count - 1; + if (i != last) + { + uint8_t dropped[6]; + memcpy(dropped, ps->groups + i * 6, 6); + memcpy(ps->groups + i * 6, ps->groups + last * 6, 6); + memcpy(ps->groups + last * 6, dropped, 6); + } + ps->group_count--; + if (ps->pcap == NULL) + return TCS_SUCCESS; + TcsResult sts = + ps->group_count == 0 ? tcs_internal_npcap_socket_open(ps) : tcs_internal_npcap_socket_filter(ps, ps->pcap); + if (sts != TCS_SUCCESS) + ps->group_count++; + return sts; +} + +static TcsResult tcs_internal_address_socket_local_impl_npcap(TcsSocket socket, struct TcsAddress* local_address) +{ + struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); + if (ps == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + *local_address = TCS_ADDRESS_NONE; + local_address->family = TCS_FAMILY_PACKET; + local_address->data.packet.interface_id = ps->interface_id; + local_address->data.packet.protocol = ps->protocol; + memcpy(local_address->data.packet.mac, ps->mac, 6); + return TCS_SUCCESS; +} + +static bool tcs_internal_address_is_supported_impl_npcap(const struct TcsAddress* addr) +{ + // Npcap is loaded by tcs_lib_init() when it is installed + return addr->family.native == TCS_FAMILY_PACKET.native && g_npcap.module != NULL; +} + TcsResult tcs_lib_init(void) { WSADATA wsa_data; if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) return TCS_ERROR_SYSTEM; + tcs_internal_lib_init_impl_npcap(); // Npcap is optional, without it packet sockets are not supported return TCS_SUCCESS; } TcsResult tcs_lib_cleanup(void) { + tcs_internal_lib_cleanup_impl_npcap(); WSACleanup(); return TCS_SUCCESS; } @@ -310,8 +1000,9 @@ TcsResult tcs_socket(TcsSocket* out_socket, TcsFamily family, TcsSocketType type { if (out_socket == NULL || *out_socket != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; - if (family.native == -1) // sentinel for unsupported families (e.g. TCS_FAMILY_PACKET on Windows) - return TCS_ERROR_NOT_SUPPORTED; + + if (family.native == TCS_FAMILY_PACKET.native) + return tcs_internal_socket_impl_npcap(out_socket, family, type, protocol); TcsSocket new_socket = socket(family.native, type.native, (int)protocol); @@ -339,6 +1030,9 @@ TcsResult tcs_close(TcsSocket* socket) if (socket == NULL || *socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(*socket)) + return tcs_internal_close_impl_npcap(socket); + int close_status = closesocket(*socket); if (close_status != SOCKET_ERROR) { @@ -360,6 +1054,9 @@ TcsResult tcs_bind(TcsSocket socket, const struct TcsAddress* address) if (address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_bind_impl_npcap(socket, address); + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; @@ -378,6 +1075,9 @@ TcsResult tcs_connect(TcsSocket socket, const struct TcsAddress* address) if (address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; @@ -403,6 +1103,8 @@ TcsResult tcs_listen(TcsSocket socket, int backlog) { if (socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; int status = listen(socket, (int)backlog); return socketstatus2retcode(status); @@ -412,6 +1114,8 @@ TcsResult tcs_accept(TcsSocket listener, TcsSocket* out_socket, struct TcsAddres { if (listener == TCS_SOCKET_INVALID || out_socket == NULL || *out_socket != TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(listener)) + return TCS_ERROR_NOT_SUPPORTED; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -443,6 +1147,8 @@ TcsResult tcs_shutdown(TcsSocket socket, TcsShutdownDirection direction) return TCS_ERROR_INVALID_ARGUMENT; if (direction < 0 || direction > 2) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; const int how = LUT[direction]; int shutdown_status = shutdown(socket, how); @@ -462,6 +1168,9 @@ TcsResult tcs_send(TcsSocket socket, const uint8_t* buffer, size_t buffer_size, if (buffer == NULL || buffer_size == 0) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_send_impl_npcap(socket, buffer, buffer_size, flags, sent_size); + // Send all if (flags & TCS_MSG_SENDALL) { @@ -518,6 +1227,9 @@ TcsResult tcs_send_to(TcsSocket socket, if (flags & TCS_MSG_SENDALL) return TCS_ERROR_NOT_IMPLEMENTED; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_send_to_impl_npcap(socket, buffer, buffer_size, flags, destination_address, sent_size); + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = 0; @@ -551,6 +1263,9 @@ TcsResult tcs_sendv(TcsSocket socket, const struct TcsIoVec* iov, size_t iov_len if (flags & TCS_MSG_SENDALL) return TCS_ERROR_NOT_IMPLEMENTED; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_sendv_impl_npcap(socket, iov, iov_length, flags, sent_size); + WSABUF stack_buffers[TCS_CFG_SENDV_STACK_MAX]; WSABUF* native_buffers = stack_buffers; WSABUF* heap_buffers = NULL; @@ -608,6 +1323,9 @@ TcsResult tcs_receive(TcsSocket socket, uint8_t* buffer, size_t buffer_size, uin if (buffer == NULL && buffer_size > 0) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_receive_from(socket, buffer, buffer_size, flags, NULL, received_size); + #if WINVER <= 0x501 if (flags & TCS_MSG_WAITALL) { @@ -673,6 +1391,9 @@ TcsResult tcs_receive_from(TcsSocket socket, if (received_size != NULL) *received_size = 0; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_receive_from_impl_npcap(socket, buffer, buffer_size, flags, source_address, received_size); + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = sizeof(native_sockaddr); @@ -758,6 +1479,8 @@ TcsResult tcs_poll_add(struct TcsPoll* poll, TcsSocket socket, void* user_data, { if (poll == NULL || socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; // select() cannot wait on an Npcap handle int map_sts = tds_map_socket_user_add(&poll->user_data, socket, user_data); if (map_sts != 0) @@ -1111,6 +1834,8 @@ TcsResult tcs_opt_set(TcsSocket socket, if (option_name == -1) return TCS_ERROR_NOT_IMPLEMENTED; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; int sockopt_status = setsockopt(socket, (int)level, (int)option_name, (const char*)option_value, (int)option_size); return socketstatus2retcode(sockopt_status); @@ -1124,6 +1849,9 @@ TcsResult tcs_opt_get(TcsSocket socket, int32_t level, int32_t option_name, void if (option_name == -1) return TCS_ERROR_NOT_IMPLEMENTED; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_get_impl_npcap(socket, level, option_name, out_option_value, option_size); + int optlen = (int)*option_size; int sockopt_status = getsockopt(socket, (int)level, (int)option_name, (char*)out_option_value, &optlen); if (sockopt_status == 0) @@ -1189,6 +1917,9 @@ TcsResult tcs_opt_receive_timeout_set(TcsSocket socket, int timeout_ms) if (socket == TCS_SOCKET_INVALID || timeout_ms < 0) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_receive_timeout_set_impl_npcap(socket, timeout_ms); + return tcs_opt_set(socket, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &timeout_ms, sizeof(timeout_ms)); } @@ -1197,6 +1928,9 @@ TcsResult tcs_opt_receive_timeout_get(TcsSocket socket, int* timeout_ms) if (socket == TCS_SOCKET_INVALID || timeout_ms == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_receive_timeout_get_impl_npcap(socket, timeout_ms); + DWORD t = 0; size_t t_size = sizeof(t); TcsResult sts = tcs_opt_get(socket, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &t, &t_size); @@ -1248,6 +1982,9 @@ TcsResult tcs_opt_nonblocking_set(TcsSocket socket, bool do_non_blocking) if (socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_nonblocking_set_impl_npcap(socket, do_non_blocking); + u_long mode = do_non_blocking ? 1 : 0; int sts = ioctlsocket((SOCKET)socket, (long)FIONBIO, &mode); return socketstatus2retcode(sts); @@ -1255,8 +1992,12 @@ TcsResult tcs_opt_nonblocking_set(TcsSocket socket, bool do_non_blocking) TcsResult tcs_opt_nonblocking_get(TcsSocket socket, bool* is_non_blocking) { - (void)socket; - (void)is_non_blocking; + if (socket == TCS_SOCKET_INVALID || is_non_blocking == NULL) + return TCS_ERROR_INVALID_ARGUMENT; + + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_nonblocking_get_impl_npcap(socket, is_non_blocking); + // Win32 does not provide an API to query the non-blocking state of a socket return TCS_ERROR_NOT_SUPPORTED; } @@ -1271,6 +2012,9 @@ TcsResult tcs_opt_membership_add(TcsSocket socket, const struct TcsAddress* mult if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_opt_membership_add_to(socket, NULL, multicast_address); + SOCKADDR_STORAGE address_native_local; memset(&address_native_local, 0, sizeof address_native_local); int address_native_local_size = sizeof address_native_local; @@ -1297,6 +2041,9 @@ TcsResult tcs_opt_membership_add_to(TcsSocket socket, if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_membership_add_to_impl_npcap(socket, local_address, multicast_address); + if (multicast_address->family.native == TCS_FAMILY_IPV4.native) { struct ip_mreq imr; @@ -1328,6 +2075,9 @@ TcsResult tcs_opt_membership_drop(TcsSocket socket, const struct TcsAddress* mul if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_opt_membership_drop_from(socket, NULL, multicast_address); + SOCKADDR_STORAGE address_native_local; memset(&address_native_local, 0, sizeof address_native_local); int address_native_local_size = sizeof address_native_local; @@ -1354,6 +2104,9 @@ TcsResult tcs_opt_membership_drop_from(TcsSocket socket, if (multicast_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_opt_membership_drop_from_impl_npcap(socket, local_address, multicast_address); + if (multicast_address->family.native == TCS_FAMILY_IPV4.native) { struct ip_mreq imr; @@ -1379,6 +2132,8 @@ TcsResult tcs_opt_multicast_interface_set(TcsSocket socket, const struct TcsAddr { if (socket == TCS_SOCKET_INVALID || local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; if (local_address->family.native == TCS_FAMILY_IPV4.native) { @@ -1398,6 +2153,8 @@ TcsResult tcs_opt_multicast_loop_set(TcsSocket socket, bool do_loopback) { if (socket == TCS_SOCKET_INVALID) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; DWORD val = do_loopback ? 1 : 0; return tcs_opt_set(socket, TCS_SOL_IP, IP_MULTICAST_LOOP, &val, sizeof(val)); @@ -1407,6 +2164,8 @@ TcsResult tcs_opt_multicast_loop_get(TcsSocket socket, bool* is_loopback) { if (socket == TCS_SOCKET_INVALID || is_loopback == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_SUPPORTED; DWORD val = 0; size_t s = sizeof(val); @@ -1501,44 +2260,12 @@ TcsResult tcs_interface_list(struct TcsInterface out_interfaces[], size_t interf if (out_length != NULL) *out_length = 0; - const int MAX_TRIES = 5; - ULONG buffer_size = 15000; // From msdn recommendation PIP_ADAPTER_ADDRESSES adapters = NULL; - ULONG adapter_sts = ERROR_NO_DATA; - for (int i = 0; i < MAX_TRIES; ++i) - { - adapters = (PIP_ADAPTER_ADDRESSES)malloc(buffer_size); - if (adapters == NULL) - return TCS_ERROR_MEMORY; - adapter_sts = GetAdaptersAddresses(AF_UNSPEC, - GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, - adapters, - &buffer_size); - if (adapter_sts == ERROR_BUFFER_OVERFLOW) - { - free(adapters); - adapters = NULL; - } - else - { - break; - } - } - if (adapter_sts == ERROR_NO_DATA) - { - if (adapters != NULL) - free(adapters); + TcsResult adapters_sts = adapters_get(&adapters); + if (adapters_sts != TCS_SUCCESS) + return adapters_sts; + if (adapters == NULL) return TCS_SUCCESS; - } - if (adapter_sts != NO_ERROR) - { - // Debug: log the actual Windows error code for diagnostics - fprintf(stderr, "GetAdaptersAddresses failed with error: %lu (0x%lX)\n", adapter_sts, adapter_sts); - if (adapters != NULL) - free(adapters); - return TCS_ERROR_UNKNOWN; - } { size_t i = 0; @@ -1605,7 +2332,7 @@ TcsResult tcs_address_resolve(const char* hostname, } } - if (address_family.native == -1) // sentinel for unsupported families (e.g. TCS_FAMILY_PACKET on Windows) + if (address_family.native == TCS_FAMILY_PACKET.native) // Only the numeric fast path above can give a MAC address return TCS_ERROR_NOT_SUPPORTED; ADDRINFOA native_hints; @@ -1664,42 +2391,12 @@ TcsResult tcs_address_list(unsigned int interface_id_filter, if (out_length != NULL) *out_length = 0; - const int MAX_TRIES = 5; - ULONG buffer_size = 15000; PIP_ADAPTER_ADDRESSES adapters = NULL; - ULONG adapter_sts = ERROR_NO_DATA; - for (int i = 0; i < MAX_TRIES; ++i) - { - adapters = (PIP_ADAPTER_ADDRESSES)malloc(buffer_size); - if (adapters == NULL) - return TCS_ERROR_MEMORY; - adapter_sts = GetAdaptersAddresses(AF_UNSPEC, - GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - NULL, - adapters, - &buffer_size); - if (adapter_sts == ERROR_BUFFER_OVERFLOW) - { - free(adapters); - adapters = NULL; - } - else - { - break; - } - } - if (adapter_sts == ERROR_NO_DATA) - { - if (adapters != NULL) - free(adapters); + TcsResult adapters_sts = adapters_get(&adapters); + if (adapters_sts != TCS_SUCCESS) + return adapters_sts; + if (adapters == NULL) return TCS_SUCCESS; - } - if (adapter_sts != NO_ERROR) - { - if (adapters != NULL) - free(adapters); - return TCS_ERROR_UNKNOWN; - } size_t populated = 0; for (PIP_ADAPTER_ADDRESSES iter = adapters; iter != NULL; iter = iter->Next) @@ -1723,13 +2420,9 @@ TcsResult tcs_address_list(unsigned int interface_id_filter, if (address_iter->Address.lpSockaddr == NULL) continue; - if (address_family_filter.native != TCS_FAMILY_ANY.native) - { - if (address_family_filter.native == -1) /* unsupported sentinel */ - continue; - if (address_iter->Address.lpSockaddr->sa_family != address_family_filter.native) - continue; - } + if (address_family_filter.native != TCS_FAMILY_ANY.native && + address_iter->Address.lpSockaddr->sa_family != address_family_filter.native) + continue; struct TcsAddress address = TCS_ADDRESS_NONE; TcsResult convert_sts = native2sockaddr(address_iter->Address.lpSockaddr, &address); @@ -1758,6 +2451,38 @@ TcsResult tcs_address_list(unsigned int interface_id_filter, (*out_length)++; } } + + // The layer 2 address of adapters with an Ethernet MAC. Needs no Npcap, so it is listed even when packet + // sockets are not supported. + if ((address_family_filter.native == TCS_FAMILY_ANY.native || + address_family_filter.native == TCS_FAMILY_PACKET.native) && + iter->PhysicalAddressLength == 6) + { + if (out_interface_addresses != NULL && populated < interface_addresses_length) + { + memset(out_interface_addresses[populated].iface.name, '\0', TCS_CFG_INTERFACE_NAME_SIZE); + TcsResult name_sts = adapter_get_friendly_name( + iter, out_interface_addresses[populated].iface.name, TCS_CFG_INTERFACE_NAME_SIZE - 1); + if (name_sts != TCS_SUCCESS) + { + free(adapters); + return TCS_ERROR_SYSTEM; + } + out_interface_addresses[populated].iface.id = iter->IfIndex; + out_interface_addresses[populated].address = TCS_ADDRESS_NONE; + out_interface_addresses[populated].address.family = TCS_FAMILY_PACKET; + out_interface_addresses[populated].address.data.packet.interface_id = iter->IfIndex; + memcpy(out_interface_addresses[populated].address.data.packet.mac, iter->PhysicalAddress, 6); + populated++; + + if (out_length != NULL) + (*out_length)++; + } + else if (out_interface_addresses == NULL && out_length != NULL) + { + (*out_length)++; + } + } } free(adapters); @@ -1769,6 +2494,9 @@ TcsResult tcs_address_socket_local(TcsSocket socket, struct TcsAddress* local_ad if (socket == TCS_SOCKET_INVALID || local_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return tcs_internal_address_socket_local_impl_npcap(socket, local_address); + SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); int addrlen = sizeof native_sockaddr; @@ -1782,6 +2510,8 @@ TcsResult tcs_address_socket_remote(TcsSocket socket, struct TcsAddress* remote_ { if (socket == TCS_SOCKET_INVALID || remote_address == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + return TCS_ERROR_NOT_CONNECTED; SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -1797,6 +2527,12 @@ TcsResult tcs_address_socket_family(TcsSocket socket, TcsFamily* out_family) if (socket == TCS_SOCKET_INVALID || out_family == NULL) return TCS_ERROR_INVALID_ARGUMENT; + if (tcs_internal_is_socket_packet(socket)) + { + *out_family = TCS_FAMILY_PACKET; + return TCS_SUCCESS; + } + WSAPROTOCOL_INFOW info; memset(&info, 0, sizeof info); int info_size = sizeof info; @@ -1816,4 +2552,19 @@ TcsResult tcs_address_socket_family(TcsSocket socket, TcsFamily* out_family) // tcs_address_is_multicast() is defined in tinycsocket_common.c // tcs_address_is_broadcast() is defined in tinycsocket_common.c +bool tcs_address_is_supported(const struct TcsAddress* addr) +{ + if (addr == NULL) + return false; + if (addr->family.native == TCS_FAMILY_ANY.native) + return true; + if (addr->family.native == TCS_FAMILY_IPV4.native) + return true; + if (addr->family.native == TCS_FAMILY_IPV6.native) + return true; + if (addr->family.native == TCS_FAMILY_PACKET.native) + return tcs_internal_address_is_supported_impl_npcap(addr); + return false; +} + #endif From 7c308d69e92b108e57f766effd65fb34daf31031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindel=C3=B6w?= Date: Wed, 28 Oct 2026 14:05:51 +0100 Subject: [PATCH 3/4] Function ptr for win32 packet impl --- include/tinycsocket.h | 525 ++++++++++++++++++++++++++++------------ src/tinycsocket_win32.c | 525 ++++++++++++++++++++++++++++------------ 2 files changed, 738 insertions(+), 312 deletions(-) diff --git a/include/tinycsocket.h b/include/tinycsocket.h index e24b6cf..f0ad72c 100644 --- a/include/tinycsocket.h +++ b/include/tinycsocket.h @@ -4713,8 +4713,8 @@ static TcsResult native2sockaddr(const PSOCKADDR in_addr, struct TcsAddress* out // // Windows has no AF_PACKET. A packet socket is an index into a table with the top bit set, which can never collide // with a kernel SOCKET handle. The public functions test that bit with tcs_internal_is_socket_packet() and hand -// packet sockets to the tcs_internal_*_impl_npcap() functions below, everything else goes to Winsock. Nothing -// outside the Npcap section touches Npcap. +// packet sockets to the tcs_internal_packet_*_impl function pointers below, everything else goes to Winsock. +// Nothing outside the Npcap section touches Npcap. #define TCS_PACKET_SOCKET_TAG ((TcsSocket)1 << (sizeof(TcsSocket) * 8 - 1)) @@ -4723,6 +4723,190 @@ static bool tcs_internal_is_socket_packet(TcsSocket socket) return socket != TCS_SOCKET_INVALID && (socket & TCS_PACKET_SOCKET_TAG) != 0; } +// Packet socket operations go through these function pointers, so that the implementation can be chosen at runtime. +// They start at the tcs_internal_packet_*_impl_not_supported() functions below. The Npcap section points them at +// its implementation when tcs_lib_init() has loaded Npcap, and back when tcs_lib_cleanup() has unloaded it. + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) // Unreferenced formal parameter, the stubs ignore their arguments +#endif + +static TcsResult tcs_internal_packet_socket_impl_not_supported(TcsSocket* out_socket, + TcsFamily family, + TcsSocketType type, + TcsProtocol protocol) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_close_impl_not_supported(TcsSocket* socket) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_bind_impl_not_supported(TcsSocket socket, const struct TcsAddress* address) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_send_impl_not_supported(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + size_t* sent_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_send_to_impl_not_supported(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + const struct TcsAddress* destination_address, + size_t* sent_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_sendv_impl_not_supported(TcsSocket socket, + const struct TcsIoVec* iov, + size_t iov_length, + uint32_t flags, + size_t* sent_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_receive_from_impl_not_supported(TcsSocket socket, + uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + struct TcsAddress* source_address, + size_t* received_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_get_impl_not_supported(TcsSocket socket, + int32_t level, + int32_t option_name, + void* out_option_value, + size_t* option_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_receive_timeout_set_impl_not_supported(TcsSocket socket, int timeout_ms) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_receive_timeout_get_impl_not_supported(TcsSocket socket, int* timeout_ms) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_nonblocking_set_impl_not_supported(TcsSocket socket, bool do_non_blocking) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_nonblocking_get_impl_not_supported(TcsSocket socket, bool* is_non_blocking) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_membership_add_to_impl_not_supported( + TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_membership_drop_from_impl_not_supported( + TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_address_socket_local_impl_not_supported(TcsSocket socket, + struct TcsAddress* local_address) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static bool tcs_internal_packet_address_is_supported_impl_not_supported(const struct TcsAddress* addr) +{ + return false; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static TcsResult (*tcs_internal_packet_socket_impl)(TcsSocket* out_socket, + TcsFamily family, + TcsSocketType type, + TcsProtocol protocol) = + tcs_internal_packet_socket_impl_not_supported; +static TcsResult (*tcs_internal_packet_close_impl)(TcsSocket* socket) = tcs_internal_packet_close_impl_not_supported; +static TcsResult (*tcs_internal_packet_bind_impl)(TcsSocket socket, const struct TcsAddress* address) = + tcs_internal_packet_bind_impl_not_supported; +static TcsResult (*tcs_internal_packet_send_impl)(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + size_t* sent_size) = tcs_internal_packet_send_impl_not_supported; +static TcsResult (*tcs_internal_packet_send_to_impl)(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + const struct TcsAddress* destination_address, + size_t* sent_size) = + tcs_internal_packet_send_to_impl_not_supported; +static TcsResult (*tcs_internal_packet_sendv_impl)(TcsSocket socket, + const struct TcsIoVec* iov, + size_t iov_length, + uint32_t flags, + size_t* sent_size) = tcs_internal_packet_sendv_impl_not_supported; +static TcsResult (*tcs_internal_packet_receive_from_impl)(TcsSocket socket, + uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + struct TcsAddress* source_address, + size_t* received_size) = + tcs_internal_packet_receive_from_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_get_impl)(TcsSocket socket, + int32_t level, + int32_t option_name, + void* out_option_value, + size_t* option_size) = + tcs_internal_packet_opt_get_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_receive_timeout_set_impl)(TcsSocket socket, int timeout_ms) = + tcs_internal_packet_opt_receive_timeout_set_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_receive_timeout_get_impl)(TcsSocket socket, int* timeout_ms) = + tcs_internal_packet_opt_receive_timeout_get_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_nonblocking_set_impl)(TcsSocket socket, bool do_non_blocking) = + tcs_internal_packet_opt_nonblocking_set_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_nonblocking_get_impl)(TcsSocket socket, bool* is_non_blocking) = + tcs_internal_packet_opt_nonblocking_get_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_membership_add_to_impl)(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) = + tcs_internal_packet_opt_membership_add_to_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_membership_drop_from_impl)(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) = + tcs_internal_packet_opt_membership_drop_from_impl_not_supported; +static TcsResult (*tcs_internal_packet_address_socket_local_impl)(TcsSocket socket, struct TcsAddress* local_address) = + tcs_internal_packet_address_socket_local_impl_not_supported; +static bool (*tcs_internal_packet_address_is_supported_impl)(const struct TcsAddress* addr) = + tcs_internal_packet_address_is_supported_impl_not_supported; + // ######## Packet Sockets: Npcap ######## // // Packet sockets are emulated on top of Npcap, whose wpcap.dll is loaded at runtime by tcs_lib_init(). Nothing from @@ -4759,17 +4943,17 @@ struct bpf_program #define TCS_PCAP_ERROR_IFACE_NOT_UP (-9) #define TCS_PCAP_ERROR_PROMISC_PERM_DENIED (-11) -typedef pcap_t*(__cdecl* TcsPcapCreate)(const char*, char*); -typedef int(__cdecl* TcsPcapSetInt)(pcap_t*, int); -typedef int(__cdecl* TcsPcapGetInt)(pcap_t*); -typedef int(__cdecl* TcsPcapSetNonblock)(pcap_t*, int, char*); -typedef HANDLE(__cdecl* TcsPcapGetEvent)(pcap_t*); -typedef int(__cdecl* TcsPcapCompile)(pcap_t*, struct bpf_program*, const char*, int, unsigned int); -typedef int(__cdecl* TcsPcapSetFilter)(pcap_t*, struct bpf_program*); -typedef void(__cdecl* TcsPcapFreeCode)(struct bpf_program*); -typedef int(__cdecl* TcsPcapNextEx)(pcap_t*, struct pcap_pkthdr**, const unsigned char**); -typedef int(__cdecl* TcsPcapSendPacket)(pcap_t*, const unsigned char*, int); -typedef void(__cdecl* TcsPcapClose)(pcap_t*); +typedef pcap_t*(__cdecl* TcsInternalPcapCreate)(const char*, char*); +typedef int(__cdecl* TcsInternalPcapSetInt)(pcap_t*, int); +typedef int(__cdecl* TcsInternalPcapGetInt)(pcap_t*); +typedef int(__cdecl* TcsInternalPcapSetNonblock)(pcap_t*, int, char*); +typedef HANDLE(__cdecl* TcsInternalPcapGetEvent)(pcap_t*); +typedef int(__cdecl* TcsInternalPcapCompile)(pcap_t*, struct bpf_program*, const char*, int, unsigned int); +typedef int(__cdecl* TcsInternalPcapSetFilter)(pcap_t*, struct bpf_program*); +typedef void(__cdecl* TcsInternalPcapFreeCode)(struct bpf_program*); +typedef int(__cdecl* TcsInternalPcapNextEx)(pcap_t*, struct pcap_pkthdr**, const unsigned char**); +typedef int(__cdecl* TcsInternalPcapSendPacket)(pcap_t*, const unsigned char*, int); +typedef void(__cdecl* TcsInternalPcapClose)(pcap_t*); struct TcsInternalNpcapSocket { @@ -4792,20 +4976,20 @@ static struct { HMODULE module; // NULL when Npcap is not installed or the library is not initialized int init_count; // tcs_lib_init() calls not yet matched by tcs_lib_cleanup() - TcsPcapCreate create; - TcsPcapSetInt set_snaplen; - TcsPcapSetInt set_promisc; - TcsPcapSetInt set_immediate_mode; - TcsPcapGetInt activate; - TcsPcapGetInt datalink; - TcsPcapSetNonblock setnonblock; - TcsPcapGetEvent getevent; - TcsPcapCompile compile; - TcsPcapSetFilter setfilter; - TcsPcapFreeCode freecode; - TcsPcapNextEx next_ex; - TcsPcapSendPacket sendpacket; - TcsPcapClose close; + TcsInternalPcapCreate create; + TcsInternalPcapSetInt set_snaplen; + TcsInternalPcapSetInt set_promisc; + TcsInternalPcapSetInt set_immediate_mode; + TcsInternalPcapGetInt activate; + TcsInternalPcapGetInt datalink; + TcsInternalPcapSetNonblock setnonblock; + TcsInternalPcapGetEvent getevent; + TcsInternalPcapCompile compile; + TcsInternalPcapSetFilter setfilter; + TcsInternalPcapFreeCode freecode; + TcsInternalPcapNextEx next_ex; + TcsInternalPcapSendPacket sendpacket; + TcsInternalPcapClose close; struct TcsInternalNpcapSocket* sockets[TCS_CFG_PACKET_SOCKETS_MAX]; // Indexed by the handle without its tag bit } g_npcap; @@ -4933,78 +5117,12 @@ static TcsResult tcs_internal_npcap_socket_open(struct TcsInternalNpcapSocket* p return TCS_SUCCESS; } -static void tcs_internal_lib_init_impl_npcap(void) -{ - if (g_npcap.init_count++ != 0) - return; - // Npcap installs to System32\Npcap. LOAD_WITH_ALTERED_SEARCH_PATH makes the loader find Packet.dll next to - // wpcap.dll without SetDllDirectory(), which would affect the whole process. The plain name is the fallback - // recommended by Npcap, for installations in WinPcap compatible mode. - const char suffix[] = "\\Npcap\\wpcap.dll"; - char path[MAX_PATH]; - UINT system_dir_size = GetSystemDirectoryA(path, MAX_PATH); - HMODULE m = NULL; - if (system_dir_size != 0 && system_dir_size + sizeof suffix <= MAX_PATH) - { - memcpy(path + system_dir_size, suffix, sizeof suffix); - m = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); - } - if (m == NULL) - m = LoadLibraryA("wpcap.dll"); - if (m == NULL) - return; - - // FARPROC is cast through void (*)(void), which gcc treats as compatible with every function pointer type - g_npcap.create = (TcsPcapCreate)(void (*)(void))GetProcAddress(m, "pcap_create"); - g_npcap.set_snaplen = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_snaplen"); - g_npcap.set_promisc = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_promisc"); - g_npcap.set_immediate_mode = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_immediate_mode"); - g_npcap.activate = (TcsPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_activate"); - g_npcap.datalink = (TcsPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_datalink"); - g_npcap.setnonblock = (TcsPcapSetNonblock)(void (*)(void))GetProcAddress(m, "pcap_setnonblock"); - g_npcap.getevent = (TcsPcapGetEvent)(void (*)(void))GetProcAddress(m, "pcap_getevent"); - g_npcap.compile = (TcsPcapCompile)(void (*)(void))GetProcAddress(m, "pcap_compile"); - g_npcap.setfilter = (TcsPcapSetFilter)(void (*)(void))GetProcAddress(m, "pcap_setfilter"); - g_npcap.freecode = (TcsPcapFreeCode)(void (*)(void))GetProcAddress(m, "pcap_freecode"); - g_npcap.next_ex = (TcsPcapNextEx)(void (*)(void))GetProcAddress(m, "pcap_next_ex"); - g_npcap.sendpacket = (TcsPcapSendPacket)(void (*)(void))GetProcAddress(m, "pcap_sendpacket"); - g_npcap.close = (TcsPcapClose)(void (*)(void))GetProcAddress(m, "pcap_close"); - - if (g_npcap.create == NULL || g_npcap.set_snaplen == NULL || g_npcap.set_promisc == NULL || - g_npcap.set_immediate_mode == NULL || g_npcap.activate == NULL || g_npcap.datalink == NULL || - g_npcap.setnonblock == NULL || g_npcap.getevent == NULL || g_npcap.compile == NULL || - g_npcap.setfilter == NULL || g_npcap.freecode == NULL || g_npcap.next_ex == NULL || - g_npcap.sendpacket == NULL || g_npcap.close == NULL) - { - FreeLibrary(m); - return; - } - g_npcap.module = m; -} - -static void tcs_internal_lib_cleanup_impl_npcap(void) -{ - if (g_npcap.init_count == 0 || --g_npcap.init_count != 0 || g_npcap.module == NULL) - return; - // Like WSACleanup(), the last cleanup closes every socket still open - for (size_t i = 0; i < TCS_CFG_PACKET_SOCKETS_MAX; ++i) - { - TcsSocket socket = TCS_PACKET_SOCKET_TAG | (TcsSocket)i; - if (g_npcap.sockets[i] != NULL) - tcs_close(&socket); - } - FreeLibrary(g_npcap.module); - memset(&g_npcap, 0, sizeof g_npcap); -} - -static TcsResult tcs_internal_socket_impl_npcap(TcsSocket* out_socket, - TcsFamily family, - TcsSocketType type, - TcsProtocol protocol) +static TcsResult tcs_internal_packet_socket_impl_npcap(TcsSocket* out_socket, + TcsFamily family, + TcsSocketType type, + TcsProtocol protocol) { (void)family; // Always TCS_FAMILY_PACKET - if (g_npcap.module == NULL) - return TCS_ERROR_NOT_SUPPORTED; if (type.native != SOCK_DGRAM && type.native != SOCK_RAW) return TCS_ERROR_NOT_SUPPORTED; size_t index = 0; @@ -5023,7 +5141,7 @@ static TcsResult tcs_internal_socket_impl_npcap(TcsSocket* out_socket, return TCS_SUCCESS; } -static TcsResult tcs_internal_close_impl_npcap(TcsSocket* socket) +static TcsResult tcs_internal_packet_close_impl_npcap(TcsSocket* socket) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(*socket); if (ps == NULL) @@ -5037,7 +5155,7 @@ static TcsResult tcs_internal_close_impl_npcap(TcsSocket* socket) return TCS_SUCCESS; } -static TcsResult tcs_internal_bind_impl_npcap(TcsSocket socket, const struct TcsAddress* address) +static TcsResult tcs_internal_packet_bind_impl_npcap(TcsSocket socket, const struct TcsAddress* address) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5052,11 +5170,11 @@ static TcsResult tcs_internal_bind_impl_npcap(TcsSocket socket, const struct Tcs return tcs_internal_npcap_socket_open(ps); } -static TcsResult tcs_internal_send_impl_npcap(TcsSocket socket, - const uint8_t* buffer, - size_t buffer_size, - uint32_t flags, - size_t* sent_size) +static TcsResult tcs_internal_packet_send_impl_npcap(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + size_t* sent_size) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5070,12 +5188,12 @@ static TcsResult tcs_internal_send_impl_npcap(TcsSocket socket, return tcs_send_to(socket, buffer, buffer_size, flags & ~TCS_MSG_SENDALL, &self, sent_size); } -static TcsResult tcs_internal_send_to_impl_npcap(TcsSocket socket, - const uint8_t* buffer, - size_t buffer_size, - uint32_t flags, - const struct TcsAddress* destination_address, - size_t* sent_size) +static TcsResult tcs_internal_packet_send_to_impl_npcap(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + const struct TcsAddress* destination_address, + size_t* sent_size) { (void)flags; // No send flag applies to a frame if (sent_size != NULL) @@ -5122,11 +5240,11 @@ static TcsResult tcs_internal_send_to_impl_npcap(TcsSocket socket, return TCS_SUCCESS; } -static TcsResult tcs_internal_sendv_impl_npcap(TcsSocket socket, - const struct TcsIoVec* iov, - size_t iov_length, - uint32_t flags, - size_t* sent_size) +static TcsResult tcs_internal_packet_sendv_impl_npcap(TcsSocket socket, + const struct TcsIoVec* iov, + size_t iov_length, + uint32_t flags, + size_t* sent_size) { // A frame is sent in one piece, so the buffers are gathered first size_t total_size = 0; @@ -5151,12 +5269,12 @@ static TcsResult tcs_internal_sendv_impl_npcap(TcsSocket socket, return send_sts; } -static TcsResult tcs_internal_receive_from_impl_npcap(TcsSocket socket, - uint8_t* buffer, - size_t buffer_size, - uint32_t flags, - struct TcsAddress* source_address, - size_t* received_size) +static TcsResult tcs_internal_packet_receive_from_impl_npcap(TcsSocket socket, + uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + struct TcsAddress* source_address, + size_t* received_size) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5227,11 +5345,11 @@ static TcsResult tcs_internal_receive_from_impl_npcap(TcsSocket socket, } } -static TcsResult tcs_internal_opt_get_impl_npcap(TcsSocket socket, - int32_t level, - int32_t option_name, - void* out_option_value, - size_t* option_size) +static TcsResult tcs_internal_packet_opt_get_impl_npcap(TcsSocket socket, + int32_t level, + int32_t option_name, + void* out_option_value, + size_t* option_size) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5243,7 +5361,7 @@ static TcsResult tcs_internal_opt_get_impl_npcap(TcsSocket socket, return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_receive_timeout_set_impl_npcap(TcsSocket socket, int timeout_ms) +static TcsResult tcs_internal_packet_opt_receive_timeout_set_impl_npcap(TcsSocket socket, int timeout_ms) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5252,7 +5370,7 @@ static TcsResult tcs_internal_opt_receive_timeout_set_impl_npcap(TcsSocket socke return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_receive_timeout_get_impl_npcap(TcsSocket socket, int* timeout_ms) +static TcsResult tcs_internal_packet_opt_receive_timeout_get_impl_npcap(TcsSocket socket, int* timeout_ms) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5261,7 +5379,7 @@ static TcsResult tcs_internal_opt_receive_timeout_get_impl_npcap(TcsSocket socke return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_nonblocking_set_impl_npcap(TcsSocket socket, bool do_non_blocking) +static TcsResult tcs_internal_packet_opt_nonblocking_set_impl_npcap(TcsSocket socket, bool do_non_blocking) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5270,7 +5388,7 @@ static TcsResult tcs_internal_opt_nonblocking_set_impl_npcap(TcsSocket socket, b return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_nonblocking_get_impl_npcap(TcsSocket socket, bool* is_non_blocking) +static TcsResult tcs_internal_packet_opt_nonblocking_get_impl_npcap(TcsSocket socket, bool* is_non_blocking) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5279,9 +5397,9 @@ static TcsResult tcs_internal_opt_nonblocking_get_impl_npcap(TcsSocket socket, b return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_membership_add_to_impl_npcap(TcsSocket socket, - const struct TcsAddress* local_address, - const struct TcsAddress* multicast_address) +static TcsResult tcs_internal_packet_opt_membership_add_to_impl_npcap(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) { (void)local_address; // A packet socket is bound to one interface struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); @@ -5305,9 +5423,9 @@ static TcsResult tcs_internal_opt_membership_add_to_impl_npcap(TcsSocket socket, return sts; } -static TcsResult tcs_internal_opt_membership_drop_from_impl_npcap(TcsSocket socket, - const struct TcsAddress* local_address, - const struct TcsAddress* multicast_address) +static TcsResult tcs_internal_packet_opt_membership_drop_from_impl_npcap(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) { (void)local_address; // A packet socket is bound to one interface struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); @@ -5339,7 +5457,7 @@ static TcsResult tcs_internal_opt_membership_drop_from_impl_npcap(TcsSocket sock return sts; } -static TcsResult tcs_internal_address_socket_local_impl_npcap(TcsSocket socket, struct TcsAddress* local_address) +static TcsResult tcs_internal_packet_address_socket_local_impl_npcap(TcsSocket socket, struct TcsAddress* local_address) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -5352,10 +5470,105 @@ static TcsResult tcs_internal_address_socket_local_impl_npcap(TcsSocket socket, return TCS_SUCCESS; } -static bool tcs_internal_address_is_supported_impl_npcap(const struct TcsAddress* addr) +static bool tcs_internal_packet_address_is_supported_impl_npcap(const struct TcsAddress* addr) { - // Npcap is loaded by tcs_lib_init() when it is installed - return addr->family.native == TCS_FAMILY_PACKET.native && g_npcap.module != NULL; + return addr->family.native == TCS_FAMILY_PACKET.native; +} + +static void tcs_internal_lib_init_impl_npcap(void) +{ + if (g_npcap.init_count++ != 0) + return; + // Npcap installs to System32\Npcap. LOAD_WITH_ALTERED_SEARCH_PATH makes the loader find Packet.dll next to + // wpcap.dll without SetDllDirectory(), which would affect the whole process. The plain name is the fallback + // recommended by Npcap, for installations in WinPcap compatible mode. + const char suffix[] = "\\Npcap\\wpcap.dll"; + char path[MAX_PATH]; + UINT system_dir_size = GetSystemDirectoryA(path, MAX_PATH); + HMODULE m = NULL; + if (system_dir_size != 0 && system_dir_size + sizeof suffix <= MAX_PATH) + { + memcpy(path + system_dir_size, suffix, sizeof suffix); + m = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + } + if (m == NULL) + m = LoadLibraryA("wpcap.dll"); + if (m == NULL) + return; + + // FARPROC is cast through void (*)(void), which gcc treats as compatible with every function pointer type + g_npcap.create = (TcsInternalPcapCreate)(void (*)(void))GetProcAddress(m, "pcap_create"); + g_npcap.set_snaplen = (TcsInternalPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_snaplen"); + g_npcap.set_promisc = (TcsInternalPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_promisc"); + g_npcap.set_immediate_mode = (TcsInternalPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_immediate_mode"); + g_npcap.activate = (TcsInternalPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_activate"); + g_npcap.datalink = (TcsInternalPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_datalink"); + g_npcap.setnonblock = (TcsInternalPcapSetNonblock)(void (*)(void))GetProcAddress(m, "pcap_setnonblock"); + g_npcap.getevent = (TcsInternalPcapGetEvent)(void (*)(void))GetProcAddress(m, "pcap_getevent"); + g_npcap.compile = (TcsInternalPcapCompile)(void (*)(void))GetProcAddress(m, "pcap_compile"); + g_npcap.setfilter = (TcsInternalPcapSetFilter)(void (*)(void))GetProcAddress(m, "pcap_setfilter"); + g_npcap.freecode = (TcsInternalPcapFreeCode)(void (*)(void))GetProcAddress(m, "pcap_freecode"); + g_npcap.next_ex = (TcsInternalPcapNextEx)(void (*)(void))GetProcAddress(m, "pcap_next_ex"); + g_npcap.sendpacket = (TcsInternalPcapSendPacket)(void (*)(void))GetProcAddress(m, "pcap_sendpacket"); + g_npcap.close = (TcsInternalPcapClose)(void (*)(void))GetProcAddress(m, "pcap_close"); + + if (g_npcap.create == NULL || g_npcap.set_snaplen == NULL || g_npcap.set_promisc == NULL || + g_npcap.set_immediate_mode == NULL || g_npcap.activate == NULL || g_npcap.datalink == NULL || + g_npcap.setnonblock == NULL || g_npcap.getevent == NULL || g_npcap.compile == NULL || + g_npcap.setfilter == NULL || g_npcap.freecode == NULL || g_npcap.next_ex == NULL || + g_npcap.sendpacket == NULL || g_npcap.close == NULL) + { + FreeLibrary(m); + return; + } + g_npcap.module = m; + tcs_internal_packet_socket_impl = tcs_internal_packet_socket_impl_npcap; + tcs_internal_packet_close_impl = tcs_internal_packet_close_impl_npcap; + tcs_internal_packet_bind_impl = tcs_internal_packet_bind_impl_npcap; + tcs_internal_packet_send_impl = tcs_internal_packet_send_impl_npcap; + tcs_internal_packet_send_to_impl = tcs_internal_packet_send_to_impl_npcap; + tcs_internal_packet_sendv_impl = tcs_internal_packet_sendv_impl_npcap; + tcs_internal_packet_receive_from_impl = tcs_internal_packet_receive_from_impl_npcap; + tcs_internal_packet_opt_get_impl = tcs_internal_packet_opt_get_impl_npcap; + tcs_internal_packet_opt_receive_timeout_set_impl = tcs_internal_packet_opt_receive_timeout_set_impl_npcap; + tcs_internal_packet_opt_receive_timeout_get_impl = tcs_internal_packet_opt_receive_timeout_get_impl_npcap; + tcs_internal_packet_opt_nonblocking_set_impl = tcs_internal_packet_opt_nonblocking_set_impl_npcap; + tcs_internal_packet_opt_nonblocking_get_impl = tcs_internal_packet_opt_nonblocking_get_impl_npcap; + tcs_internal_packet_opt_membership_add_to_impl = tcs_internal_packet_opt_membership_add_to_impl_npcap; + tcs_internal_packet_opt_membership_drop_from_impl = tcs_internal_packet_opt_membership_drop_from_impl_npcap; + tcs_internal_packet_address_socket_local_impl = tcs_internal_packet_address_socket_local_impl_npcap; + tcs_internal_packet_address_is_supported_impl = tcs_internal_packet_address_is_supported_impl_npcap; +} + +static void tcs_internal_lib_cleanup_impl_npcap(void) +{ + if (g_npcap.init_count == 0 || --g_npcap.init_count != 0 || g_npcap.module == NULL) + return; + // Like WSACleanup(), the last cleanup closes every socket still open + for (size_t i = 0; i < TCS_CFG_PACKET_SOCKETS_MAX; ++i) + { + TcsSocket socket = TCS_PACKET_SOCKET_TAG | (TcsSocket)i; + if (g_npcap.sockets[i] != NULL) + tcs_close(&socket); + } + tcs_internal_packet_socket_impl = tcs_internal_packet_socket_impl_not_supported; + tcs_internal_packet_close_impl = tcs_internal_packet_close_impl_not_supported; + tcs_internal_packet_bind_impl = tcs_internal_packet_bind_impl_not_supported; + tcs_internal_packet_send_impl = tcs_internal_packet_send_impl_not_supported; + tcs_internal_packet_send_to_impl = tcs_internal_packet_send_to_impl_not_supported; + tcs_internal_packet_sendv_impl = tcs_internal_packet_sendv_impl_not_supported; + tcs_internal_packet_receive_from_impl = tcs_internal_packet_receive_from_impl_not_supported; + tcs_internal_packet_opt_get_impl = tcs_internal_packet_opt_get_impl_not_supported; + tcs_internal_packet_opt_receive_timeout_set_impl = tcs_internal_packet_opt_receive_timeout_set_impl_not_supported; + tcs_internal_packet_opt_receive_timeout_get_impl = tcs_internal_packet_opt_receive_timeout_get_impl_not_supported; + tcs_internal_packet_opt_nonblocking_set_impl = tcs_internal_packet_opt_nonblocking_set_impl_not_supported; + tcs_internal_packet_opt_nonblocking_get_impl = tcs_internal_packet_opt_nonblocking_get_impl_not_supported; + tcs_internal_packet_opt_membership_add_to_impl = tcs_internal_packet_opt_membership_add_to_impl_not_supported; + tcs_internal_packet_opt_membership_drop_from_impl = tcs_internal_packet_opt_membership_drop_from_impl_not_supported; + tcs_internal_packet_address_socket_local_impl = tcs_internal_packet_address_socket_local_impl_not_supported; + tcs_internal_packet_address_is_supported_impl = tcs_internal_packet_address_is_supported_impl_not_supported; + FreeLibrary(g_npcap.module); + memset(&g_npcap, 0, sizeof g_npcap); } TcsResult tcs_lib_init(void) @@ -5380,7 +5593,7 @@ TcsResult tcs_socket(TcsSocket* out_socket, TcsFamily family, TcsSocketType type return TCS_ERROR_INVALID_ARGUMENT; if (family.native == TCS_FAMILY_PACKET.native) - return tcs_internal_socket_impl_npcap(out_socket, family, type, protocol); + return tcs_internal_packet_socket_impl(out_socket, family, type, protocol); TcsSocket new_socket = socket(family.native, type.native, (int)protocol); @@ -5409,7 +5622,7 @@ TcsResult tcs_close(TcsSocket* socket) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(*socket)) - return tcs_internal_close_impl_npcap(socket); + return tcs_internal_packet_close_impl(socket); int close_status = closesocket(*socket); if (close_status != SOCKET_ERROR) @@ -5433,7 +5646,7 @@ TcsResult tcs_bind(TcsSocket socket, const struct TcsAddress* address) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_bind_impl_npcap(socket, address); + return tcs_internal_packet_bind_impl(socket, address); SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -5547,7 +5760,7 @@ TcsResult tcs_send(TcsSocket socket, const uint8_t* buffer, size_t buffer_size, return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_send_impl_npcap(socket, buffer, buffer_size, flags, sent_size); + return tcs_internal_packet_send_impl(socket, buffer, buffer_size, flags, sent_size); // Send all if (flags & TCS_MSG_SENDALL) @@ -5606,7 +5819,7 @@ TcsResult tcs_send_to(TcsSocket socket, return TCS_ERROR_NOT_IMPLEMENTED; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_send_to_impl_npcap(socket, buffer, buffer_size, flags, destination_address, sent_size); + return tcs_internal_packet_send_to_impl(socket, buffer, buffer_size, flags, destination_address, sent_size); SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -5642,7 +5855,7 @@ TcsResult tcs_sendv(TcsSocket socket, const struct TcsIoVec* iov, size_t iov_len return TCS_ERROR_NOT_IMPLEMENTED; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_sendv_impl_npcap(socket, iov, iov_length, flags, sent_size); + return tcs_internal_packet_sendv_impl(socket, iov, iov_length, flags, sent_size); WSABUF stack_buffers[TCS_CFG_SENDV_STACK_MAX]; WSABUF* native_buffers = stack_buffers; @@ -5770,7 +5983,7 @@ TcsResult tcs_receive_from(TcsSocket socket, *received_size = 0; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_receive_from_impl_npcap(socket, buffer, buffer_size, flags, source_address, received_size); + return tcs_internal_packet_receive_from_impl(socket, buffer, buffer_size, flags, source_address, received_size); SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -6228,7 +6441,7 @@ TcsResult tcs_opt_get(TcsSocket socket, int32_t level, int32_t option_name, void return TCS_ERROR_NOT_IMPLEMENTED; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_get_impl_npcap(socket, level, option_name, out_option_value, option_size); + return tcs_internal_packet_opt_get_impl(socket, level, option_name, out_option_value, option_size); int optlen = (int)*option_size; int sockopt_status = getsockopt(socket, (int)level, (int)option_name, (char*)out_option_value, &optlen); @@ -6296,7 +6509,7 @@ TcsResult tcs_opt_receive_timeout_set(TcsSocket socket, int timeout_ms) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_receive_timeout_set_impl_npcap(socket, timeout_ms); + return tcs_internal_packet_opt_receive_timeout_set_impl(socket, timeout_ms); return tcs_opt_set(socket, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &timeout_ms, sizeof(timeout_ms)); } @@ -6307,7 +6520,7 @@ TcsResult tcs_opt_receive_timeout_get(TcsSocket socket, int* timeout_ms) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_receive_timeout_get_impl_npcap(socket, timeout_ms); + return tcs_internal_packet_opt_receive_timeout_get_impl(socket, timeout_ms); DWORD t = 0; size_t t_size = sizeof(t); @@ -6361,7 +6574,7 @@ TcsResult tcs_opt_nonblocking_set(TcsSocket socket, bool do_non_blocking) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_nonblocking_set_impl_npcap(socket, do_non_blocking); + return tcs_internal_packet_opt_nonblocking_set_impl(socket, do_non_blocking); u_long mode = do_non_blocking ? 1 : 0; int sts = ioctlsocket((SOCKET)socket, (long)FIONBIO, &mode); @@ -6374,7 +6587,7 @@ TcsResult tcs_opt_nonblocking_get(TcsSocket socket, bool* is_non_blocking) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_nonblocking_get_impl_npcap(socket, is_non_blocking); + return tcs_internal_packet_opt_nonblocking_get_impl(socket, is_non_blocking); // Win32 does not provide an API to query the non-blocking state of a socket return TCS_ERROR_NOT_SUPPORTED; @@ -6420,7 +6633,7 @@ TcsResult tcs_opt_membership_add_to(TcsSocket socket, return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_membership_add_to_impl_npcap(socket, local_address, multicast_address); + return tcs_internal_packet_opt_membership_add_to_impl(socket, local_address, multicast_address); if (multicast_address->family.native == TCS_FAMILY_IPV4.native) { @@ -6483,7 +6696,7 @@ TcsResult tcs_opt_membership_drop_from(TcsSocket socket, return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_membership_drop_from_impl_npcap(socket, local_address, multicast_address); + return tcs_internal_packet_opt_membership_drop_from_impl(socket, local_address, multicast_address); if (multicast_address->family.native == TCS_FAMILY_IPV4.native) { @@ -6873,7 +7086,7 @@ TcsResult tcs_address_socket_local(TcsSocket socket, struct TcsAddress* local_ad return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_address_socket_local_impl_npcap(socket, local_address); + return tcs_internal_packet_address_socket_local_impl(socket, local_address); SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -6941,7 +7154,7 @@ bool tcs_address_is_supported(const struct TcsAddress* addr) if (addr->family.native == TCS_FAMILY_IPV6.native) return true; if (addr->family.native == TCS_FAMILY_PACKET.native) - return tcs_internal_address_is_supported_impl_npcap(addr); + return tcs_internal_packet_address_is_supported_impl(addr); return false; } diff --git a/src/tinycsocket_win32.c b/src/tinycsocket_win32.c index f5341b8..a31bf2e 100644 --- a/src/tinycsocket_win32.c +++ b/src/tinycsocket_win32.c @@ -335,8 +335,8 @@ static TcsResult native2sockaddr(const PSOCKADDR in_addr, struct TcsAddress* out // // Windows has no AF_PACKET. A packet socket is an index into a table with the top bit set, which can never collide // with a kernel SOCKET handle. The public functions test that bit with tcs_internal_is_socket_packet() and hand -// packet sockets to the tcs_internal_*_impl_npcap() functions below, everything else goes to Winsock. Nothing -// outside the Npcap section touches Npcap. +// packet sockets to the tcs_internal_packet_*_impl function pointers below, everything else goes to Winsock. +// Nothing outside the Npcap section touches Npcap. #define TCS_PACKET_SOCKET_TAG ((TcsSocket)1 << (sizeof(TcsSocket) * 8 - 1)) @@ -345,6 +345,190 @@ static bool tcs_internal_is_socket_packet(TcsSocket socket) return socket != TCS_SOCKET_INVALID && (socket & TCS_PACKET_SOCKET_TAG) != 0; } +// Packet socket operations go through these function pointers, so that the implementation can be chosen at runtime. +// They start at the tcs_internal_packet_*_impl_not_supported() functions below. The Npcap section points them at +// its implementation when tcs_lib_init() has loaded Npcap, and back when tcs_lib_cleanup() has unloaded it. + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) // Unreferenced formal parameter, the stubs ignore their arguments +#endif + +static TcsResult tcs_internal_packet_socket_impl_not_supported(TcsSocket* out_socket, + TcsFamily family, + TcsSocketType type, + TcsProtocol protocol) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_close_impl_not_supported(TcsSocket* socket) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_bind_impl_not_supported(TcsSocket socket, const struct TcsAddress* address) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_send_impl_not_supported(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + size_t* sent_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_send_to_impl_not_supported(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + const struct TcsAddress* destination_address, + size_t* sent_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_sendv_impl_not_supported(TcsSocket socket, + const struct TcsIoVec* iov, + size_t iov_length, + uint32_t flags, + size_t* sent_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_receive_from_impl_not_supported(TcsSocket socket, + uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + struct TcsAddress* source_address, + size_t* received_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_get_impl_not_supported(TcsSocket socket, + int32_t level, + int32_t option_name, + void* out_option_value, + size_t* option_size) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_receive_timeout_set_impl_not_supported(TcsSocket socket, int timeout_ms) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_receive_timeout_get_impl_not_supported(TcsSocket socket, int* timeout_ms) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_nonblocking_set_impl_not_supported(TcsSocket socket, bool do_non_blocking) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_nonblocking_get_impl_not_supported(TcsSocket socket, bool* is_non_blocking) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_membership_add_to_impl_not_supported( + TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_opt_membership_drop_from_impl_not_supported( + TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static TcsResult tcs_internal_packet_address_socket_local_impl_not_supported(TcsSocket socket, + struct TcsAddress* local_address) +{ + return TCS_ERROR_NOT_SUPPORTED; +} + +static bool tcs_internal_packet_address_is_supported_impl_not_supported(const struct TcsAddress* addr) +{ + return false; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static TcsResult (*tcs_internal_packet_socket_impl)(TcsSocket* out_socket, + TcsFamily family, + TcsSocketType type, + TcsProtocol protocol) = + tcs_internal_packet_socket_impl_not_supported; +static TcsResult (*tcs_internal_packet_close_impl)(TcsSocket* socket) = tcs_internal_packet_close_impl_not_supported; +static TcsResult (*tcs_internal_packet_bind_impl)(TcsSocket socket, const struct TcsAddress* address) = + tcs_internal_packet_bind_impl_not_supported; +static TcsResult (*tcs_internal_packet_send_impl)(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + size_t* sent_size) = tcs_internal_packet_send_impl_not_supported; +static TcsResult (*tcs_internal_packet_send_to_impl)(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + const struct TcsAddress* destination_address, + size_t* sent_size) = + tcs_internal_packet_send_to_impl_not_supported; +static TcsResult (*tcs_internal_packet_sendv_impl)(TcsSocket socket, + const struct TcsIoVec* iov, + size_t iov_length, + uint32_t flags, + size_t* sent_size) = tcs_internal_packet_sendv_impl_not_supported; +static TcsResult (*tcs_internal_packet_receive_from_impl)(TcsSocket socket, + uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + struct TcsAddress* source_address, + size_t* received_size) = + tcs_internal_packet_receive_from_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_get_impl)(TcsSocket socket, + int32_t level, + int32_t option_name, + void* out_option_value, + size_t* option_size) = + tcs_internal_packet_opt_get_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_receive_timeout_set_impl)(TcsSocket socket, int timeout_ms) = + tcs_internal_packet_opt_receive_timeout_set_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_receive_timeout_get_impl)(TcsSocket socket, int* timeout_ms) = + tcs_internal_packet_opt_receive_timeout_get_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_nonblocking_set_impl)(TcsSocket socket, bool do_non_blocking) = + tcs_internal_packet_opt_nonblocking_set_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_nonblocking_get_impl)(TcsSocket socket, bool* is_non_blocking) = + tcs_internal_packet_opt_nonblocking_get_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_membership_add_to_impl)(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) = + tcs_internal_packet_opt_membership_add_to_impl_not_supported; +static TcsResult (*tcs_internal_packet_opt_membership_drop_from_impl)(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) = + tcs_internal_packet_opt_membership_drop_from_impl_not_supported; +static TcsResult (*tcs_internal_packet_address_socket_local_impl)(TcsSocket socket, struct TcsAddress* local_address) = + tcs_internal_packet_address_socket_local_impl_not_supported; +static bool (*tcs_internal_packet_address_is_supported_impl)(const struct TcsAddress* addr) = + tcs_internal_packet_address_is_supported_impl_not_supported; + // ######## Packet Sockets: Npcap ######## // // Packet sockets are emulated on top of Npcap, whose wpcap.dll is loaded at runtime by tcs_lib_init(). Nothing from @@ -381,17 +565,17 @@ struct bpf_program #define TCS_PCAP_ERROR_IFACE_NOT_UP (-9) #define TCS_PCAP_ERROR_PROMISC_PERM_DENIED (-11) -typedef pcap_t*(__cdecl* TcsPcapCreate)(const char*, char*); -typedef int(__cdecl* TcsPcapSetInt)(pcap_t*, int); -typedef int(__cdecl* TcsPcapGetInt)(pcap_t*); -typedef int(__cdecl* TcsPcapSetNonblock)(pcap_t*, int, char*); -typedef HANDLE(__cdecl* TcsPcapGetEvent)(pcap_t*); -typedef int(__cdecl* TcsPcapCompile)(pcap_t*, struct bpf_program*, const char*, int, unsigned int); -typedef int(__cdecl* TcsPcapSetFilter)(pcap_t*, struct bpf_program*); -typedef void(__cdecl* TcsPcapFreeCode)(struct bpf_program*); -typedef int(__cdecl* TcsPcapNextEx)(pcap_t*, struct pcap_pkthdr**, const unsigned char**); -typedef int(__cdecl* TcsPcapSendPacket)(pcap_t*, const unsigned char*, int); -typedef void(__cdecl* TcsPcapClose)(pcap_t*); +typedef pcap_t*(__cdecl* TcsInternalPcapCreate)(const char*, char*); +typedef int(__cdecl* TcsInternalPcapSetInt)(pcap_t*, int); +typedef int(__cdecl* TcsInternalPcapGetInt)(pcap_t*); +typedef int(__cdecl* TcsInternalPcapSetNonblock)(pcap_t*, int, char*); +typedef HANDLE(__cdecl* TcsInternalPcapGetEvent)(pcap_t*); +typedef int(__cdecl* TcsInternalPcapCompile)(pcap_t*, struct bpf_program*, const char*, int, unsigned int); +typedef int(__cdecl* TcsInternalPcapSetFilter)(pcap_t*, struct bpf_program*); +typedef void(__cdecl* TcsInternalPcapFreeCode)(struct bpf_program*); +typedef int(__cdecl* TcsInternalPcapNextEx)(pcap_t*, struct pcap_pkthdr**, const unsigned char**); +typedef int(__cdecl* TcsInternalPcapSendPacket)(pcap_t*, const unsigned char*, int); +typedef void(__cdecl* TcsInternalPcapClose)(pcap_t*); struct TcsInternalNpcapSocket { @@ -414,20 +598,20 @@ static struct { HMODULE module; // NULL when Npcap is not installed or the library is not initialized int init_count; // tcs_lib_init() calls not yet matched by tcs_lib_cleanup() - TcsPcapCreate create; - TcsPcapSetInt set_snaplen; - TcsPcapSetInt set_promisc; - TcsPcapSetInt set_immediate_mode; - TcsPcapGetInt activate; - TcsPcapGetInt datalink; - TcsPcapSetNonblock setnonblock; - TcsPcapGetEvent getevent; - TcsPcapCompile compile; - TcsPcapSetFilter setfilter; - TcsPcapFreeCode freecode; - TcsPcapNextEx next_ex; - TcsPcapSendPacket sendpacket; - TcsPcapClose close; + TcsInternalPcapCreate create; + TcsInternalPcapSetInt set_snaplen; + TcsInternalPcapSetInt set_promisc; + TcsInternalPcapSetInt set_immediate_mode; + TcsInternalPcapGetInt activate; + TcsInternalPcapGetInt datalink; + TcsInternalPcapSetNonblock setnonblock; + TcsInternalPcapGetEvent getevent; + TcsInternalPcapCompile compile; + TcsInternalPcapSetFilter setfilter; + TcsInternalPcapFreeCode freecode; + TcsInternalPcapNextEx next_ex; + TcsInternalPcapSendPacket sendpacket; + TcsInternalPcapClose close; struct TcsInternalNpcapSocket* sockets[TCS_CFG_PACKET_SOCKETS_MAX]; // Indexed by the handle without its tag bit } g_npcap; @@ -555,78 +739,12 @@ static TcsResult tcs_internal_npcap_socket_open(struct TcsInternalNpcapSocket* p return TCS_SUCCESS; } -static void tcs_internal_lib_init_impl_npcap(void) -{ - if (g_npcap.init_count++ != 0) - return; - // Npcap installs to System32\Npcap. LOAD_WITH_ALTERED_SEARCH_PATH makes the loader find Packet.dll next to - // wpcap.dll without SetDllDirectory(), which would affect the whole process. The plain name is the fallback - // recommended by Npcap, for installations in WinPcap compatible mode. - const char suffix[] = "\\Npcap\\wpcap.dll"; - char path[MAX_PATH]; - UINT system_dir_size = GetSystemDirectoryA(path, MAX_PATH); - HMODULE m = NULL; - if (system_dir_size != 0 && system_dir_size + sizeof suffix <= MAX_PATH) - { - memcpy(path + system_dir_size, suffix, sizeof suffix); - m = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); - } - if (m == NULL) - m = LoadLibraryA("wpcap.dll"); - if (m == NULL) - return; - - // FARPROC is cast through void (*)(void), which gcc treats as compatible with every function pointer type - g_npcap.create = (TcsPcapCreate)(void (*)(void))GetProcAddress(m, "pcap_create"); - g_npcap.set_snaplen = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_snaplen"); - g_npcap.set_promisc = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_promisc"); - g_npcap.set_immediate_mode = (TcsPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_immediate_mode"); - g_npcap.activate = (TcsPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_activate"); - g_npcap.datalink = (TcsPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_datalink"); - g_npcap.setnonblock = (TcsPcapSetNonblock)(void (*)(void))GetProcAddress(m, "pcap_setnonblock"); - g_npcap.getevent = (TcsPcapGetEvent)(void (*)(void))GetProcAddress(m, "pcap_getevent"); - g_npcap.compile = (TcsPcapCompile)(void (*)(void))GetProcAddress(m, "pcap_compile"); - g_npcap.setfilter = (TcsPcapSetFilter)(void (*)(void))GetProcAddress(m, "pcap_setfilter"); - g_npcap.freecode = (TcsPcapFreeCode)(void (*)(void))GetProcAddress(m, "pcap_freecode"); - g_npcap.next_ex = (TcsPcapNextEx)(void (*)(void))GetProcAddress(m, "pcap_next_ex"); - g_npcap.sendpacket = (TcsPcapSendPacket)(void (*)(void))GetProcAddress(m, "pcap_sendpacket"); - g_npcap.close = (TcsPcapClose)(void (*)(void))GetProcAddress(m, "pcap_close"); - - if (g_npcap.create == NULL || g_npcap.set_snaplen == NULL || g_npcap.set_promisc == NULL || - g_npcap.set_immediate_mode == NULL || g_npcap.activate == NULL || g_npcap.datalink == NULL || - g_npcap.setnonblock == NULL || g_npcap.getevent == NULL || g_npcap.compile == NULL || - g_npcap.setfilter == NULL || g_npcap.freecode == NULL || g_npcap.next_ex == NULL || - g_npcap.sendpacket == NULL || g_npcap.close == NULL) - { - FreeLibrary(m); - return; - } - g_npcap.module = m; -} - -static void tcs_internal_lib_cleanup_impl_npcap(void) -{ - if (g_npcap.init_count == 0 || --g_npcap.init_count != 0 || g_npcap.module == NULL) - return; - // Like WSACleanup(), the last cleanup closes every socket still open - for (size_t i = 0; i < TCS_CFG_PACKET_SOCKETS_MAX; ++i) - { - TcsSocket socket = TCS_PACKET_SOCKET_TAG | (TcsSocket)i; - if (g_npcap.sockets[i] != NULL) - tcs_close(&socket); - } - FreeLibrary(g_npcap.module); - memset(&g_npcap, 0, sizeof g_npcap); -} - -static TcsResult tcs_internal_socket_impl_npcap(TcsSocket* out_socket, - TcsFamily family, - TcsSocketType type, - TcsProtocol protocol) +static TcsResult tcs_internal_packet_socket_impl_npcap(TcsSocket* out_socket, + TcsFamily family, + TcsSocketType type, + TcsProtocol protocol) { (void)family; // Always TCS_FAMILY_PACKET - if (g_npcap.module == NULL) - return TCS_ERROR_NOT_SUPPORTED; if (type.native != SOCK_DGRAM && type.native != SOCK_RAW) return TCS_ERROR_NOT_SUPPORTED; size_t index = 0; @@ -645,7 +763,7 @@ static TcsResult tcs_internal_socket_impl_npcap(TcsSocket* out_socket, return TCS_SUCCESS; } -static TcsResult tcs_internal_close_impl_npcap(TcsSocket* socket) +static TcsResult tcs_internal_packet_close_impl_npcap(TcsSocket* socket) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(*socket); if (ps == NULL) @@ -659,7 +777,7 @@ static TcsResult tcs_internal_close_impl_npcap(TcsSocket* socket) return TCS_SUCCESS; } -static TcsResult tcs_internal_bind_impl_npcap(TcsSocket socket, const struct TcsAddress* address) +static TcsResult tcs_internal_packet_bind_impl_npcap(TcsSocket socket, const struct TcsAddress* address) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -674,11 +792,11 @@ static TcsResult tcs_internal_bind_impl_npcap(TcsSocket socket, const struct Tcs return tcs_internal_npcap_socket_open(ps); } -static TcsResult tcs_internal_send_impl_npcap(TcsSocket socket, - const uint8_t* buffer, - size_t buffer_size, - uint32_t flags, - size_t* sent_size) +static TcsResult tcs_internal_packet_send_impl_npcap(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + size_t* sent_size) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -692,12 +810,12 @@ static TcsResult tcs_internal_send_impl_npcap(TcsSocket socket, return tcs_send_to(socket, buffer, buffer_size, flags & ~TCS_MSG_SENDALL, &self, sent_size); } -static TcsResult tcs_internal_send_to_impl_npcap(TcsSocket socket, - const uint8_t* buffer, - size_t buffer_size, - uint32_t flags, - const struct TcsAddress* destination_address, - size_t* sent_size) +static TcsResult tcs_internal_packet_send_to_impl_npcap(TcsSocket socket, + const uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + const struct TcsAddress* destination_address, + size_t* sent_size) { (void)flags; // No send flag applies to a frame if (sent_size != NULL) @@ -744,11 +862,11 @@ static TcsResult tcs_internal_send_to_impl_npcap(TcsSocket socket, return TCS_SUCCESS; } -static TcsResult tcs_internal_sendv_impl_npcap(TcsSocket socket, - const struct TcsIoVec* iov, - size_t iov_length, - uint32_t flags, - size_t* sent_size) +static TcsResult tcs_internal_packet_sendv_impl_npcap(TcsSocket socket, + const struct TcsIoVec* iov, + size_t iov_length, + uint32_t flags, + size_t* sent_size) { // A frame is sent in one piece, so the buffers are gathered first size_t total_size = 0; @@ -773,12 +891,12 @@ static TcsResult tcs_internal_sendv_impl_npcap(TcsSocket socket, return send_sts; } -static TcsResult tcs_internal_receive_from_impl_npcap(TcsSocket socket, - uint8_t* buffer, - size_t buffer_size, - uint32_t flags, - struct TcsAddress* source_address, - size_t* received_size) +static TcsResult tcs_internal_packet_receive_from_impl_npcap(TcsSocket socket, + uint8_t* buffer, + size_t buffer_size, + uint32_t flags, + struct TcsAddress* source_address, + size_t* received_size) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -849,11 +967,11 @@ static TcsResult tcs_internal_receive_from_impl_npcap(TcsSocket socket, } } -static TcsResult tcs_internal_opt_get_impl_npcap(TcsSocket socket, - int32_t level, - int32_t option_name, - void* out_option_value, - size_t* option_size) +static TcsResult tcs_internal_packet_opt_get_impl_npcap(TcsSocket socket, + int32_t level, + int32_t option_name, + void* out_option_value, + size_t* option_size) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -865,7 +983,7 @@ static TcsResult tcs_internal_opt_get_impl_npcap(TcsSocket socket, return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_receive_timeout_set_impl_npcap(TcsSocket socket, int timeout_ms) +static TcsResult tcs_internal_packet_opt_receive_timeout_set_impl_npcap(TcsSocket socket, int timeout_ms) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -874,7 +992,7 @@ static TcsResult tcs_internal_opt_receive_timeout_set_impl_npcap(TcsSocket socke return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_receive_timeout_get_impl_npcap(TcsSocket socket, int* timeout_ms) +static TcsResult tcs_internal_packet_opt_receive_timeout_get_impl_npcap(TcsSocket socket, int* timeout_ms) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -883,7 +1001,7 @@ static TcsResult tcs_internal_opt_receive_timeout_get_impl_npcap(TcsSocket socke return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_nonblocking_set_impl_npcap(TcsSocket socket, bool do_non_blocking) +static TcsResult tcs_internal_packet_opt_nonblocking_set_impl_npcap(TcsSocket socket, bool do_non_blocking) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -892,7 +1010,7 @@ static TcsResult tcs_internal_opt_nonblocking_set_impl_npcap(TcsSocket socket, b return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_nonblocking_get_impl_npcap(TcsSocket socket, bool* is_non_blocking) +static TcsResult tcs_internal_packet_opt_nonblocking_get_impl_npcap(TcsSocket socket, bool* is_non_blocking) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -901,9 +1019,9 @@ static TcsResult tcs_internal_opt_nonblocking_get_impl_npcap(TcsSocket socket, b return TCS_SUCCESS; } -static TcsResult tcs_internal_opt_membership_add_to_impl_npcap(TcsSocket socket, - const struct TcsAddress* local_address, - const struct TcsAddress* multicast_address) +static TcsResult tcs_internal_packet_opt_membership_add_to_impl_npcap(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) { (void)local_address; // A packet socket is bound to one interface struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); @@ -927,9 +1045,9 @@ static TcsResult tcs_internal_opt_membership_add_to_impl_npcap(TcsSocket socket, return sts; } -static TcsResult tcs_internal_opt_membership_drop_from_impl_npcap(TcsSocket socket, - const struct TcsAddress* local_address, - const struct TcsAddress* multicast_address) +static TcsResult tcs_internal_packet_opt_membership_drop_from_impl_npcap(TcsSocket socket, + const struct TcsAddress* local_address, + const struct TcsAddress* multicast_address) { (void)local_address; // A packet socket is bound to one interface struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); @@ -961,7 +1079,7 @@ static TcsResult tcs_internal_opt_membership_drop_from_impl_npcap(TcsSocket sock return sts; } -static TcsResult tcs_internal_address_socket_local_impl_npcap(TcsSocket socket, struct TcsAddress* local_address) +static TcsResult tcs_internal_packet_address_socket_local_impl_npcap(TcsSocket socket, struct TcsAddress* local_address) { struct TcsInternalNpcapSocket* ps = tcs_internal_npcap_socket_get(socket); if (ps == NULL) @@ -974,10 +1092,105 @@ static TcsResult tcs_internal_address_socket_local_impl_npcap(TcsSocket socket, return TCS_SUCCESS; } -static bool tcs_internal_address_is_supported_impl_npcap(const struct TcsAddress* addr) +static bool tcs_internal_packet_address_is_supported_impl_npcap(const struct TcsAddress* addr) +{ + return addr->family.native == TCS_FAMILY_PACKET.native; +} + +static void tcs_internal_lib_init_impl_npcap(void) +{ + if (g_npcap.init_count++ != 0) + return; + // Npcap installs to System32\Npcap. LOAD_WITH_ALTERED_SEARCH_PATH makes the loader find Packet.dll next to + // wpcap.dll without SetDllDirectory(), which would affect the whole process. The plain name is the fallback + // recommended by Npcap, for installations in WinPcap compatible mode. + const char suffix[] = "\\Npcap\\wpcap.dll"; + char path[MAX_PATH]; + UINT system_dir_size = GetSystemDirectoryA(path, MAX_PATH); + HMODULE m = NULL; + if (system_dir_size != 0 && system_dir_size + sizeof suffix <= MAX_PATH) + { + memcpy(path + system_dir_size, suffix, sizeof suffix); + m = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + } + if (m == NULL) + m = LoadLibraryA("wpcap.dll"); + if (m == NULL) + return; + + // FARPROC is cast through void (*)(void), which gcc treats as compatible with every function pointer type + g_npcap.create = (TcsInternalPcapCreate)(void (*)(void))GetProcAddress(m, "pcap_create"); + g_npcap.set_snaplen = (TcsInternalPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_snaplen"); + g_npcap.set_promisc = (TcsInternalPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_promisc"); + g_npcap.set_immediate_mode = (TcsInternalPcapSetInt)(void (*)(void))GetProcAddress(m, "pcap_set_immediate_mode"); + g_npcap.activate = (TcsInternalPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_activate"); + g_npcap.datalink = (TcsInternalPcapGetInt)(void (*)(void))GetProcAddress(m, "pcap_datalink"); + g_npcap.setnonblock = (TcsInternalPcapSetNonblock)(void (*)(void))GetProcAddress(m, "pcap_setnonblock"); + g_npcap.getevent = (TcsInternalPcapGetEvent)(void (*)(void))GetProcAddress(m, "pcap_getevent"); + g_npcap.compile = (TcsInternalPcapCompile)(void (*)(void))GetProcAddress(m, "pcap_compile"); + g_npcap.setfilter = (TcsInternalPcapSetFilter)(void (*)(void))GetProcAddress(m, "pcap_setfilter"); + g_npcap.freecode = (TcsInternalPcapFreeCode)(void (*)(void))GetProcAddress(m, "pcap_freecode"); + g_npcap.next_ex = (TcsInternalPcapNextEx)(void (*)(void))GetProcAddress(m, "pcap_next_ex"); + g_npcap.sendpacket = (TcsInternalPcapSendPacket)(void (*)(void))GetProcAddress(m, "pcap_sendpacket"); + g_npcap.close = (TcsInternalPcapClose)(void (*)(void))GetProcAddress(m, "pcap_close"); + + if (g_npcap.create == NULL || g_npcap.set_snaplen == NULL || g_npcap.set_promisc == NULL || + g_npcap.set_immediate_mode == NULL || g_npcap.activate == NULL || g_npcap.datalink == NULL || + g_npcap.setnonblock == NULL || g_npcap.getevent == NULL || g_npcap.compile == NULL || + g_npcap.setfilter == NULL || g_npcap.freecode == NULL || g_npcap.next_ex == NULL || + g_npcap.sendpacket == NULL || g_npcap.close == NULL) + { + FreeLibrary(m); + return; + } + g_npcap.module = m; + tcs_internal_packet_socket_impl = tcs_internal_packet_socket_impl_npcap; + tcs_internal_packet_close_impl = tcs_internal_packet_close_impl_npcap; + tcs_internal_packet_bind_impl = tcs_internal_packet_bind_impl_npcap; + tcs_internal_packet_send_impl = tcs_internal_packet_send_impl_npcap; + tcs_internal_packet_send_to_impl = tcs_internal_packet_send_to_impl_npcap; + tcs_internal_packet_sendv_impl = tcs_internal_packet_sendv_impl_npcap; + tcs_internal_packet_receive_from_impl = tcs_internal_packet_receive_from_impl_npcap; + tcs_internal_packet_opt_get_impl = tcs_internal_packet_opt_get_impl_npcap; + tcs_internal_packet_opt_receive_timeout_set_impl = tcs_internal_packet_opt_receive_timeout_set_impl_npcap; + tcs_internal_packet_opt_receive_timeout_get_impl = tcs_internal_packet_opt_receive_timeout_get_impl_npcap; + tcs_internal_packet_opt_nonblocking_set_impl = tcs_internal_packet_opt_nonblocking_set_impl_npcap; + tcs_internal_packet_opt_nonblocking_get_impl = tcs_internal_packet_opt_nonblocking_get_impl_npcap; + tcs_internal_packet_opt_membership_add_to_impl = tcs_internal_packet_opt_membership_add_to_impl_npcap; + tcs_internal_packet_opt_membership_drop_from_impl = tcs_internal_packet_opt_membership_drop_from_impl_npcap; + tcs_internal_packet_address_socket_local_impl = tcs_internal_packet_address_socket_local_impl_npcap; + tcs_internal_packet_address_is_supported_impl = tcs_internal_packet_address_is_supported_impl_npcap; +} + +static void tcs_internal_lib_cleanup_impl_npcap(void) { - // Npcap is loaded by tcs_lib_init() when it is installed - return addr->family.native == TCS_FAMILY_PACKET.native && g_npcap.module != NULL; + if (g_npcap.init_count == 0 || --g_npcap.init_count != 0 || g_npcap.module == NULL) + return; + // Like WSACleanup(), the last cleanup closes every socket still open + for (size_t i = 0; i < TCS_CFG_PACKET_SOCKETS_MAX; ++i) + { + TcsSocket socket = TCS_PACKET_SOCKET_TAG | (TcsSocket)i; + if (g_npcap.sockets[i] != NULL) + tcs_close(&socket); + } + tcs_internal_packet_socket_impl = tcs_internal_packet_socket_impl_not_supported; + tcs_internal_packet_close_impl = tcs_internal_packet_close_impl_not_supported; + tcs_internal_packet_bind_impl = tcs_internal_packet_bind_impl_not_supported; + tcs_internal_packet_send_impl = tcs_internal_packet_send_impl_not_supported; + tcs_internal_packet_send_to_impl = tcs_internal_packet_send_to_impl_not_supported; + tcs_internal_packet_sendv_impl = tcs_internal_packet_sendv_impl_not_supported; + tcs_internal_packet_receive_from_impl = tcs_internal_packet_receive_from_impl_not_supported; + tcs_internal_packet_opt_get_impl = tcs_internal_packet_opt_get_impl_not_supported; + tcs_internal_packet_opt_receive_timeout_set_impl = tcs_internal_packet_opt_receive_timeout_set_impl_not_supported; + tcs_internal_packet_opt_receive_timeout_get_impl = tcs_internal_packet_opt_receive_timeout_get_impl_not_supported; + tcs_internal_packet_opt_nonblocking_set_impl = tcs_internal_packet_opt_nonblocking_set_impl_not_supported; + tcs_internal_packet_opt_nonblocking_get_impl = tcs_internal_packet_opt_nonblocking_get_impl_not_supported; + tcs_internal_packet_opt_membership_add_to_impl = tcs_internal_packet_opt_membership_add_to_impl_not_supported; + tcs_internal_packet_opt_membership_drop_from_impl = tcs_internal_packet_opt_membership_drop_from_impl_not_supported; + tcs_internal_packet_address_socket_local_impl = tcs_internal_packet_address_socket_local_impl_not_supported; + tcs_internal_packet_address_is_supported_impl = tcs_internal_packet_address_is_supported_impl_not_supported; + FreeLibrary(g_npcap.module); + memset(&g_npcap, 0, sizeof g_npcap); } TcsResult tcs_lib_init(void) @@ -1002,7 +1215,7 @@ TcsResult tcs_socket(TcsSocket* out_socket, TcsFamily family, TcsSocketType type return TCS_ERROR_INVALID_ARGUMENT; if (family.native == TCS_FAMILY_PACKET.native) - return tcs_internal_socket_impl_npcap(out_socket, family, type, protocol); + return tcs_internal_packet_socket_impl(out_socket, family, type, protocol); TcsSocket new_socket = socket(family.native, type.native, (int)protocol); @@ -1031,7 +1244,7 @@ TcsResult tcs_close(TcsSocket* socket) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(*socket)) - return tcs_internal_close_impl_npcap(socket); + return tcs_internal_packet_close_impl(socket); int close_status = closesocket(*socket); if (close_status != SOCKET_ERROR) @@ -1055,7 +1268,7 @@ TcsResult tcs_bind(TcsSocket socket, const struct TcsAddress* address) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_bind_impl_npcap(socket, address); + return tcs_internal_packet_bind_impl(socket, address); SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -1169,7 +1382,7 @@ TcsResult tcs_send(TcsSocket socket, const uint8_t* buffer, size_t buffer_size, return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_send_impl_npcap(socket, buffer, buffer_size, flags, sent_size); + return tcs_internal_packet_send_impl(socket, buffer, buffer_size, flags, sent_size); // Send all if (flags & TCS_MSG_SENDALL) @@ -1228,7 +1441,7 @@ TcsResult tcs_send_to(TcsSocket socket, return TCS_ERROR_NOT_IMPLEMENTED; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_send_to_impl_npcap(socket, buffer, buffer_size, flags, destination_address, sent_size); + return tcs_internal_packet_send_to_impl(socket, buffer, buffer_size, flags, destination_address, sent_size); SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -1264,7 +1477,7 @@ TcsResult tcs_sendv(TcsSocket socket, const struct TcsIoVec* iov, size_t iov_len return TCS_ERROR_NOT_IMPLEMENTED; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_sendv_impl_npcap(socket, iov, iov_length, flags, sent_size); + return tcs_internal_packet_sendv_impl(socket, iov, iov_length, flags, sent_size); WSABUF stack_buffers[TCS_CFG_SENDV_STACK_MAX]; WSABUF* native_buffers = stack_buffers; @@ -1392,7 +1605,7 @@ TcsResult tcs_receive_from(TcsSocket socket, *received_size = 0; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_receive_from_impl_npcap(socket, buffer, buffer_size, flags, source_address, received_size); + return tcs_internal_packet_receive_from_impl(socket, buffer, buffer_size, flags, source_address, received_size); SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -1850,7 +2063,7 @@ TcsResult tcs_opt_get(TcsSocket socket, int32_t level, int32_t option_name, void return TCS_ERROR_NOT_IMPLEMENTED; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_get_impl_npcap(socket, level, option_name, out_option_value, option_size); + return tcs_internal_packet_opt_get_impl(socket, level, option_name, out_option_value, option_size); int optlen = (int)*option_size; int sockopt_status = getsockopt(socket, (int)level, (int)option_name, (char*)out_option_value, &optlen); @@ -1918,7 +2131,7 @@ TcsResult tcs_opt_receive_timeout_set(TcsSocket socket, int timeout_ms) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_receive_timeout_set_impl_npcap(socket, timeout_ms); + return tcs_internal_packet_opt_receive_timeout_set_impl(socket, timeout_ms); return tcs_opt_set(socket, TCS_SOL_SOCKET, TCS_SO_RCVTIMEO, &timeout_ms, sizeof(timeout_ms)); } @@ -1929,7 +2142,7 @@ TcsResult tcs_opt_receive_timeout_get(TcsSocket socket, int* timeout_ms) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_receive_timeout_get_impl_npcap(socket, timeout_ms); + return tcs_internal_packet_opt_receive_timeout_get_impl(socket, timeout_ms); DWORD t = 0; size_t t_size = sizeof(t); @@ -1983,7 +2196,7 @@ TcsResult tcs_opt_nonblocking_set(TcsSocket socket, bool do_non_blocking) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_nonblocking_set_impl_npcap(socket, do_non_blocking); + return tcs_internal_packet_opt_nonblocking_set_impl(socket, do_non_blocking); u_long mode = do_non_blocking ? 1 : 0; int sts = ioctlsocket((SOCKET)socket, (long)FIONBIO, &mode); @@ -1996,7 +2209,7 @@ TcsResult tcs_opt_nonblocking_get(TcsSocket socket, bool* is_non_blocking) return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_nonblocking_get_impl_npcap(socket, is_non_blocking); + return tcs_internal_packet_opt_nonblocking_get_impl(socket, is_non_blocking); // Win32 does not provide an API to query the non-blocking state of a socket return TCS_ERROR_NOT_SUPPORTED; @@ -2042,7 +2255,7 @@ TcsResult tcs_opt_membership_add_to(TcsSocket socket, return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_membership_add_to_impl_npcap(socket, local_address, multicast_address); + return tcs_internal_packet_opt_membership_add_to_impl(socket, local_address, multicast_address); if (multicast_address->family.native == TCS_FAMILY_IPV4.native) { @@ -2105,7 +2318,7 @@ TcsResult tcs_opt_membership_drop_from(TcsSocket socket, return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_opt_membership_drop_from_impl_npcap(socket, local_address, multicast_address); + return tcs_internal_packet_opt_membership_drop_from_impl(socket, local_address, multicast_address); if (multicast_address->family.native == TCS_FAMILY_IPV4.native) { @@ -2495,7 +2708,7 @@ TcsResult tcs_address_socket_local(TcsSocket socket, struct TcsAddress* local_ad return TCS_ERROR_INVALID_ARGUMENT; if (tcs_internal_is_socket_packet(socket)) - return tcs_internal_address_socket_local_impl_npcap(socket, local_address); + return tcs_internal_packet_address_socket_local_impl(socket, local_address); SOCKADDR_STORAGE native_sockaddr; memset(&native_sockaddr, 0, sizeof native_sockaddr); @@ -2563,7 +2776,7 @@ bool tcs_address_is_supported(const struct TcsAddress* addr) if (addr->family.native == TCS_FAMILY_IPV6.native) return true; if (addr->family.native == TCS_FAMILY_PACKET.native) - return tcs_internal_address_is_supported_impl_npcap(addr); + return tcs_internal_packet_address_is_supported_impl(addr); return false; } From ee04c122f4b4b73b4e18f4e8c7624e501152eb45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindel=C3=B6w?= Date: Wed, 28 Oct 2026 14:26:09 +0100 Subject: [PATCH 4/4] Do not support wine for packet --- include/tinycsocket.h | 7 +++++++ src/tinycsocket_win32.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/include/tinycsocket.h b/include/tinycsocket.h index f0ad72c..e330414 100644 --- a/include/tinycsocket.h +++ b/include/tinycsocket.h @@ -4919,6 +4919,9 @@ static bool (*tcs_internal_packet_address_is_supported_impl)(const struct TcsAdd // pcap_setmintocopy(p, 0) would admit them. Behaviour that depends on the version: 802.1Q tags are restored on // receive from Npcap 1.81 (fixed for TCI 0 in 1.82 and for stacked tags in 1.88). Before that, NDIS has already // stripped the tag, so tagged frames arrive untagged. The filter in tcs_internal_npcap_socket_filter() matches both cases. +// +// Not supported: Wine. Its builtin wpcap.dll forwards to the host libpcap, but pcap_getevent() is a stub that raises +// an exception, so tcs_internal_lib_init_impl_npcap() leaves packet sockets unsupported when it detects Wine. typedef struct pcap pcap_t; @@ -5479,6 +5482,10 @@ static void tcs_internal_lib_init_impl_npcap(void) { if (g_npcap.init_count++ != 0) return; + // Wine ships its own wpcap.dll where pcap_getevent() is a stub that raises an exception, so packet sockets stay + // unsupported there. Checking ntdll for wine_get_version() is the detection Wine's developer FAQ recommends. + if (GetProcAddress(GetModuleHandleA("ntdll.dll"), "wine_get_version") != NULL) + return; // Npcap installs to System32\Npcap. LOAD_WITH_ALTERED_SEARCH_PATH makes the loader find Packet.dll next to // wpcap.dll without SetDllDirectory(), which would affect the whole process. The plain name is the fallback // recommended by Npcap, for installations in WinPcap compatible mode. diff --git a/src/tinycsocket_win32.c b/src/tinycsocket_win32.c index a31bf2e..1c88520 100644 --- a/src/tinycsocket_win32.c +++ b/src/tinycsocket_win32.c @@ -541,6 +541,9 @@ static bool (*tcs_internal_packet_address_is_supported_impl)(const struct TcsAdd // pcap_setmintocopy(p, 0) would admit them. Behaviour that depends on the version: 802.1Q tags are restored on // receive from Npcap 1.81 (fixed for TCI 0 in 1.82 and for stacked tags in 1.88). Before that, NDIS has already // stripped the tag, so tagged frames arrive untagged. The filter in tcs_internal_npcap_socket_filter() matches both cases. +// +// Not supported: Wine. Its builtin wpcap.dll forwards to the host libpcap, but pcap_getevent() is a stub that raises +// an exception, so tcs_internal_lib_init_impl_npcap() leaves packet sockets unsupported when it detects Wine. typedef struct pcap pcap_t; @@ -1101,6 +1104,10 @@ static void tcs_internal_lib_init_impl_npcap(void) { if (g_npcap.init_count++ != 0) return; + // Wine ships its own wpcap.dll where pcap_getevent() is a stub that raises an exception, so packet sockets stay + // unsupported there. Checking ntdll for wine_get_version() is the detection Wine's developer FAQ recommends. + if (GetProcAddress(GetModuleHandleA("ntdll.dll"), "wine_get_version") != NULL) + return; // Npcap installs to System32\Npcap. LOAD_WITH_ALTERED_SEARCH_PATH makes the loader find Packet.dll next to // wpcap.dll without SetDllDirectory(), which would affect the whole process. The plain name is the fallback // recommended by Npcap, for installations in WinPcap compatible mode.