diff options
Diffstat (limited to 'src/VBox/Devices/Network/testcase')
-rw-r--r-- | src/VBox/Devices/Network/testcase/CppUnitEmulation.h | 71 | ||||
-rw-r--r-- | src/VBox/Devices/Network/testcase/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Devices/Network/testcase/tstDevEEPROM.cpp | 550 | ||||
-rw-r--r-- | src/VBox/Devices/Network/testcase/tstDevPhy.cpp | 192 | ||||
-rw-r--r-- | src/VBox/Devices/Network/testcase/tstIntNet-1.cpp | 1030 | ||||
-rw-r--r-- | src/VBox/Devices/Network/testcase/tstIntNetR0.cpp | 867 |
6 files changed, 2710 insertions, 0 deletions
diff --git a/src/VBox/Devices/Network/testcase/CppUnitEmulation.h b/src/VBox/Devices/Network/testcase/CppUnitEmulation.h new file mode 100644 index 00000000..18131c7f --- /dev/null +++ b/src/VBox/Devices/Network/testcase/CppUnitEmulation.h @@ -0,0 +1,71 @@ +/* $Id: CppUnitEmulation.h $ */ +/** @file + * Simple cppunit emulation + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_Network_testcase_CppUnitEmulation_h +#define VBOX_INCLUDED_SRC_Network_testcase_CppUnitEmulation_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/test.h> + +#define CPPUNIT_TEST_SUITE(a_Name) \ +public: \ + RTEXITCODE run(void) \ + { \ + RTTEST hTest = NIL_RTTEST; \ + RTEXITCODE rcExit = RTTestInitAndCreate(#a_Name, &hTest); \ + if (rcExit == RTEXITCODE_SUCCESS) \ + { \ + RTTestBanner(hTest) + +#define CPPUNIT_TEST(a_MethodName) \ + RTTestISub(#a_MethodName); \ + setUp(); \ + a_MethodName(); \ + tearDown() + +#define CPPUNIT_TEST_SUITE_END() \ + rcExit = RTTestSummaryAndDestroy(hTest); \ + } \ + return rcExit; \ + } \ + typedef int dummy_end + +#define CPPUNIT_FAIL(a_Msg) RTTestIFailed(a_Msg) +#if __cplusplus+0 < 201100 +# define CPPUNIT_ASSERT_EQUAL(a_Value1, a_Value2) RTTESTI_CHECK((a_Value1) == (a_Value2)) +#else +# define CPPUNIT_ASSERT_EQUAL(a_Value1, a_Value2) do { \ + auto const Val1 = (a_Value1); \ + auto const Val2 = (a_Value2); \ + if (Val1 != Val2) \ + RTTestIFailed("%s (%#llx) != %s (%#llx)", #a_Value1, (uint64_t)Val1, #a_Value2, (uint64_t)Val2); \ + } while (0) +#endif + +#endif /* !VBOX_INCLUDED_SRC_Network_testcase_CppUnitEmulation_h */ diff --git a/src/VBox/Devices/Network/testcase/Makefile.kup b/src/VBox/Devices/Network/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/Network/testcase/Makefile.kup diff --git a/src/VBox/Devices/Network/testcase/tstDevEEPROM.cpp b/src/VBox/Devices/Network/testcase/tstDevEEPROM.cpp new file mode 100644 index 00000000..148bc7aa --- /dev/null +++ b/src/VBox/Devices/Network/testcase/tstDevEEPROM.cpp @@ -0,0 +1,550 @@ +/* $Id: tstDevEEPROM.cpp $ */ +/** @file + * EEPROM 93C46 unit tests. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef USE_CPPUNIT +# include <cppunit/ui/text/TestRunner.h> +# include <cppunit/extensions/HelperMacros.h> +#else +# include "CppUnitEmulation.h" +#endif +#include <VBox/vmm/pdmdev.h> +#include "../DevEEPROM.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const uint16_t g_abInitialContent[] = +{ + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f +}; + + +/** + * Test fixture for 93C46-compatible EEPROM device emulation. + */ +class EEPROMTest +#ifdef USE_CPPUNIT + : public CppUnit::TestFixture +#endif +{ + CPPUNIT_TEST_SUITE( tstDevEEPROM ); + + CPPUNIT_TEST( testRead ); + CPPUNIT_TEST( testSequentialRead ); + CPPUNIT_TEST( testWrite ); + CPPUNIT_TEST( testWriteAll ); + CPPUNIT_TEST( testWriteDisabled ); + CPPUNIT_TEST( testErase ); + CPPUNIT_TEST( testEraseAll ); + + CPPUNIT_TEST_SUITE_END(); + +private: + enum Wires { DO=8, DI=4, CS=2, SK=0x01 }; + enum OpCodes { + READ_OPCODE = 0x6, + WRITE_OPCODE = 0x5, + ERASE_OPCODE = 0x7, + EWDS_OPCODE = 0x10, // erase/write disable + WRAL_OPCODE = 0x11, // write all + ERAL_OPCODE = 0x12, // erase all + EWEN_OPCODE = 0x13 // erase/write enable + }; + enum BitWidths { + READ_OPCODE_BITS = 3, + WRITE_OPCODE_BITS = 3, + ERASE_OPCODE_BITS = 3, + EWDS_OPCODE_BITS = 5, + WRAL_OPCODE_BITS = 5, + ERAL_OPCODE_BITS = 5, + EWEN_OPCODE_BITS = 5, + READ_ADDR_BITS = 6, + WRITE_ADDR_BITS = 6, + ERASE_ADDR_BITS = 6, + EWDS_ADDR_BITS = 4, + WRAL_ADDR_BITS = 4, + ERAL_ADDR_BITS = 4, + EWEN_ADDR_BITS = 4, + DATA_BITS = 16 + }; + + EEPROM93C46 *eeprom; + + // Helper methods + void shiftOutBits(uint16_t data, uint16_t count); + uint16_t shiftInBits(uint16_t count); + void getReady(); + void standby(); + void stop(); + uint16_t readAt(uint16_t addr); + bool writeTo(uint16_t addr, uint16_t value); + void writeOpAddr(int opCode, int opCodeBits, uint16_t addr, int addrBits); + void writeData(uint16_t value) { shiftOutBits(value, DATA_BITS); } + bool waitForCompletion(); + +public: + void setUp() + { + eeprom = new EEPROM93C46; + eeprom->init(g_abInitialContent); + } + + void tearDown() + { + delete eeprom; + } + + void testSize() + { + CPPUNIT_ASSERT_EQUAL( sizeof(g_abInitialContent), (size_t)EEPROM93C46::SIZE ); + } + + void testRead() + { + getReady(); + for ( uint32_t wordAddr=0; wordAddr < EEPROM93C46::SIZE; wordAddr++ ) { + shiftOutBits(READ_OPCODE, READ_OPCODE_BITS); + shiftOutBits(wordAddr, READ_ADDR_BITS); + + CPPUNIT_ASSERT_EQUAL( g_abInitialContent[wordAddr], (uint16_t)wordAddr ); + CPPUNIT_ASSERT_EQUAL( g_abInitialContent[wordAddr], shiftInBits(DATA_BITS) ); + standby(); + } + stop(); + } + + void testSequentialRead() + { + getReady(); + shiftOutBits(READ_OPCODE, READ_OPCODE_BITS); + shiftOutBits(0, READ_ADDR_BITS); + for ( int wordAddr=0; wordAddr < EEPROM93C46::SIZE; wordAddr++ ) { + CPPUNIT_ASSERT_EQUAL( g_abInitialContent[wordAddr], shiftInBits(DATA_BITS) ); + } + stop(); + } + + void testWrite() + { + //unused: int i; + uint16_t wordAddr; + + getReady(); + // Enable write + writeOpAddr(EWEN_OPCODE, EWEN_OPCODE_BITS, 0, EWEN_ADDR_BITS); + standby(); + + for ( wordAddr=0; wordAddr < EEPROM93C46::SIZE; wordAddr++ ) { + //writeOpAddr(WRITE_OPCODE, WRITE_OPCODE_BITS, (uint16_t)wordAddr, WRITE_ADDR_BITS); + writeTo(wordAddr, 0x3F00 - (wordAddr<<8)); + standby(); + + if (!waitForCompletion()) { + CPPUNIT_FAIL("EEPROM write was not completed"); + stop(); + return; + } + standby(); + } + + // Disable write + writeOpAddr(EWDS_OPCODE, EWDS_OPCODE_BITS, 0, EWDS_ADDR_BITS); + + stop(); + + // Now check the result + getReady(); + writeOpAddr(READ_OPCODE, READ_OPCODE_BITS, 0, READ_ADDR_BITS); + for ( wordAddr=0; wordAddr < EEPROM93C46::SIZE; wordAddr++ ) { + CPPUNIT_ASSERT_EQUAL((uint16_t)(0x3F00 - (wordAddr<<8)), shiftInBits(DATA_BITS) ); + } + stop(); + } + + void testWriteDisabled() + { + getReady(); + + uint16_t addr = 0; + uint16_t oldValue = readAt(addr); + stop(); + getReady(); + if (writeTo(addr, ~oldValue)) { + // Write appears to be successful -- continue + CPPUNIT_ASSERT_EQUAL(oldValue, readAt(addr)); + } + else { + CPPUNIT_FAIL("EEPROM write was not completed"); + } + stop(); + } + + void testErase() + { + int i; + uint16_t addr = 0x1F; + + getReady(); + // Enable write + shiftOutBits(EWEN_OPCODE, EWEN_OPCODE_BITS); + shiftOutBits(0, EWEN_ADDR_BITS); + standby(); + + if (writeTo(addr, addr)) { + stop(); + getReady(); + // Write successful -- continue + CPPUNIT_ASSERT_EQUAL(addr, readAt(addr)); + stop(); + getReady(); + + shiftOutBits(ERASE_OPCODE, ERASE_OPCODE_BITS); + shiftOutBits(addr, ERASE_ADDR_BITS); + + standby(); + + for (i = 0; i < 200; i++) { + if (eeprom->read() & DO) + break; + //usec_delay(50); + } + + if (i == 200) { + CPPUNIT_FAIL("EEPROM erase was not completed"); + stop(); + return; + } + + standby(); + + shiftOutBits(EWDS_OPCODE, EWDS_OPCODE_BITS); + shiftOutBits(0, EWDS_ADDR_BITS); + + stop(); + getReady(); + CPPUNIT_ASSERT_EQUAL((uint16_t)0xFFFF, readAt(addr)); + } + else { + CPPUNIT_FAIL("EEPROM write was not completed"); + } + stop(); + } + + void testWriteAll() + { + uint16_t addr; + + getReady(); + // Enable write + writeOpAddr(EWEN_OPCODE, EWEN_OPCODE_BITS, 0, EWEN_ADDR_BITS); + standby(); + // Fill all memory + writeOpAddr(WRAL_OPCODE, WRAL_OPCODE_BITS, 0, WRAL_ADDR_BITS); + writeData(0xABBA); + standby(); + + if (waitForCompletion()) { + stop(); + getReady(); + // Write successful -- verify all memory + for ( addr=0; addr < EEPROM93C46::SIZE; addr++ ) { + CPPUNIT_ASSERT_EQUAL((uint16_t)0xABBA, readAt(addr)); + } + } + else { + CPPUNIT_FAIL("EEPROM write was not completed"); + } + stop(); + } + + void testEraseAll() + { + //unused: int i; + uint16_t addr = 0x1F; + + getReady(); + // Enable write + writeOpAddr(EWEN_OPCODE, EWEN_OPCODE_BITS, 0, EWEN_ADDR_BITS); + standby(); + // Fill all memory + writeOpAddr(WRITE_OPCODE, WRITE_OPCODE_BITS, addr, WRITE_ADDR_BITS); + writeData(0); + standby(); + + if (waitForCompletion()) { + stop(); + getReady(); + // Write successful -- verify random location + CPPUNIT_ASSERT_EQUAL((uint16_t)0, readAt(addr)); + stop(); + getReady(); + + writeOpAddr(ERAL_OPCODE, ERAL_OPCODE_BITS, addr, ERAL_ADDR_BITS); + standby(); + + if (!waitForCompletion()) { + CPPUNIT_FAIL("EEPROM erase was not completed"); + stop(); + return; + } + + standby(); + + writeOpAddr(EWDS_OPCODE, EWDS_OPCODE_BITS, 0, EWDS_ADDR_BITS); + stop(); + + getReady(); + for ( addr=0; addr < EEPROM93C46::SIZE; addr++ ) { + CPPUNIT_ASSERT_EQUAL((uint16_t)0xFFFF, readAt(addr)); + } + } + else { + CPPUNIT_FAIL("EEPROM write was not completed"); + } + stop(); + } +}; + +/** + * shiftOutBits - Shift data bits our to the EEPROM + * @hw: pointer to the EEPROM object + * @data: data to send to the EEPROM + * @count: number of bits to shift out + * + * We need to shift 'count' bits out to the EEPROM. So, the value in the + * "data" parameter will be shifted out to the EEPROM one bit at a time. + * In order to do this, "data" must be broken down into bits. + **/ +void EEPROMTest::shiftOutBits(uint16_t data, uint16_t count) { + uint32_t wires = eeprom->read(); + uint32_t mask; + + mask = 0x01 << (count - 1); + wires &= ~DO; + + do { + wires &= ~DI; + + if (data & mask) + wires |= DI; + + eeprom->write(wires); + + // Raise clock + eeprom->write(wires |= SK); + // Lower clock + eeprom->write(wires &= ~SK); + + mask >>= 1; + } while (mask); + + wires &= ~DI; + eeprom->write(wires); +} + +/** + * shiftInBits - Shift data bits in from the EEPROM + * @count: number of bits to shift in + * + * In order to read a register from the EEPROM, we need to shift 'count' bits + * in from the EEPROM. Bits are "shifted in" by raising the clock input to + * the EEPROM (setting the SK bit), and then reading the value of the data out + * "DO" bit. During this "shifting in" process the data in "DI" bit should + * always be clear. + **/ +uint16_t EEPROMTest::shiftInBits(uint16_t count) +{ + uint32_t wires; + uint32_t i; + uint16_t data; + + wires = eeprom->read(); + + wires &= ~(DO | DI); + data = 0; + + for (i = 0; i < count; i++) { + data <<= 1; + // Raise clock + eeprom->write(wires |= SK); + + wires = eeprom->read(); + + wires &= ~DI; + if (wires & DO) + data |= 1; + + // Lower clock + eeprom->write(wires &= ~SK); + } + + return data; +} + +/** + * getReady - Prepares EEPROM for read/write + * + * Setups the EEPROM for reading and writing. + **/ +void EEPROMTest::getReady() +{ + unsigned wires = eeprom->read(); + /* Clear SK and DI */ + eeprom->write(wires &= ~(DI | SK)); + /* Set CS */ + eeprom->write(wires | CS); +} + +/** + * standby - Return EEPROM to standby state + * + * Return the EEPROM to a standby state. + **/ +void EEPROMTest::standby() +{ + unsigned wires = eeprom->read(); + + eeprom->write(wires &= ~(CS | SK)); + + // Raise clock + eeprom->write(wires |= SK); + + // Select EEPROM + eeprom->write(wires |= CS); + + // Lower clock + eeprom->write(wires &= ~SK); +} + +/** + * stop - Terminate EEPROM command + * + * Terminates the current command by inverting the EEPROM's chip select pin. + **/ +void EEPROMTest::stop() +{ + unsigned wires = eeprom->read(); + + eeprom->write(wires &= ~(CS | DI)); + // Raise clock + eeprom->write(wires |= SK); + // Lower clock + eeprom->write(wires &= ~SK); +} + +/** + * readAt - Read a word at specified address + * @addr: address to read + * + * Returns the value of the word specified in 'addr' parameter. + **/ +uint16_t EEPROMTest::readAt(uint16_t addr) +{ + getReady(); + shiftOutBits(READ_OPCODE, READ_OPCODE_BITS); + shiftOutBits(addr, READ_ADDR_BITS); + + uint16_t value = shiftInBits(DATA_BITS); + stop(); + + return value; +} + +/** + * writeTo - Write a word to specified address + * @addr: address to write to + * @value: value to store + * + * Returns false if write did not complete. + * + * Note: Make sure EEPROM is selected and writable before attempting + * to write. Use getReady() and stop() to select/deselect + * EEPROM. + **/ +bool EEPROMTest::writeTo(uint16_t addr, uint16_t value) +{ + writeOpAddr(WRITE_OPCODE, WRITE_OPCODE_BITS, addr, WRITE_ADDR_BITS); + writeData(value); + standby(); + return waitForCompletion(); +} + + +/** + * waitForCompletion - Wait until EEPROM clears the busy bit + * + * Returns false if the EEPROM is still busy. + */ +bool EEPROMTest::waitForCompletion() { + for (int i = 0; i < 200; i++) { + if (eeprom->read() & DO) { + standby(); + return true; + } + // Wait 50 usec; + } + + return false; +} + +/** + * writeOpAddr - Write an opcode and address + * @opCode: operation code + * @opCodeBits: number of bits in opCode + * @addr: address to write to + * @addrBits: number of bits in address + **/ +void EEPROMTest::writeOpAddr(int opCode, int opCodeBits, uint16_t addr, int addrBits) +{ + shiftOutBits(opCode, opCodeBits); + shiftOutBits(addr, addrBits); +} + +int main() +{ +#ifdef USE_CPPUNIT + CppUnit::TextUi::TestRunner runner; + runner.addTest( EEPROMTest::suite() ); + return runner.run() ? 0 : 1; +#else + EEPROMTest Test; + return Test.run(); +#endif +} + diff --git a/src/VBox/Devices/Network/testcase/tstDevPhy.cpp b/src/VBox/Devices/Network/testcase/tstDevPhy.cpp new file mode 100644 index 00000000..93186bfe --- /dev/null +++ b/src/VBox/Devices/Network/testcase/tstDevPhy.cpp @@ -0,0 +1,192 @@ +/* $Id: tstDevPhy.cpp $ */ +/** @file + * PHY MDIO unit tests. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef USE_CPPUNIT +# include <cppunit/ui/text/TestRunner.h> +# include <cppunit/extensions/HelperMacros.h> +#else +# include "CppUnitEmulation.h" +#endif + +#include "../DevE1000Phy.h" + + +/** + * Test fixture for PHY MDIO/MDC interface emulation. + */ +class PhyTest +#ifdef USE_CPPUNIT + : public CppUnit::TestFixture +#endif +{ + CPPUNIT_TEST_SUITE( tstDevPhy ); + + CPPUNIT_TEST(testSize); + CPPUNIT_TEST(testReadPID); + CPPUNIT_TEST(testReadEPID); + CPPUNIT_TEST(testWriteANA); + + CPPUNIT_TEST_SUITE_END(); + +private: + enum Op + { + WRITE_OP = 0x1, + READ_OP = 0x2 + }; + +#define PHYADR_VAL (uint16_t)0 +#define ST_VAL (uint16_t)1 +#define TA_VAL (uint16_t)2 +#define PREAMBLE_VAL 0xFFFFFFFF + + enum BitWidths { + ST_BITS = 2, + OP_BITS = 2, + PHYADR_BITS = 5, + REGADR_BITS = 5, + TA_BITS = 2, + DATA_BITS = 16, + PREAMBLE_BITS = 32 + }; + + PPHY phy; + + // Helper methods + void shiftOutBits(uint32_t data, uint16_t count); + uint16_t shiftInBits(uint16_t count); + int readAt(uint16_t addr); + void writeTo(uint16_t addr, uint32_t value); + +public: + void setUp() + { + phy = new PHY; + Phy::init(phy, 0, PHY_EPID_M881000); + } + + void tearDown() + { + delete phy; + } + + void testSize() + { + CPPUNIT_ASSERT_EQUAL(32, ST_BITS+OP_BITS+PHYADR_BITS+REGADR_BITS+TA_BITS+DATA_BITS); + } + + void testReadPID() + { + CPPUNIT_ASSERT_EQUAL(0x0141, readAt(2)); + } + + void testReadEPID() + { + CPPUNIT_ASSERT_EQUAL(0x0141, readAt(2)); + CPPUNIT_ASSERT_EQUAL(PHY_EPID_M881000, readAt(3)); + } + + void testWriteANA() + { + writeTo(4, 0xBEEF); + CPPUNIT_ASSERT_EQUAL(0xBEEF, readAt(4)); + } + +}; + +/** + * shiftOutBits - Shift data bits our to MDIO + **/ +void PhyTest::shiftOutBits(uint32_t data, uint16_t count) { + uint32_t mask = 0x01 << (count - 1); + + do { + Phy::writeMDIO(phy, data & mask, NULL /*pDevIns*/); + mask >>= 1; + } while (mask); +} + +/** + * shiftInBits - Shift data bits in from MDIO + **/ +uint16_t PhyTest::shiftInBits(uint16_t count) +{ + uint16_t data = 0; + + while (count--) + { + data <<= 1; + data |= Phy::readMDIO(phy) ? 1 : 0; + } + + return data; +} + +int PhyTest::readAt(uint16_t addr) +{ + shiftOutBits(PREAMBLE_VAL, PREAMBLE_BITS); + + shiftOutBits(ST_VAL, ST_BITS); + shiftOutBits(READ_OP, OP_BITS); + shiftOutBits(PHYADR_VAL, PHYADR_BITS); + shiftOutBits(addr, REGADR_BITS); + + CPPUNIT_ASSERT_EQUAL((uint16_t)0, shiftInBits(1)); + uint16_t u16Data = shiftInBits(DATA_BITS); + shiftInBits(1); + return u16Data; +} + +void PhyTest::writeTo(uint16_t addr, uint32_t value) +{ + shiftOutBits(PREAMBLE_VAL, PREAMBLE_BITS); + + shiftOutBits(ST_VAL, ST_BITS); + shiftOutBits(WRITE_OP, OP_BITS); + shiftOutBits(PHYADR_VAL, PHYADR_BITS); + shiftOutBits(addr, REGADR_BITS); + shiftOutBits(TA_VAL, TA_BITS); + shiftOutBits(value, DATA_BITS); +} + +// Create text test runner and run all tests. +int main() +{ +#ifdef USE_CPPUNIT + CppUnit::TextUi::TestRunner runner; + runner.addTest( PhyTest::suite() ); + return runner.run() ? 0 : 1; +#else + PhyTest Test; + return Test.run(); +#endif +} + diff --git a/src/VBox/Devices/Network/testcase/tstIntNet-1.cpp b/src/VBox/Devices/Network/testcase/tstIntNet-1.cpp new file mode 100644 index 00000000..fbbff794 --- /dev/null +++ b/src/VBox/Devices/Network/testcase/tstIntNet-1.cpp @@ -0,0 +1,1030 @@ +/* $Id: tstIntNet-1.cpp $ */ +/** @file + * VBox - Testcase for internal networking, simple NetFlt trunk creation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/intnet.h> +#include <VBox/intnetinline.h> +#include <VBox/vmm/pdmnetinline.h> +#include <VBox/sup.h> +#include <VBox/vmm/vmm.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/alloc.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/param.h> +#include <iprt/getopt.h> +#include <iprt/rand.h> +#include <iprt/log.h> +#include <iprt/crc.h> +#include <iprt/net.h> + +#include "../Pcap.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static int g_cErrors = 0; +static uint64_t g_StartTS = 0; +static uint32_t g_DhcpXID = 0; +static bool g_fDhcpReply = false; +static bool g_fPingReply = false; +static uint32_t g_cOtherPkts = 0; +static uint32_t g_cArpPkts = 0; +static uint32_t g_cIpv4Pkts = 0; +static uint32_t g_cUdpPkts = 0; +static uint32_t g_cDhcpPkts = 0; +static uint32_t g_cTcpPkts = 0; + + +/** + * Error reporting wrapper. + * + * @param pErrStrm The stream to write the error message to. Can be NULL. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static void tstIntNetError(PRTSTREAM pErrStrm, const char *pszFormat, ...) +{ + if (!pErrStrm) + pErrStrm = g_pStdOut; + + va_list va; + va_start(va, pszFormat); + RTStrmPrintf(pErrStrm, "tstIntNet-1: ERROR - "); + RTStrmPrintfV(pErrStrm, pszFormat, va); + va_end(va); + + g_cErrors++; +} + + +/** + * Parses a frame an runs in thru the RTNet validation code so it gets + * some exercise. + * + * @param pvFrame Pointer to the ethernet frame. + * @param cbFrame The size of the ethernet frame. + * @param pErrStrm The error stream. + */ +static void tstIntNetTestFrame(void const *pvFrame, size_t cbFrame, PRTSTREAM pErrStrm, bool fGso) +{ + /* + * Ethernet header. + */ + PCRTNETETHERHDR pEtherHdr = (PCRTNETETHERHDR)pvFrame; + if (cbFrame <= sizeof(*pEtherHdr)) + return tstIntNetError(pErrStrm, "cbFrame=%#x <= %#x (ether)\n", cbFrame, sizeof(*pEtherHdr)); + ssize_t cbLeft = cbFrame - sizeof(*pEtherHdr); + uint8_t const *pbCur = (uint8_t const *)(pEtherHdr + 1); + + switch (RT_BE2H_U16(pEtherHdr->EtherType)) + { + case RTNET_ETHERTYPE_ARP: + { + g_cArpPkts++; + break; + } + + case RTNET_ETHERTYPE_IPV4: + { + g_cIpv4Pkts++; + + PCRTNETIPV4 pIpHdr = (PCRTNETIPV4)pbCur; + if (!RTNetIPv4IsHdrValid(pIpHdr, cbLeft, cbLeft, !fGso /*fChecksum*/)) + return tstIntNetError(pErrStrm, "RTNetIPv4IsHdrValid failed\n"); + pbCur += pIpHdr->ip_hl * 4; + cbLeft -= pIpHdr->ip_hl * 4; + AssertFatal(cbLeft >= 0); + + switch (pIpHdr->ip_p) + { + case RTNETIPV4_PROT_ICMP: + { + /** @todo ICMP? */ + break; + } + + case RTNETIPV4_PROT_UDP: + { + g_cUdpPkts++; + PCRTNETUDP pUdpHdr = (PCRTNETUDP)pbCur; + if (!RTNetIPv4IsUDPValid(pIpHdr, pUdpHdr, pUdpHdr + 1, cbLeft, !fGso /*fChecksum*/)) + return tstIntNetError(pErrStrm, "RTNetIPv4IsUDPValid failed\n"); + pbCur += sizeof(*pUdpHdr); + cbLeft -= sizeof(*pUdpHdr); + + if (RT_BE2H_U16(pUdpHdr->uh_dport) == RTNETIPV4_PORT_BOOTPS) + { + g_cDhcpPkts++; + PCRTNETBOOTP pDhcp = (PCRTNETBOOTP)pbCur; + if (!RTNetIPv4IsDHCPValid(pUdpHdr, pDhcp, cbLeft, NULL)) + return tstIntNetError(pErrStrm, "RTNetIPv4IsDHCPValid failed\n"); + } + break; + } + + case RTNETIPV4_PROT_TCP: + { + g_cTcpPkts++; + PCRTNETTCP pTcpHdr = (PCRTNETTCP)pbCur; + if (!RTNetIPv4IsTCPValid(pIpHdr, pTcpHdr, cbLeft, NULL, cbLeft, !fGso /*fChecksum*/)) + return tstIntNetError(pErrStrm, "RTNetIPv4IsTCPValid failed\n"); + break; + } + } + break; + } + + //case RTNET_ETHERTYPE_IPV6: + default: + g_cOtherPkts++; + break; + } +} + + +/** + * Transmits one frame after appending the CRC. + * + * @param hIf The interface handle. + * @param pSession The session. + * @param pBuf The shared interface buffer. + * @param pvFrame The frame without a crc. + * @param cbFrame The size of it. + * @param pFileRaw The file to write the raw data to (optional). + * @param pFileText The file to write a textual packet summary to (optional). + */ +static void doXmitFrame(INTNETIFHANDLE hIf, PSUPDRVSESSION pSession, PINTNETBUF pBuf, void *pvFrame, size_t cbFrame, PRTSTREAM pFileRaw, PRTSTREAM pFileText) +{ + /* + * Log it. + */ + if (pFileText) + { + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pvFrame; + uint64_t NanoTS = RTTimeNanoTS() - g_StartTS; + RTStrmPrintf(pFileText, "%3RU64.%09u: cb=%04x dst=%.6Rhxs src=%.6Rhxs type=%04x Send!\n", + NanoTS / 1000000000, (uint32_t)(NanoTS % 1000000000), + cbFrame, &pEthHdr->SrcMac, &pEthHdr->DstMac, RT_BE2H_U16(pEthHdr->EtherType)); + } + + /* + * Run in thru the frame validator to test the RTNet code. + */ + tstIntNetTestFrame(pvFrame, cbFrame, pFileText, false /*fGso*/); + + /* + * Write the frame and push the queue. + * + * Don't bother with dealing with overflows like DrvIntNet does, because + * it's not supposed to happen here in this testcase. + */ + int rc = IntNetRingWriteFrame(&pBuf->Send, pvFrame, cbFrame); + if (RT_SUCCESS(rc)) + { + if (pFileRaw) + PcapStreamFrame(pFileRaw, g_StartTS, pvFrame, cbFrame, 0xffff); + } + else + { + RTPrintf("tstIntNet-1: IntNetRingWriteFrame failed, %Rrc; cbFrame=%d pBuf->cbSend=%d\n", rc, cbFrame, pBuf->cbSend); + g_cErrors++; + } + + INTNETIFSENDREQ SendReq; + SendReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + SendReq.Hdr.cbReq = sizeof(SendReq); + SendReq.pSession = pSession; + SendReq.hIf = hIf; + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_SEND, 0, &SendReq.Hdr); + if (RT_FAILURE(rc)) + { + RTPrintf("tstIntNet-1: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_SEND,) failed, rc=%Rrc\n", rc); + g_cErrors++; + } + +} + + +/** + * Does the transmit test. + * + * @param hIf The interface handle. + * @param pSession The session. + * @param pBuf The shared interface buffer. + * @param pSrcMac The mac address to use as source. + * @param pFileRaw The file to write the raw data to (optional). + * @param pFileText The file to write a textual packet summary to (optional). + */ +static void doXmitTest(INTNETIFHANDLE hIf, PSUPDRVSESSION pSession, PINTNETBUF pBuf, PCRTMAC pSrcMac, PRTSTREAM pFileRaw, PRTSTREAM pFileText) +{ + uint8_t abFrame[4096]; + PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)&abFrame[0]; + PRTNETIPV4 pIpHdr = (PRTNETIPV4) (pEthHdr + 1); + PRTNETUDP pUdpHdr = (PRTNETUDP) (pIpHdr + 1); + PRTNETDHCP pDhcpMsg = (PRTNETDHCP) (pUdpHdr + 1); + + /* + * Create a simple DHCP broadcast request. + */ + memset(&abFrame, 0, sizeof(abFrame)); + + pDhcpMsg->Op = 1; /* request */ + pDhcpMsg->HType = 1; /* ethernet */ + pDhcpMsg->HLen = sizeof(RTMAC); + pDhcpMsg->Hops = 0; + pDhcpMsg->XID = g_DhcpXID = RTRandU32(); + pDhcpMsg->Secs = 0; + pDhcpMsg->Flags = 0; /* unicast */ //RT_H2BE_U16(0x8000); /* broadcast */ + pDhcpMsg->CIAddr.u = 0; + pDhcpMsg->YIAddr.u = 0; + pDhcpMsg->SIAddr.u = 0; + pDhcpMsg->GIAddr.u = 0; + memset(&pDhcpMsg->CHAddr[0], '\0', sizeof(pDhcpMsg->CHAddr)); + memcpy(&pDhcpMsg->CHAddr[0], pSrcMac, sizeof(*pSrcMac)); + memset(&pDhcpMsg->SName[0], '\0', sizeof(pDhcpMsg->SName)); + memset(&pDhcpMsg->File[0], '\0', sizeof(pDhcpMsg->File)); + pDhcpMsg->abMagic[0] = 99; + pDhcpMsg->abMagic[1] = 130; + pDhcpMsg->abMagic[2] = 83; + pDhcpMsg->abMagic[3] = 99; + + pDhcpMsg->DhcpOpt = 53; /* DHCP Msssage Type option */ + pDhcpMsg->DhcpLen = 1; + pDhcpMsg->DhcpReq = 1; /* DHCPDISCOVER */ + + memset(&pDhcpMsg->abOptions[0], '\0', sizeof(pDhcpMsg->abOptions)); + uint8_t *pbOpt = &pDhcpMsg->abOptions[0]; + + *pbOpt++ = 116; /* DHCP Auto-Configure */ + *pbOpt++ = 1; + *pbOpt++ = 1; + + *pbOpt++ = 61; /* Client identifier */ + *pbOpt++ = 1 + sizeof(*pSrcMac); + *pbOpt++ = 1; /* hw type: ethernet */ + memcpy(pbOpt, pSrcMac, sizeof(*pSrcMac)); + pbOpt += sizeof(*pSrcMac); + + *pbOpt++ = 12; /* Host name */ + *pbOpt++ = sizeof("tstIntNet-1") - 1; + memcpy(pbOpt, "tstIntNet-1", sizeof("tstIntNet-1") - 1); + pbOpt += sizeof("tstIntNet-1") - 1; + + *pbOpt = 0xff; /* the end */ + + /* UDP */ + pUdpHdr->uh_sport = RT_H2BE_U16(68); /* bootp */ + pUdpHdr->uh_dport = RT_H2BE_U16(67); /* bootps */ + pUdpHdr->uh_ulen = RT_H2BE_U16(sizeof(*pDhcpMsg) + sizeof(*pUdpHdr)); + pUdpHdr->uh_sum = 0; /* pretend checksumming is disabled */ + + /* IP */ + pIpHdr->ip_v = 4; + pIpHdr->ip_hl = sizeof(*pIpHdr) / sizeof(uint32_t); + pIpHdr->ip_tos = 0; + pIpHdr->ip_len = RT_H2BE_U16(sizeof(*pDhcpMsg) + sizeof(*pUdpHdr) + sizeof(*pIpHdr)); + pIpHdr->ip_id = (uint16_t)RTRandU32(); + pIpHdr->ip_off = 0; + pIpHdr->ip_ttl = 255; + pIpHdr->ip_p = 0x11; /* UDP */ + pIpHdr->ip_sum = 0; + pIpHdr->ip_src.u = 0; + pIpHdr->ip_dst.u = UINT32_C(0xffffffff); /* broadcast */ + pIpHdr->ip_sum = RTNetIPv4HdrChecksum(pIpHdr); + + /* calc the UDP checksum. */ + pUdpHdr->uh_sum = RTNetIPv4UDPChecksum(pIpHdr, pUdpHdr, pUdpHdr + 1); + + /* Ethernet */ + memset(&pEthHdr->DstMac, 0xff, sizeof(pEthHdr->DstMac)); /* broadcast */ + pEthHdr->SrcMac = *pSrcMac; + pEthHdr->EtherType = RT_H2BE_U16(RTNET_ETHERTYPE_IPV4); /* IP */ + + doXmitFrame(hIf, pSession, pBuf, &abFrame[0], (uint8_t *)(pDhcpMsg + 1) - (uint8_t *)&abFrame[0], pFileRaw, pFileText); +} + + +static uint16_t icmpChecksum(PRTNETICMPV4HDR pHdr, size_t cbHdr) +{ + size_t cbLeft = cbHdr; + uint16_t *pbSrc = (uint16_t *)pHdr; + uint16_t oddByte = 0; + int cSum = 0; + + while (cbLeft > 1) + { + cSum += *pbSrc++; + cbLeft -= 2; + } + + if (cbLeft == 1) + { + *(uint16_t *)(&oddByte) = *(uint16_t *)pbSrc; + cSum += oddByte; + } + + cSum = (cSum >> 16) + (cSum & 0xffff); + cSum += (cSum >> 16); + uint16_t Result = ~cSum; + return Result; +} + + +/** + * Does the rudimentary ping test with fixed destination and source IPs. + * + * @param hIf The interface handle. + * @param pSession The session. + * @param pBuf The shared interface buffer. + * @param pSrcMac The mac address to use as source. + * @param pFileRaw The file to write the raw data to (optional). + * @param pFileText The file to write a textual packet summary to (optional). + */ +static void doPingTest(INTNETIFHANDLE hIf, PSUPDRVSESSION pSession, PINTNETBUF pBuf, PCRTMAC pSrcMac, PRTSTREAM pFileRaw, PRTSTREAM pFileText) +{ + uint8_t abFrame[4096]; + PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)&abFrame[0]; + PRTNETIPV4 pIpHdr = (PRTNETIPV4) (pEthHdr + 1); + PRTNETICMPV4ECHO pIcmpEcho = (PRTNETICMPV4ECHO) (pIpHdr + 1); + + /* + * Create a simple ping request. + */ + memset(&abFrame, 0, sizeof(abFrame)); + + pIcmpEcho->Hdr.icmp_type = RTNETICMPV4_TYPE_ECHO_REQUEST; + pIcmpEcho->Hdr.icmp_code = 0; + pIcmpEcho->icmp_id = 0x06; + pIcmpEcho->icmp_seq = 0x05; + size_t cbPad = 56; + memset(&pIcmpEcho->icmp_data, '\0', cbPad); + pIcmpEcho->Hdr.icmp_cksum = icmpChecksum(&pIcmpEcho->Hdr, cbPad + 8); + + /* IP */ + pIpHdr->ip_v = 4; + pIpHdr->ip_hl = sizeof(*pIpHdr) / sizeof(uint32_t); + pIpHdr->ip_tos = 0; + pIpHdr->ip_len = RT_H2BE_U16((uint16_t)(sizeof(*pIcmpEcho) + cbPad + sizeof(*pIpHdr))); + pIpHdr->ip_id = (uint16_t)RTRandU32(); + pIpHdr->ip_off = 0; + pIpHdr->ip_ttl = 255; + pIpHdr->ip_p = 0x01; /*ICMP */ + pIpHdr->ip_sum = 0; + pIpHdr->ip_src.u = UINT32_C(0x9701A8C0); /* 192.168.1.151 */ + pIpHdr->ip_dst.u = UINT32_C(0xF9A344D0); /* 208.68.163.249 */ + pIpHdr->ip_sum = RTNetIPv4HdrChecksum(pIpHdr); + + /* Ethernet */ + memset(&pEthHdr->DstMac, 0xff, sizeof(pEthHdr->DstMac)); /* broadcast */ + + pEthHdr->SrcMac = *pSrcMac; +#if 0 /* Enable with host's real Mac address for testing of the testcase. */ + pEthHdr->SrcMac.au8[0] = 0x00; + pEthHdr->SrcMac.au8[1] = 0x1b; + pEthHdr->SrcMac.au8[2] = 0x24; + pEthHdr->SrcMac.au8[3] = 0xa0; + pEthHdr->SrcMac.au8[4] = 0x2f; + pEthHdr->SrcMac.au8[5] = 0xce; +#endif + + pEthHdr->EtherType = RT_H2BE_U16(RTNET_ETHERTYPE_IPV4); /* IP */ + + doXmitFrame(hIf, pSession, pBuf, &abFrame[0], (uint8_t *)(pIcmpEcho + 1) + cbPad - (uint8_t *)&abFrame[0], pFileRaw, pFileText); +} + + +/** + * Does packet sniffing for a given period of time. + * + * @param hIf The interface handle. + * @param pSession The session. + * @param pBuf The shared interface buffer. + * @param cMillies The time period, ms. + * @param pFileRaw The file to write the raw data to (optional). + * @param pFileText The file to write a textual packet summary to (optional). + * @param pSrcMac Out MAC address. + */ +static void doPacketSniffing(INTNETIFHANDLE hIf, PSUPDRVSESSION pSession, PINTNETBUF pBuf, uint32_t cMillies, + PRTSTREAM pFileRaw, PRTSTREAM pFileText, PCRTMAC pSrcMac) +{ + /* + * The loop. + */ + PINTNETRINGBUF pRingBuf = &pBuf->Recv; + for (;;) + { + /* + * Wait for a packet to become available. + */ + uint64_t cElapsedMillies = (RTTimeNanoTS() - g_StartTS) / 1000000; + if (cElapsedMillies >= cMillies) + break; + INTNETIFWAITREQ WaitReq; + WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + WaitReq.Hdr.cbReq = sizeof(WaitReq); + WaitReq.pSession = pSession; + WaitReq.hIf = hIf; + WaitReq.cMillies = cMillies - (uint32_t)cElapsedMillies; + int rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_WAIT, 0, &WaitReq.Hdr); + if (rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED) + break; + if (RT_FAILURE(rc)) + { + g_cErrors++; + RTPrintf("tstIntNet-1: VMMR0_DO_INTNET_IF_WAIT returned %Rrc\n", rc); + break; + } + + /* + * Process the receive buffer. + */ + PINTNETHDR pHdr; + while ((pHdr = IntNetRingGetNextFrameToRead(pRingBuf))) + { + if (pHdr->u8Type == INTNETHDR_TYPE_FRAME) + { + size_t cbFrame = pHdr->cbFrame; + const void *pvFrame = IntNetHdrGetFramePtr(pHdr, pBuf); + uint64_t NanoTS = RTTimeNanoTS() - g_StartTS; + + if (pFileRaw) + PcapStreamFrame(pFileRaw, g_StartTS, pvFrame, cbFrame, 0xffff); + + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pvFrame; + if (pFileText) + RTStrmPrintf(pFileText, "%3RU64.%09u: cb=%04x dst=%.6Rhxs src=%.6Rhxs type=%04x%s\n", + NanoTS / 1000000000, (uint32_t)(NanoTS % 1000000000), + cbFrame, &pEthHdr->DstMac, &pEthHdr->SrcMac, RT_BE2H_U16(pEthHdr->EtherType), + !memcmp(&pEthHdr->DstMac, pSrcMac, sizeof(*pSrcMac)) ? " Mine!" : ""); + tstIntNetTestFrame(pvFrame, cbFrame, pFileText, false /*fGso*/); + + /* Loop for the DHCP reply. */ + if ( cbFrame > 64 + && RT_BE2H_U16(pEthHdr->EtherType) == 0x0800 /* EtherType == IP */) + { + PCRTNETIPV4 pIpHdr = (PCRTNETIPV4)(pEthHdr + 1); + PCRTNETUDP pUdpHdr = (PCRTNETUDP)((uint32_t *)pIpHdr + pIpHdr->ip_hl); + if ( pIpHdr->ip_p == 0x11 /*UDP*/ + && RT_BE2H_U16(pUdpHdr->uh_dport) == 68 /* bootp */ + && RT_BE2H_U16(pUdpHdr->uh_sport) == 67 /* bootps */) + { + PCRTNETDHCP pDhcpMsg = (PCRTNETDHCP)(pUdpHdr + 1); + if ( pDhcpMsg->Op == 2 /* boot reply */ + && pDhcpMsg->HType == 1 /* ethernet */ + && pDhcpMsg->HLen == sizeof(RTMAC) + && (pDhcpMsg->XID == g_DhcpXID || !g_DhcpXID) + && !memcmp(&pDhcpMsg->CHAddr[0], pSrcMac, sizeof(*pSrcMac))) + { + g_fDhcpReply = true; + RTPrintf("tstIntNet-1: DHCP server reply! My IP: %d.%d.%d.%d\n", + pDhcpMsg->YIAddr.au8[0], + pDhcpMsg->YIAddr.au8[1], + pDhcpMsg->YIAddr.au8[2], + pDhcpMsg->YIAddr.au8[3]); + } + } + else if (pIpHdr->ip_p == 0x01) /* ICMP */ + { + PRTNETICMPV4HDR pIcmpHdr = (PRTNETICMPV4HDR)(pIpHdr + 1); + PRTNETICMPV4ECHO pIcmpEcho = (PRTNETICMPV4ECHO)(pIpHdr + 1); + if ( pIcmpHdr->icmp_type == RTNETICMPV4_TYPE_ECHO_REPLY + && pIcmpEcho->icmp_seq == 0x05 + && pIpHdr->ip_dst.u == UINT32_C(0x9701A8C0) +#if 0 + /** Enable with the host's real Mac address for testing of the testcase.*/ + && pEthHdr->DstMac.au8[0] == 0x00 + && pEthHdr->DstMac.au8[1] == 0x1b + && pEthHdr->DstMac.au8[2] == 0x24 + && pEthHdr->DstMac.au8[3] == 0xa0 + && pEthHdr->DstMac.au8[4] == 0x2f + && pEthHdr->DstMac.au8[5] == 0xce +#else + && pEthHdr->DstMac.au16[0] == pSrcMac->au16[0] + && pEthHdr->DstMac.au16[1] == pSrcMac->au16[1] + && pEthHdr->DstMac.au16[2] == pSrcMac->au16[2] +#endif + ) + { + g_fPingReply = true; + RTPrintf("tstIntNet-1: Ping reply! From %d.%d.%d.%d\n", + pIpHdr->ip_src.au8[0], + pIpHdr->ip_src.au8[1], + pIpHdr->ip_src.au8[2], + pIpHdr->ip_src.au8[3]); + } + else + RTPrintf("type=%d seq=%d dstmac=%.6Rhxs ip=%d.%d.%d.%d\n", pIcmpHdr->icmp_type, pIcmpEcho->icmp_seq, + &pEthHdr->DstMac, pIpHdr->ip_dst.au8[0], pIpHdr->ip_dst.au8[1], pIpHdr->ip_dst.au8[2], pIpHdr->ip_dst.au8[3]); + } + } + } + else if (pHdr->u8Type == INTNETHDR_TYPE_GSO) + { + PCPDMNETWORKGSO pGso = IntNetHdrGetGsoContext(pHdr, pBuf); + size_t cbFrame = pHdr->cbFrame; + if (PDMNetGsoIsValid(pGso, cbFrame, cbFrame - sizeof(*pGso))) + { + const void *pvFrame = pGso + 1; + uint64_t NanoTS = RTTimeNanoTS() - g_StartTS; + cbFrame -= sizeof(pGso); + + if (pFileRaw) + PcapStreamGsoFrame(pFileRaw, g_StartTS, pGso, pvFrame, cbFrame, 0xffff); + + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pvFrame; + if (pFileText) + RTStrmPrintf(pFileText, "%3RU64.%09u: cb=%04x dst=%.6Rhxs src=%.6Rhxs type=%04x%s [GSO]\n", + NanoTS / 1000000000, (uint32_t)(NanoTS % 1000000000), + cbFrame, &pEthHdr->DstMac, &pEthHdr->SrcMac, RT_BE2H_U16(pEthHdr->EtherType), + !memcmp(&pEthHdr->DstMac, pSrcMac, sizeof(*pSrcMac)) ? " Mine!" : ""); + tstIntNetTestFrame(pvFrame, cbFrame, pFileText, true /*fGso*/); + } + else + { + RTPrintf("tstIntNet-1: Bad GSO frame: %.*Rhxs\n", sizeof(*pGso), pGso); + STAM_REL_COUNTER_INC(&pBuf->cStatBadFrames); + g_cErrors++; + } + } + else if (pHdr->u8Type != INTNETHDR_TYPE_PADDING) + { + RTPrintf("tstIntNet-1: Unknown frame type %d\n", pHdr->u8Type); + STAM_REL_COUNTER_INC(&pBuf->cStatBadFrames); + g_cErrors++; + } + + /* Advance to the next frame. */ + IntNetRingSkipFrame(pRingBuf); + } + } + + uint64_t NanoTS = RTTimeNanoTS() - g_StartTS; + RTStrmPrintf(pFileText ? pFileText : g_pStdOut, + "%3RU64.%09u: stopped. cRecvs=%RU64 cbRecv=%RU64 cLost=%RU64 cOYs=%RU64 cNYs=%RU64\n", + NanoTS / 1000000000, (uint32_t)(NanoTS % 1000000000), + pBuf->Recv.cStatFrames.c, + pBuf->Recv.cbStatWritten.c, + pBuf->cStatLost.c, + pBuf->cStatYieldsOk.c, + pBuf->cStatYieldsNok.c + ); + RTStrmPrintf(pFileText ? pFileText : g_pStdOut, + "%3RU64.%09u: cOtherPkts=%RU32 cArpPkts=%RU32 cIpv4Pkts=%RU32 cTcpPkts=%RU32 cUdpPkts=%RU32 cDhcpPkts=%RU32\n", + NanoTS / 1000000000, (uint32_t)(NanoTS % 1000000000), + g_cOtherPkts, g_cArpPkts, g_cIpv4Pkts, g_cTcpPkts, g_cUdpPkts, g_cDhcpPkts); +} + +#ifdef RT_OS_LINUX +#include <stdio.h> +#include <net/if.h> +#include <net/route.h> +/** + * Obtain the name of the interface used for default routing. + * + * NOTE: Copied from Main/src-server/linux/NetIf-linux.cpp + * + * @returns VBox status code. + * + * @param pszName The buffer where to put the name. + * @param cbName The buffer length. + */ +static int getDefaultIfaceName(char *pszName, size_t cbName) +{ + FILE *fp = fopen("/proc/net/route", "r"); + char szBuf[1024]; + char szIfName[17]; + uint32_t uAddr; + uint32_t uGateway; + uint32_t uMask; + int iTmp; + unsigned uFlags; + + if (fp) + { + while (fgets(szBuf, sizeof(szBuf)-1, fp)) + { + int n = sscanf(szBuf, "%16s %x %x %x %d %d %d %x %d %d %d\n", + szIfName, &uAddr, &uGateway, &uFlags, &iTmp, &iTmp, &iTmp, + &uMask, &iTmp, &iTmp, &iTmp); + if (n < 10 || !(uFlags & RTF_UP)) + continue; + + if (uAddr == 0 && uMask == 0) + { + fclose(fp); + szIfName[sizeof(szIfName) - 1] = '\0'; + return RTStrCopy(pszName, cbName, szIfName); + } + } + fclose(fp); + } + return VERR_INTERNAL_ERROR; +} +#endif /* RT_OS_LINUX */ + + +/** + * Entry point. + */ +extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) +{ + RT_NOREF(envp); + + /* + * Init the runtime and parse the arguments. + */ + RTR3InitExe(argc, &argv, 0); + + static RTGETOPTDEF const s_aOptions[] = + { + { "--duration", 'd', RTGETOPT_REQ_UINT32 }, + { "--file", 'f', RTGETOPT_REQ_STRING }, + { "--interface", 'i', RTGETOPT_REQ_STRING }, + { "--mac-sharing", 'm', RTGETOPT_REQ_NOTHING }, + { "--network", 'n', RTGETOPT_REQ_STRING }, + { "--promiscuous", 'p', RTGETOPT_REQ_NOTHING }, + { "--recv-buffer", 'r', RTGETOPT_REQ_UINT32 }, + { "--send-buffer", 's', RTGETOPT_REQ_UINT32 }, + { "--sniffer", 'S', RTGETOPT_REQ_NOTHING }, + { "--text-file", 't', RTGETOPT_REQ_STRING }, + { "--xmit-test", 'x', RTGETOPT_REQ_NOTHING }, + { "--ping-test", 'P', RTGETOPT_REQ_NOTHING }, + }; + + uint32_t cMillies = 1000; + PRTSTREAM pFileRaw = NULL; +#ifdef RT_OS_DARWIN + const char *pszIf = "en0"; +#elif defined(RT_OS_LINUX) + char szIf[IFNAMSIZ+1] = "eth0"; /* Reasonable default */ + /* + * Try to update the default interface by consulting the routing table. + * If we fail we still have our reasonable default. + */ + getDefaultIfaceName(szIf, sizeof(szIf)); + const char *pszIf = szIf; +#elif defined(RT_OS_SOLARIS) + const char* pszIf = "rge0"; +#else + const char *pszIf = "em0"; +#endif + bool fMacSharing = false; + const char *pszNetwork = "tstIntNet-1"; + bool fPromiscuous = false; + uint32_t cbRecv = 0; + uint32_t cbSend = 0; + bool fSniffer = false; + PRTSTREAM pFileText = g_pStdOut; + bool fXmitTest = false; + bool fPingTest = false; + RTMAC SrcMac; + SrcMac.au8[0] = 0x08; + SrcMac.au8[1] = 0x03; + SrcMac.au8[2] = 0x86; + RTRandBytes(&SrcMac.au8[3], sizeof(SrcMac) - 3); + + int rc; + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */); + while ((ch = RTGetOpt(&GetState, &Value))) + switch (ch) + { + case 'd': + cMillies = Value.u32 * 1000; + if (cMillies / 1000 != Value.u32) + { + RTPrintf("tstIntNet-1: warning duration overflowed\n"); + cMillies = UINT32_MAX - 1; + } + break; + + case 'f': + rc = RTStrmOpen(Value.psz, "w+b", &pFileRaw); + if (RT_FAILURE(rc)) + { + RTPrintf("tstIntNet-1: Failed to creating \"%s\" for writing: %Rrc\n", Value.psz, rc); + return 1; + } + break; + + case 'i': + pszIf = Value.psz; + if (strlen(pszIf) >= INTNET_MAX_TRUNK_NAME) + { + RTPrintf("tstIntNet-1: Interface name is too long (max %d chars): %s\n", INTNET_MAX_TRUNK_NAME - 1, pszIf); + return 1; + } + break; + + case 'm': + fMacSharing = true; + break; + + case 'n': + pszNetwork = Value.psz; + if (strlen(pszNetwork) >= INTNET_MAX_NETWORK_NAME) + { + RTPrintf("tstIntNet-1: Network name is too long (max %d chars): %s\n", INTNET_MAX_NETWORK_NAME - 1, pszNetwork); + return 1; + } + break; + + case 'p': + fPromiscuous = true; + break; + + case 'r': + cbRecv = Value.u32; + break; + + case 's': + cbSend = Value.u32; + break; + + case 'S': + fSniffer = true; + break; + + case 't': + if (!*Value.psz) + pFileText = NULL; + else if (!strcmp(Value.psz, "-")) + pFileText = g_pStdOut; + else if (!strcmp(Value.psz, "!")) + pFileText = g_pStdErr; + else + { + rc = RTStrmOpen(Value.psz, "w", &pFileText); + if (RT_FAILURE(rc)) + { + RTPrintf("tstIntNet-1: Failed to creating \"%s\" for writing: %Rrc\n", Value.psz, rc); + return 1; + } + } + break; + + case 'x': + fXmitTest = true; + break; + + case 'P': + fPingTest = true; + break; + + case 'h': + RTPrintf("syntax: tstIntNet-1 <options>\n" + "\n" + "Options:\n"); + for (size_t i = 0; i < RT_ELEMENTS(s_aOptions); i++) + RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong); + RTPrintf("\n" + "Examples:\n" + " tstIntNet-1 -r 8192 -s 4096 -xS\n" + " tstIntNet-1 -n VBoxNetDhcp -r 4096 -s 4096 -i \"\" -xS\n"); + return 1; + + case 'V': + RTPrintf("$Revision: 155244 $\n"); + return 0; + + default: + return RTGetOptPrintError(ch, &Value); + } + + RTPrintf("tstIntNet-1: TESTING...\n"); + + /* + * Open the session, load ring-0 and issue the request. + */ + PSUPDRVSESSION pSession; + rc = SUPR3Init(&pSession); + if (RT_FAILURE(rc)) + { + RTPrintf("tstIntNet-1: SUPR3Init -> %Rrc\n", rc); + return 1; + } + + char szPath[RTPATH_MAX]; + rc = RTPathExecDir(szPath, sizeof(szPath) - sizeof("/../VMMR0.r0")); + if (RT_FAILURE(rc)) + { + RTPrintf("tstIntNet-1: RTPathExecDir -> %Rrc\n", rc); + return 1; + } + + strcat(szPath, "/../VMMR0.r0"); + + char szAbsPath[RTPATH_MAX]; + rc = RTPathAbs(szPath, szAbsPath, sizeof(szAbsPath)); + if (RT_FAILURE(rc)) + { + RTPrintf("tstIntNet-1: RTPathAbs -> %Rrc\n", rc); + return 1; + } + + rc = SUPR3LoadVMM(szAbsPath, NULL); + if (RT_FAILURE(rc)) + { + RTPrintf("tstIntNet-1: SUPR3LoadVMM(\"%s\") -> %Rrc\n", szAbsPath, rc); + return 1; + } + + /* + * Create the request, picking the network and trunk names from argv[2] + * and argv[1] if present. + */ + INTNETOPENREQ OpenReq; + OpenReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + OpenReq.Hdr.cbReq = sizeof(OpenReq); + OpenReq.pSession = pSession; + RTStrCopy(OpenReq.szNetwork, sizeof(OpenReq.szNetwork), pszNetwork); + RTStrCopy(OpenReq.szTrunk, sizeof(OpenReq.szTrunk), pszIf); + OpenReq.enmTrunkType = *pszIf ? kIntNetTrunkType_NetFlt : kIntNetTrunkType_WhateverNone; + OpenReq.fFlags = fMacSharing ? INTNET_OPEN_FLAGS_SHARED_MAC_ON_WIRE : 0; + OpenReq.cbSend = cbSend; + OpenReq.cbRecv = cbRecv; + OpenReq.hIf = INTNET_HANDLE_INVALID; + + /* + * Issue the request. + */ + RTPrintf("tstIntNet-1: attempting to open/create network \"%s\" with NetFlt trunk \"%s\"...\n", + OpenReq.szNetwork, OpenReq.szTrunk); + RTStrmFlush(g_pStdOut); + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_OPEN, 0, &OpenReq.Hdr); + if (RT_SUCCESS(rc)) + { + RTPrintf("tstIntNet-1: successfully opened/created \"%s\" with NetFlt trunk \"%s\" - hIf=%#x\n", + OpenReq.szNetwork, OpenReq.szTrunk, OpenReq.hIf); + RTStrmFlush(g_pStdOut); + + /* + * Get the ring-3 address of the shared interface buffer. + */ + INTNETIFGETBUFFERPTRSREQ GetBufferPtrsReq; + GetBufferPtrsReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + GetBufferPtrsReq.Hdr.cbReq = sizeof(GetBufferPtrsReq); + GetBufferPtrsReq.pSession = pSession; + GetBufferPtrsReq.hIf = OpenReq.hIf; + GetBufferPtrsReq.pRing3Buf = NULL; + GetBufferPtrsReq.pRing0Buf = NIL_RTR0PTR; + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, 0, &GetBufferPtrsReq.Hdr); + if (RT_SUCCESS(rc)) + { + PINTNETBUF pBuf = GetBufferPtrsReq.pRing3Buf; + RTPrintf("tstIntNet-1: pBuf=%p cbBuf=%d cbSend=%d cbRecv=%d\n", + pBuf, pBuf->cbBuf, pBuf->cbSend, pBuf->cbRecv); + RTStrmFlush(g_pStdOut); + if (fPromiscuous) + { + INTNETIFSETPROMISCUOUSMODEREQ PromiscReq; + PromiscReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + PromiscReq.Hdr.cbReq = sizeof(PromiscReq); + PromiscReq.pSession = pSession; + PromiscReq.hIf = OpenReq.hIf; + PromiscReq.fPromiscuous = true; + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE, 0, &PromiscReq.Hdr); + if (RT_SUCCESS(rc)) + RTPrintf("tstIntNet-1: interface in promiscuous mode\n"); + } + if (RT_SUCCESS(rc)) + { + /* + * Activate the interface. + */ + INTNETIFSETACTIVEREQ ActiveReq; + ActiveReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + ActiveReq.Hdr.cbReq = sizeof(ActiveReq); + ActiveReq.pSession = pSession; + ActiveReq.hIf = OpenReq.hIf; + ActiveReq.fActive = true; + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_SET_ACTIVE, 0, &ActiveReq.Hdr); + if (RT_SUCCESS(rc)) + { + /* + * Start the stop watch, init the pcap file. + */ + g_StartTS = RTTimeNanoTS(); + if (pFileRaw) + PcapStreamHdr(pFileRaw, g_StartTS); + + /* + * Do the transmit test first and so we can sniff for the response. + */ + if (fXmitTest) + doXmitTest(OpenReq.hIf, pSession, pBuf, &SrcMac, pFileRaw, pFileText); + + if (fPingTest) + doPingTest(OpenReq.hIf, pSession, pBuf, &SrcMac, pFileRaw, pFileText); + + /* + * Either enter sniffing mode or do a timeout thing. + */ + if (fSniffer) + { + doPacketSniffing(OpenReq.hIf, pSession, pBuf, cMillies, pFileRaw, pFileText, &SrcMac); + if ( fXmitTest + && !g_fDhcpReply) + { + RTPrintf("tstIntNet-1: Error! The DHCP server didn't reply... (Perhaps you don't have one?)\n"); + g_cErrors++; + } + + if ( fPingTest + && !g_fPingReply) + { + RTPrintf("tstIntNet-1: Error! No reply for ping request...\n"); + g_cErrors++; + } + } + else + RTThreadSleep(cMillies); + } + else + { + RTPrintf("tstIntNet-1: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE,) failed, rc=%Rrc\n", rc); + g_cErrors++; + } + } + else + { + RTPrintf("tstIntNet-1: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE,) failed, rc=%Rrc\n", rc); + g_cErrors++; + } + } + else + { + RTPrintf("tstIntNet-1: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS,) failed, rc=%Rrc\n", rc); + g_cErrors++; + } + } + else + { + RTPrintf("tstIntNet-1: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_OPEN,) failed, rc=%Rrc\n", rc); + g_cErrors++; + } + + SUPR3Term(false /*fForced*/); + + /* close open files */ + if (pFileRaw) + RTStrmClose(pFileRaw); + if (pFileText && pFileText != g_pStdErr && pFileText != g_pStdOut) + RTStrmClose(pFileText); + + /* + * Summary. + */ + if (!g_cErrors) + RTPrintf("tstIntNet-1: SUCCESS\n"); + else + RTPrintf("tstIntNet-1: FAILURE - %d errors\n", g_cErrors); + + return !!g_cErrors; +} + + +#if !defined(VBOX_WITH_HARDENING) || !defined(RT_OS_WINDOWS) +/** + * Main entry point. + */ +int main(int argc, char **argv, char **envp) +{ + return TrustedMain(argc, argv, envp); +} +#endif + diff --git a/src/VBox/Devices/Network/testcase/tstIntNetR0.cpp b/src/VBox/Devices/Network/testcase/tstIntNetR0.cpp new file mode 100644 index 00000000..5cb94894 --- /dev/null +++ b/src/VBox/Devices/Network/testcase/tstIntNetR0.cpp @@ -0,0 +1,867 @@ +/* $Id: tstIntNetR0.cpp $ */ +/** @file + * Internal networking - Usermode testcase for the kernel mode bits. + * + * This is a bit hackish as we're mixing context here, however it is + * very useful when making changes to the internal networking service. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IN_INTNET_TESTCASE +#define IN_INTNET_R3 +#include <VBox/cdefs.h> +#undef INTNETR0DECL +#define INTNETR0DECL INTNETR3DECL +#undef DECLR0CALLBACKMEMBER +#define DECLR0CALLBACKMEMBER(type, name, args) DECLR3CALLBACKMEMBER(type, name, args) +#include <VBox/types.h> +typedef void *MYPSUPDRVSESSION; +#define PSUPDRVSESSION MYPSUPDRVSESSION + +#include <VBox/intnet.h> +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/asm.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/mp.h> +#include <iprt/stream.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/test.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Security objectype. + */ +typedef enum SUPDRVOBJTYPE +{ + /** The usual invalid object. */ + SUPDRVOBJTYPE_INVALID = 0, + /** Internal network. */ + SUPDRVOBJTYPE_INTERNAL_NETWORK, + /** Internal network interface. */ + SUPDRVOBJTYPE_INTERNAL_NETWORK_INTERFACE, + /** The first invalid object type in this end. */ + SUPDRVOBJTYPE_END, + /** The usual 32-bit type size hack. */ + SUPDRVOBJTYPE_32_BIT_HACK = 0x7ffffff +} SUPDRVOBJTYPE; + +/** + * Object destructor callback. + * This is called for reference counted objectes when the count reaches 0. + * + * @param pvObj The object pointer. + * @param pvUser1 The first user argument. + * @param pvUser2 The second user argument. + */ +typedef DECLCALLBACKTYPE(void, FNSUPDRVDESTRUCTOR,(void *pvObj, void *pvUser1, void *pvUser2)); +/** Pointer to a FNSUPDRVDESTRUCTOR(). */ +typedef FNSUPDRVDESTRUCTOR *PFNSUPDRVDESTRUCTOR; + + +/** + * Dummy + */ +typedef struct OBJREF +{ + PFNSUPDRVDESTRUCTOR pfnDestructor; + void *pvUser1; + void *pvUser2; + uint32_t volatile cRefs; +} OBJREF, *POBJREF; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The test handle.*/ +static RTTEST g_hTest = NIL_RTTEST; +/** The size (in bytes) of the large transfer tests. */ +static uint32_t g_cbTransfer = _1M * 384; +/** Fake session handle. */ +const PSUPDRVSESSION g_pSession = (PSUPDRVSESSION)(uintptr_t)0xdeadface; + + +INTNETR3DECL(void *) SUPR0ObjRegister(PSUPDRVSESSION pSession, SUPDRVOBJTYPE enmType, + PFNSUPDRVDESTRUCTOR pfnDestructor, void *pvUser1, void *pvUser2) +{ + RTTEST_CHECK_RET(g_hTest, pSession == g_pSession, NULL); + POBJREF pRef = (POBJREF)RTTestGuardedAllocTail(g_hTest, sizeof(OBJREF)); + if (!pRef) + return NULL; + pRef->cRefs = 1; + pRef->pfnDestructor = pfnDestructor; + pRef->pvUser1 = pvUser1; + pRef->pvUser2 = pvUser2; + NOREF(enmType); + return pRef; +} + +INTNETR3DECL(int) SUPR0ObjAddRefEx(void *pvObj, PSUPDRVSESSION pSession, bool fNoBlocking) +{ + RTTEST_CHECK_RET(g_hTest, pSession == g_pSession, VERR_INVALID_PARAMETER); + POBJREF pRef = (POBJREF)pvObj; + ASMAtomicIncU32(&pRef->cRefs); + NOREF(fNoBlocking); + return VINF_SUCCESS; +} + +INTNETR3DECL(int) SUPR0ObjAddRef(void *pvObj, PSUPDRVSESSION pSession) +{ + return SUPR0ObjAddRefEx(pvObj, pSession, false); +} + +INTNETR3DECL(int) SUPR0ObjRelease(void *pvObj, PSUPDRVSESSION pSession) +{ + RTTEST_CHECK_RET(g_hTest, pSession == g_pSession, VERR_INVALID_PARAMETER); + POBJREF pRef = (POBJREF)pvObj; + if (!ASMAtomicDecU32(&pRef->cRefs)) + { + pRef->pfnDestructor(pRef, pRef->pvUser1, pRef->pvUser2); + RTTestGuardedFree(g_hTest, pRef); + return VINF_OBJECT_DESTROYED; + } + return VINF_SUCCESS; +} + +INTNETR3DECL(int) SUPR0ObjVerifyAccess(void *pvObj, PSUPDRVSESSION pSession, const char *pszObjName) +{ + RTTEST_CHECK_RET(g_hTest, pSession == g_pSession, VERR_INVALID_PARAMETER); + NOREF(pvObj); NOREF(pszObjName); + return VINF_SUCCESS; +} + +INTNETR3DECL(int) SUPR0MemAlloc(PSUPDRVSESSION pSession, uint32_t cb, PRTR0PTR ppvR0, PRTR3PTR ppvR3) +{ + RTTEST_CHECK_RET(g_hTest, pSession == g_pSession, VERR_INVALID_PARAMETER); + void *pv = RTTestGuardedAllocTail(g_hTest, cb); + if (!pv) + return VERR_NO_MEMORY; + *ppvR0 = (RTR0PTR)pv; + if (ppvR3) + *ppvR3 = pv; + return VINF_SUCCESS; +} + +INTNETR3DECL(int) SUPR0MemFree(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr) +{ + RTTEST_CHECK_RET(g_hTest, pSession == g_pSession, VERR_INVALID_PARAMETER); + RTTestGuardedFree(g_hTest, (void *)uPtr); + return VINF_SUCCESS; +} + +/* Fake non-existing ring-0 APIs. */ +#define RTThreadIsInInterrupt(hThread) false +#define RTThreadPreemptIsEnabled(hThread) true +#define RTMpCpuId() 0 + +/* No CLI/POPF, please. */ +#include <iprt/spinlock.h> +#undef RTSPINLOCK_FLAGS_INTERRUPT_SAFE +#define RTSPINLOCK_FLAGS_INTERRUPT_SAFE RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE + + +/* ugly but necessary for making R0 code compilable for R3. */ +#undef LOG_GROUP +#include "../SrvIntNetR0.cpp" + + +/** + * Sends the data @a pvBuf points to. + */ +static int tstIntNetSendBuf(PINTNETRINGBUF pRingBuf, INTNETIFHANDLE hIf, + PSUPDRVSESSION pSession, void const *pvBuf, size_t cbBuf) +{ + INTNETSG Sg; + IntNetSgInitTemp(&Sg, (void *)pvBuf, (uint32_t)cbBuf); + int rc = intnetR0RingWriteFrame(pRingBuf, &Sg, NULL); + if (RT_SUCCESS(rc)) + rc = IntNetR0IfSend(hIf, pSession); + return rc; +} + + +typedef struct MYARGS +{ + PINTNETBUF pBuf; + INTNETIFHANDLE hIf; + RTMAC Mac; + uint32_t cbFrame; + uint64_t u64Start; + uint64_t u64End; + uint32_t cbSent; + uint32_t cFramesSent; +} MYARGS, *PMYARGS; + + +/** + * Frame header used when testing. + */ +#pragma pack(1) +typedef struct MYFRAMEHDR +{ + RTMAC SrcMac; + RTMAC DstMac; + uint32_t iFrame; + uint32_t auEos[3]; +} MYFRAMEHDR; +#pragma pack() + +/** + * Send thread. + * This is constantly sending frames to the other interface. + */ +static DECLCALLBACK(int) SendThread(RTTHREAD hThreadSelf, void *pvArg) +{ + PMYARGS pArgs = (PMYARGS)pvArg; + int rc; + NOREF(hThreadSelf); + + /* + * Send g_cbTransfer of data. + */ + uint8_t abBuf[16384] = {0}; + MYFRAMEHDR *pHdr = (MYFRAMEHDR *)&abBuf[0]; + uint32_t iFrame = 0; + uint32_t cbSent = 0; + uint32_t cErrors = 0; + + pHdr->SrcMac = pArgs->Mac; + pHdr->DstMac = pArgs->Mac; + pHdr->DstMac.au16[2] = (pArgs->Mac.au16[2] + 1) % 2; + + pArgs->u64Start = RTTimeNanoTS(); + for (; cbSent < g_cbTransfer; iFrame++) + { + const unsigned cb = pArgs->cbFrame + ? pArgs->cbFrame + : iFrame % 1519 + sizeof(RTMAC) * 2 + sizeof(unsigned); + pHdr->iFrame = iFrame; + + INTNETSG Sg; + IntNetSgInitTemp(&Sg, abBuf, cb); + RTTEST_CHECK_RC_OK(g_hTest, rc = intnetR0RingWriteFrame(&pArgs->pBuf->Send, &Sg, NULL)); + if (RT_SUCCESS(rc)) + RTTEST_CHECK_RC_OK(g_hTest, rc = IntNetR0IfSend(pArgs->hIf, g_pSession)); + if (RT_FAILURE(rc) && ++cErrors > 64) + { + RTTestFailed(g_hTest, "Aborting xmit after >64 errors"); + break; + } + + cbSent += cb; + } + pArgs->cbSent = cbSent; + pArgs->cFramesSent = iFrame; + + /* + * Termination frames. + */ + pHdr->iFrame = 0xffffdead; + pHdr->auEos[0] = 0xffffdead; + pHdr->auEos[1] = 0xffffdead; + pHdr->auEos[2] = 0xffffdead; + for (unsigned c = 0; c < 20; c++) + { + RTTEST_CHECK_RC_OK(g_hTest, rc = tstIntNetSendBuf(&pArgs->pBuf->Send, pArgs->hIf, g_pSession, + abBuf, sizeof(RTMAC) * 2 + sizeof(unsigned) * 4)); + RTThreadSleep(1); + } + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "sender thread %.6Rhxs terminating.\n" + "iFrame=%u cb=%'u\n", + &pArgs->Mac, iFrame, cbSent); + return 0; +} + + +/** Ignore lost frames. It only makes things worse to bitch about it. */ +#define IGNORE_LOST_FRAMES + +/** + * Receive thread. + * This is reading stuff from the network. + */ +static DECLCALLBACK(int) ReceiveThread(RTTHREAD hThreadSelf, void *pvArg) +{ + uint32_t cbReceived = 0; + uint32_t cLostFrames = 0; + uint32_t iFrame = UINT32_MAX; + PMYARGS pArgs = (PMYARGS)pvArg; + NOREF(hThreadSelf); + + for (;;) + { + /* + * Read data. + */ + while (IntNetRingHasMoreToRead(&pArgs->pBuf->Recv)) + { + uint8_t abBuf[16384 + 1024]; + MYFRAMEHDR *pHdr = (MYFRAMEHDR *)&abBuf[0]; + uint32_t cb = IntNetRingReadAndSkipFrame(&pArgs->pBuf->Recv, abBuf); + + /* check for termination frame. */ + if ( pHdr->iFrame == 0xffffdead + && pHdr->auEos[0] == 0xffffdead + && pHdr->auEos[1] == 0xffffdead + && pHdr->auEos[2] == 0xffffdead) + { + pArgs->u64End = RTTimeNanoTS(); + RTThreadSleep(10); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "receiver thread %.6Rhxs terminating.\n" + " iFrame=%u cb=%'u c=%'u %'uKB/s %'ufps cLost=%'u \n", + &pArgs->Mac, iFrame, cbReceived, iFrame - cLostFrames, + (unsigned)((double)cbReceived * 1000000000.0 / 1024 / (double)(pArgs->u64End - pArgs->u64Start)), + (unsigned)((double)(iFrame - cLostFrames) * 1000000000.0 / (double)(pArgs->u64End - pArgs->u64Start)), + cLostFrames); + return VINF_SUCCESS; + } + + /* validate frame header */ + if ( pHdr->DstMac.au16[0] != pArgs->Mac.au16[0] + || pHdr->DstMac.au16[1] != pArgs->Mac.au16[1] + || pHdr->DstMac.au16[2] != pArgs->Mac.au16[2] + || pHdr->SrcMac.au16[0] != pArgs->Mac.au16[0] + || pHdr->SrcMac.au16[1] != pArgs->Mac.au16[1] + || pHdr->SrcMac.au16[2] != (pArgs->Mac.au16[2] + 1) % 2) + { + RTTestFailed(g_hTest, "receiver thread %.6Rhxs received frame header: %.16Rhxs\n", &pArgs->Mac, abBuf); + } + + /* frame stuff and stats. */ + int32_t off = pHdr->iFrame - (iFrame + 1); + if (off) + { + if (off > 0) + { +#ifndef IGNORE_LOST_FRAMES + RTTestFailed(g_hTest, "receiver thread %.6Rhxs: iFrame=%#x *puFrame=%#x off=%d\n", + &pArgs->Mac, iFrame, pHdr->iFrame, off); +#endif + cLostFrames += off; + } + else + { + cLostFrames++; + RTTestFailed(g_hTest, "receiver thread %.6Rhxs: iFrame=%#x *puFrame=%#x off=%d\n", + &pArgs->Mac, iFrame, pHdr->iFrame, off); + } + } + iFrame = pHdr->iFrame; + cbReceived += cb; + } + + /* + * Wait for data. + */ + int rc = IntNetR0IfWait(pArgs->hIf, g_pSession, RT_INDEFINITE_WAIT); + switch (rc) + { + case VERR_INTERRUPTED: + case VINF_SUCCESS: + break; + case VERR_SEM_DESTROYED: + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "receiver thread %.6Rhxs terminating. iFrame=%u cb=%'u c=%'u cLost=%'u\n", + &pArgs->Mac, iFrame, cbReceived, iFrame - cLostFrames, cLostFrames); + return VINF_SUCCESS; + + default: + RTTestFailed(g_hTest, "receiver thread %.6Rhxs got odd return value %Rrc! iFrame=%u cb=%'u c=%'u cLost=%'u\n", + &pArgs->Mac, rc, iFrame, cbReceived, iFrame - cLostFrames, cLostFrames); + return rc; + } + + } +} + + +/** + * Drains the interface buffer before starting a new bi-directional run. + * + * We may have termination frames from previous runs pending in the buffer. + */ +static void tstDrainInterfaceBuffer(PMYARGS pArgs) +{ + uint8_t abBuf[16384 + 1024]; + while (IntNetRingHasMoreToRead(&pArgs->pBuf->Recv)) + IntNetRingReadAndSkipFrame(&pArgs->pBuf->Recv, abBuf); +} + + +/** + * Test state. + */ +typedef struct TSTSTATE +{ + PINTNETBUF pBuf0; + INTNETIFHANDLE hIf0; + + PINTNETBUF pBuf1; + INTNETIFHANDLE hIf1; +} TSTSTATE; +typedef TSTSTATE *PTSTSTATE; + + +/** + * Open two internal network interfaces. + * + * @returns IPRT status of the first failure. + * @param pThis The test instance. + */ +static int tstOpenInterfaces(PTSTSTATE pThis, const char *pszNetwork, uint32_t cbSend, uint32_t cbRecv) +{ + pThis->hIf0 = INTNET_HANDLE_INVALID; + RTTESTI_CHECK_RC_OK_RET(IntNetR0Open(g_pSession, pszNetwork, kIntNetTrunkType_None, "", 0/*fFlags*/, cbSend, cbRecv, + NULL /*pfnRecvAvail*/, NULL /*pvUser*/, &pThis->hIf0), rcCheck); + RTTESTI_CHECK_RET(pThis->hIf0 != INTNET_HANDLE_INVALID, VERR_INTERNAL_ERROR); + RTTESTI_CHECK_RC_RET(IntNetR0IfGetBufferPtrs(pThis->hIf0, g_pSession, &pThis->pBuf0, NULL), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RET(pThis->pBuf0, VERR_INTERNAL_ERROR); + + + pThis->hIf1 = INTNET_HANDLE_INVALID; + RTTESTI_CHECK_RC_OK_RET(IntNetR0Open(g_pSession, pszNetwork, kIntNetTrunkType_None, "", 0/*fFlags*/, cbSend, cbRecv, + NULL /*pfnRecvAvail*/, NULL /*pvUser*/, &pThis->hIf1), rcCheck); + RTTESTI_CHECK_RET(pThis->hIf1 != INTNET_HANDLE_INVALID, VERR_INTERNAL_ERROR); + RTTESTI_CHECK_RC_RET(IntNetR0IfGetBufferPtrs(pThis->hIf1, g_pSession, &pThis->pBuf1, NULL), VINF_SUCCESS, rcCheck); + RTTESTI_CHECK_RET(pThis->pBuf1, VERR_INTERNAL_ERROR); + + return VINF_SUCCESS; +} + +/** + * Close the interfaces. + * + * @param pThis The test instance. + */ +static void tstCloseInterfaces(PTSTSTATE pThis) +{ + int rc; + RTTESTI_CHECK_RC_OK(rc = IntNetR0IfClose(pThis->hIf0, g_pSession)); + if (RT_SUCCESS(rc)) + { + pThis->hIf0 = INTNET_HANDLE_INVALID; + pThis->pBuf0 = NULL; + } + + RTTESTI_CHECK_RC_OK(rc = IntNetR0IfClose(pThis->hIf1, g_pSession)); + if (RT_SUCCESS(rc)) + { + pThis->hIf1 = INTNET_HANDLE_INVALID; + pThis->pBuf1 = NULL; + } + + /* The network should be dead now. */ + RTTESTI_CHECK(IntNetR0GetNetworkCount() == 0); +} + +/** + * Do the bi-directional transfer test. + */ +static void tstBidirectionalTransfer(PTSTSTATE pThis, uint32_t cbFrame) +{ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "-------------------------------------------------------------\n"); + + /* + * Reset statistics. + */ + pThis->pBuf0->cStatYieldsOk.c = 0; + pThis->pBuf0->cStatYieldsNok.c = 0; + pThis->pBuf0->cStatLost.c = 0; + pThis->pBuf0->cStatBadFrames.c = 0; + pThis->pBuf0->Recv.cStatFrames.c = 0; + pThis->pBuf0->Recv.cbStatWritten.c = 0; + pThis->pBuf0->Recv.cOverflows.c = 0; + pThis->pBuf0->Send.cStatFrames.c = 0; + pThis->pBuf0->Send.cbStatWritten.c = 0; + pThis->pBuf0->Send.cOverflows.c = 0; + pThis->pBuf1->cStatYieldsOk.c = 0; + pThis->pBuf1->cStatYieldsNok.c = 0; + pThis->pBuf1->cStatLost.c = 0; + pThis->pBuf1->cStatBadFrames.c = 0; + pThis->pBuf1->Recv.cStatFrames.c = 0; + pThis->pBuf1->Recv.cbStatWritten.c = 0; + pThis->pBuf1->Recv.cOverflows.c = 0; + pThis->pBuf1->Send.cStatFrames.c = 0; + pThis->pBuf1->Send.cbStatWritten.c = 0; + pThis->pBuf1->Send.cOverflows.c = 0; + + /* + * Do the benchmarking. + */ + MYARGS Args0; + RT_ZERO(Args0); + Args0.hIf = pThis->hIf0; + Args0.pBuf = pThis->pBuf0; + Args0.Mac.au16[0] = 0x8086; + Args0.Mac.au16[1] = 0; + Args0.Mac.au16[2] = 0; + Args0.cbFrame = cbFrame; + tstDrainInterfaceBuffer(&Args0); + //RTTESTI_CHECK_RC_OK(IntNetR0IfSetMacAddress(pThis->hIf0, g_pSession, &Args0.Mac)); + + MYARGS Args1; + RT_ZERO(Args1); + Args1.hIf = pThis->hIf1; + Args1.pBuf = pThis->pBuf1; + Args1.Mac.au16[0] = 0x8086; + Args1.Mac.au16[1] = 0; + Args1.Mac.au16[2] = 1; + Args1.cbFrame = cbFrame; + tstDrainInterfaceBuffer(&Args1); + //RTTESTI_CHECK_RC_OK(IntNetR0IfSetMacAddress(pThis->hIf1, g_pSession, &Args1.Mac)); + + RTTHREAD ThreadRecv0 = NIL_RTTHREAD; + RTTHREAD ThreadRecv1 = NIL_RTTHREAD; + RTTHREAD ThreadSend0 = NIL_RTTHREAD; + RTTHREAD ThreadSend1 = NIL_RTTHREAD; + RTTESTI_CHECK_RC_OK_RETV(RTThreadCreate(&ThreadRecv0, ReceiveThread, &Args0, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "RECV0")); + RTTESTI_CHECK_RC_OK_RETV(RTThreadCreate(&ThreadRecv1, ReceiveThread, &Args1, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "RECV1")); + RTTESTI_CHECK_RC_OK_RETV(RTThreadCreate(&ThreadSend0, SendThread, &Args0, 0, RTTHREADTYPE_EMULATION, RTTHREADFLAGS_WAITABLE, "SEND0")); + RTTESTI_CHECK_RC_OK_RETV(RTThreadCreate(&ThreadSend1, SendThread, &Args1, 0, RTTHREADTYPE_EMULATION, RTTHREADFLAGS_WAITABLE, "SEND1")); + + int rc2 = VINF_SUCCESS; + int rc; + RTTESTI_CHECK_RC_OK(rc = RTThreadWait(ThreadSend0, RT_MS_5MIN, &rc2)); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC_OK(rc2); + ThreadSend0 = NIL_RTTHREAD; + RTTESTI_CHECK_RC_OK(rc = RTThreadWait(ThreadSend1, RT_MS_5MIN, RT_SUCCESS(rc2) ? &rc2 : NULL)); + if (RT_SUCCESS(rc)) + { + ThreadSend1 = NIL_RTTHREAD; + RTTESTI_CHECK_RC_OK(rc2); + } + } + if (RTTestErrorCount(g_hTest) == 0) + { + /* + * Wait a bit for the receivers to finish up. + */ + unsigned cYields = 100000; + while ( ( IntNetRingHasMoreToRead(&pThis->pBuf0->Recv) + || IntNetRingHasMoreToRead(&pThis->pBuf1->Recv)) + && cYields-- > 0) + RTThreadYield(); + + /* + * Wait for the threads to finish up... + */ + RTTESTI_CHECK_RC_OK(rc = RTThreadWait(ThreadRecv0, RT_MS_5SEC, &rc2)); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC_OK(rc2); + ThreadRecv0 = NIL_RTTHREAD; + } + + RTTESTI_CHECK_RC_OK(rc = RTThreadWait(ThreadRecv1, RT_MS_5MIN, &rc2)); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC_OK(rc2); + ThreadRecv1 = NIL_RTTHREAD; + } + + /* + * Report the results. + */ + uint64_t cNsElapsed = RT_MAX(Args0.u64End, Args1.u64End) - RT_MIN(Args0.u64Start, Args1.u64Start); + uint64_t cbSent = (uint64_t)Args0.cbSent + Args1.cbSent; + uint64_t cKbps = (uint64_t)((double)(cbSent / 1024) / ((double)cNsElapsed / 1000000000.0)); + uint64_t cFrames = (uint64_t)Args0.cFramesSent + Args1.cFramesSent; + uint64_t cFps = (uint64_t)((double)cFrames / ((double)cNsElapsed / 1000000000.0)); + RTTestValue(g_hTest, "frame size", cbFrame, RTTESTUNIT_BYTES); + RTTestValue(g_hTest, "xmit time", cNsElapsed, RTTESTUNIT_NS); + RTTestValue(g_hTest, "bytes sent", cbSent, RTTESTUNIT_BYTES); + RTTestValue(g_hTest, "speed", cKbps, RTTESTUNIT_KILOBYTES_PER_SEC); + RTTestValue(g_hTest, "frames sent", cFrames, RTTESTUNIT_FRAMES); + RTTestValue(g_hTest, "fps", cFps, RTTESTUNIT_FRAMES_PER_SEC); + RTTestValue(g_hTest, "overflows", + pThis->pBuf0->Send.cOverflows.c + pThis->pBuf1->Send.cOverflows.c, RTTESTUNIT_OCCURRENCES); + + } + + /* + * Give them a chance to complete... + */ + RTThreadWait(ThreadRecv0, RT_MS_5MIN, NULL); + RTThreadWait(ThreadRecv1, RT_MS_5MIN, NULL); + RTThreadWait(ThreadSend0, RT_MS_5MIN, NULL); + RTThreadWait(ThreadSend1, RT_MS_5MIN, NULL); + + + /* + * Display statistics. + */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Buf0: Yields-OK=%llu Yields-NOK=%llu Lost=%llu Bad=%llu\n", + pThis->pBuf0->cStatYieldsOk.c, + pThis->pBuf0->cStatYieldsNok.c, + pThis->pBuf0->cStatLost.c, + pThis->pBuf0->cStatBadFrames.c); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Buf0.Recv: Frames=%llu Bytes=%llu Overflows=%llu\n", + pThis->pBuf0->Recv.cStatFrames.c, + pThis->pBuf0->Recv.cbStatWritten.c, + pThis->pBuf0->Recv.cOverflows.c); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Buf0.Send: Frames=%llu Bytes=%llu Overflows=%llu\n", + pThis->pBuf0->Send.cStatFrames.c, + pThis->pBuf0->Send.cbStatWritten.c, + pThis->pBuf0->Send.cOverflows.c); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Buf1: Yields-OK=%llu Yields-NOK=%llu Lost=%llu Bad=%llu\n", + pThis->pBuf1->cStatYieldsOk.c, + pThis->pBuf1->cStatYieldsNok.c, + pThis->pBuf1->cStatLost.c, + pThis->pBuf1->cStatBadFrames.c); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Buf1.Recv: Frames=%llu Bytes=%llu Overflows=%llu\n", + pThis->pBuf1->Recv.cStatFrames.c, + pThis->pBuf1->Recv.cbStatWritten.c, + pThis->pBuf1->Recv.cOverflows.c); + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, + "Buf1.Send: Frames=%llu Bytes=%llu Overflows=%llu\n", + pThis->pBuf1->Send.cStatFrames.c, + pThis->pBuf1->Send.cbStatWritten.c, + pThis->pBuf1->Send.cOverflows.c); + +} + +/** + * Performs a simple broadcast test. + * + * @param pThis The test instance. + * @param fHeadGuard Whether to use a head or tail guard. + */ +static void doBroadcastTest(PTSTSTATE pThis, bool fHeadGuard) +{ + static uint16_t const s_au16Frame[7] = { /* dst:*/ 0xffff, 0xffff, 0xffff, /*src:*/0x8086, 0, 0, 0x0800 }; + + RTTESTI_CHECK_RC_RETV(tstIntNetSendBuf(&pThis->pBuf0->Send, pThis->hIf0, + g_pSession, &s_au16Frame, sizeof(s_au16Frame)), + VINF_SUCCESS); + + /* No echo, please */ + RTTESTI_CHECK_RC_RETV(IntNetR0IfWait(pThis->hIf0, g_pSession, 1), VERR_TIMEOUT); + + /* The other interface should see it though. But Wait should only return once, thank you. */ + RTTESTI_CHECK_RC_RETV(IntNetR0IfWait(pThis->hIf1, g_pSession, 1), VINF_SUCCESS); + RTTESTI_CHECK_RC_RETV(IntNetR0IfWait(pThis->hIf1, g_pSession, 0), VERR_TIMEOUT); + + /* Receive the data. */ + const unsigned cbExpect = RT_ALIGN(sizeof(s_au16Frame) + sizeof(INTNETHDR), sizeof(INTNETHDR)); + RTTESTI_CHECK_MSG(IntNetRingGetReadable(&pThis->pBuf1->Recv) == cbExpect, + ("%#x vs. %#x\n", IntNetRingGetReadable(&pThis->pBuf1->Recv), cbExpect)); + + void *pvBuf; + RTTESTI_CHECK_RC_OK_RETV(RTTestGuardedAlloc(g_hTest, sizeof(s_au16Frame), 1, fHeadGuard, &pvBuf)); + uint32_t cb; + RTTESTI_CHECK_MSG_RETV((cb = IntNetRingReadAndSkipFrame(&pThis->pBuf1->Recv, pvBuf)) == sizeof(s_au16Frame), + ("%#x vs. %#x\n", cb, sizeof(s_au16Frame))); + + if (memcmp(pvBuf, &s_au16Frame, sizeof(s_au16Frame))) + RTTestIFailed("Got invalid data!\n" + "received: %.*Rhxs\n" + "expected: %.*Rhxs\n", + cb, pvBuf, sizeof(s_au16Frame), &s_au16Frame); +} + +/** + * Performs a simple unicast test. + * + * @param pThis The test instance. + * @param fHeadGuard Whether to use a head or tail guard. + */ +static void doUnicastTest(PTSTSTATE pThis, bool fHeadGuard) +{ + static uint16_t const s_au16Frame[7] = { /* dst:*/ 0x8086, 0, 0, /*src:*/0x8086, 0, 1, 0x0800 }; + + RTTESTI_CHECK_RC_RETV(tstIntNetSendBuf(&pThis->pBuf1->Send, pThis->hIf1, + g_pSession, s_au16Frame, sizeof(s_au16Frame)), + VINF_SUCCESS); + + /* No echo, please */ + RTTESTI_CHECK_RC_RETV(IntNetR0IfWait(pThis->hIf1, g_pSession, 1), VERR_TIMEOUT); + + /* The other interface should see it though. But Wait should only return once, thank you. */ + RTTESTI_CHECK_RC_RETV(IntNetR0IfWait(pThis->hIf0, g_pSession, 1), VINF_SUCCESS); + RTTESTI_CHECK_RC_RETV(IntNetR0IfWait(pThis->hIf0, g_pSession, 0), VERR_TIMEOUT); + + /* Receive the data. */ + const unsigned cbExpect = RT_ALIGN(sizeof(s_au16Frame) + sizeof(INTNETHDR), sizeof(INTNETHDR)); + RTTESTI_CHECK_MSG(IntNetRingGetReadable(&pThis->pBuf0->Recv) == cbExpect, + ("%#x vs. %#x\n", IntNetRingGetReadable(&pThis->pBuf0->Recv), cbExpect)); + + void *pvBuf; + RTTESTI_CHECK_RC_OK_RETV(RTTestGuardedAlloc(g_hTest, sizeof(s_au16Frame), 1, fHeadGuard, &pvBuf)); + uint32_t cb; + RTTESTI_CHECK_MSG_RETV((cb = IntNetRingReadAndSkipFrame(&pThis->pBuf0->Recv, pvBuf)) == sizeof(s_au16Frame), + ("%#x vs. %#x\n", cb, sizeof(s_au16Frame))); + + if (memcmp(pvBuf, &s_au16Frame, sizeof(s_au16Frame))) + RTTestIFailed("Got invalid data!\n" + "received: %.*Rhxs\n" + "expected: %.*Rhxs\n", + cb, pvBuf, sizeof(s_au16Frame), s_au16Frame); +} + +static void doTest(PTSTSTATE pThis, uint32_t cbRecv, uint32_t cbSend) +{ + + /* + * Create an INTNET instance. + */ + RTTestISub("IntNetR0Init"); + RTTESTI_CHECK_RC_RETV(IntNetR0Init(), VINF_SUCCESS); + + /* + * Create two interfaces and activate them. + */ + RTTestISub("Network creation"); + int rc = tstOpenInterfaces(pThis, "test", cbSend, cbRecv); + if (RT_FAILURE(rc)) + return; + RTTESTI_CHECK_RC(IntNetR0IfSetActive(pThis->hIf0, g_pSession, true), VINF_SUCCESS); + RTTESTI_CHECK_RC(IntNetR0IfSetActive(pThis->hIf1, g_pSession, true), VINF_SUCCESS); + + /* + * Test basic waiting. + */ + RTTestISub("IntNetR0IfWait"); + RTTESTI_CHECK_RC(IntNetR0IfWait(pThis->hIf0, g_pSession, 1), VERR_TIMEOUT); + RTTESTI_CHECK_RC(IntNetR0IfWait(pThis->hIf0, g_pSession, 0), VERR_TIMEOUT); + RTTESTI_CHECK_RC(IntNetR0IfWait(pThis->hIf1, g_pSession, 1), VERR_TIMEOUT); + RTTESTI_CHECK_RC(IntNetR0IfWait(pThis->hIf1, g_pSession, 0), VERR_TIMEOUT); + + /* + * Broadcast send and receive. + * (This establishes the MAC address of the 1st interface.) + */ + RTTestISub("Broadcast"); + doBroadcastTest(pThis, false /*fHeadGuard*/); + doBroadcastTest(pThis, true /*fHeadGuard*/); + + /* + * Unicast send and receive. + * (This establishes the MAC address of the 2nd interface.) + */ + RTTestISub("Unicast"); + doUnicastTest(pThis, false /*fHeadGuard*/); + doUnicastTest(pThis, true /*fHeadGuard*/); + + /* + * Do the big bi-directional transfer test if the basics worked out. + */ + if (!RTTestIErrorCount()) + { + RTTestISubF("bi-dir benchmark, xbuf=%u rbuf=%u xfer=%u", + pThis->pBuf0->cbSend, pThis->pBuf0->cbRecv, g_cbTransfer); + tstBidirectionalTransfer(pThis, 256); + + /* Only doing up to half the xmit buffer size as it is easy to get into a + bad frame position from a previous run and run into overflow issues. */ + /** @todo fix the code so it skips to a more optimal buffer position? */ + for (uint32_t cbFrame = 64; cbFrame < cbSend / 2 - 64; cbFrame += 16) + { + RTTestISubF("bi-dir benchmark, xbuf=%u rbuf=%u xmit=%u frm=%u", + pThis->pBuf0->cbSend, pThis->pBuf0->cbRecv, g_cbTransfer, cbFrame); + tstBidirectionalTransfer(pThis, cbFrame); + } + } + + /* + * Destroy the service. + */ + tstCloseInterfaces(pThis); + IntNetR0Term(); +} + + +int main(int argc, char **argv) +{ + int rc = RTTestInitAndCreate("tstIntNetR0", &g_hTest); + if (rc) + return rc; + + /* + * Parse the arguments. + */ + static RTGETOPTDEF const s_aOptions[] = + { + { "--recv-buffer", 'r', RTGETOPT_REQ_UINT32 }, + { "--send-buffer", 's', RTGETOPT_REQ_UINT32 }, + { "--transfer-size", 'l', RTGETOPT_REQ_UINT32 }, + }; + + uint32_t cbSend = 1536*2 + 4; + uint32_t cbRecv = 0x8000; + + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &Value))) + switch (ch) + { + case 'l': + g_cbTransfer = Value.u32; + break; + + case 'r': + cbRecv = Value.u32; + break; + + case 's': + cbSend = Value.u32; + break; + + default: + return RTGetOptPrintError(ch, &Value); + } + + /* + * Do the testing and report summary. + */ + TSTSTATE This; + RT_ZERO(This); + doTest(&This, cbRecv, cbSend); + + return RTTestSummaryAndDestroy(g_hTest); +} + |