From d655ce4748f09e2bdbc1c5008b576b6615ce9721 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Jul 2025 01:21:19 +0000 Subject: [PATCH 1/2] Checkpoint before follow-up message --- .github/workflows/ci.yml | 142 +++++++++++++++++++++++++++++++ .gitignore | 2 +- .travis.yml | 6 -- CMakeLists.txt | 146 ++++++++++++++++++++++++++++++++ README.md | 117 ++++++++++++++++++++----- SConstruct | 38 --------- flow_db.c | 11 ++- pkt2flow.c | 30 +++++-- tests/test_flow_db.cpp | 135 +++++++++++++++++++++++++++++ tests/test_main.cpp | 18 ++++ tests/test_utilities.cpp | 178 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 753 insertions(+), 70 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml create mode 100644 CMakeLists.txt delete mode 100755 SConstruct create mode 100644 tests/test_flow_db.cpp create mode 100644 tests/test_main.cpp create mode 100644 tests/test_utilities.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8866923 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,142 @@ +name: CI + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +jobs: + build-and-test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + build_type: [Release, Debug] + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y \ + libpcap-dev \ + libgoogle-glog-dev \ + libgtest-dev \ + cmake \ + build-essential + + - name: Install dependencies (macOS) + if: matrix.os == 'macos-latest' + run: | + brew update + brew install \ + libpcap \ + glog \ + googletest \ + cmake + + - name: Configure CMake + run: | + cmake -B build \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DBUILD_TESTS=ON + + - name: Build + run: cmake --build build --config ${{ matrix.build_type }} -j $(nproc 2>/dev/null || sysctl -n hw.ncpu) + + - name: Run tests + working-directory: build + run: ctest --output-on-failure --verbose + + - name: Test installation + run: | + sudo cmake --install build + which pkt2flow + pkt2flow -h || true # Show help (exits with non-zero) + + static-analysis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libpcap-dev \ + libgoogle-glog-dev \ + libgtest-dev \ + cmake \ + build-essential \ + cppcheck \ + clang-format + + - name: Run cppcheck + run: | + cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem \ + --suppress=unusedFunction \ + --inline-suppr \ + *.c *.h + + - name: Check code formatting + run: | + clang-format --dry-run --Werror *.c *.h + + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y doxygen graphviz + + - name: Generate documentation + run: | + doxygen --version + # Documentation generation can be added when Doxyfile is created + + cross-compile: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, aarch64] + steps: + - uses: actions/checkout@v4 + + - name: Install cross-compilation tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: Install dependencies + run: | + sudo apt-get install -y \ + libpcap-dev \ + libgoogle-glog-dev \ + cmake \ + build-essential + + - name: Configure CMake (x86_64) + if: matrix.target == 'x86_64' + run: | + cmake -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTS=OFF + + - name: Configure CMake (aarch64) + if: matrix.target == 'aarch64' + run: | + cmake -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTS=OFF \ + -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \ + -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ + + - name: Build + run: cmake --build build -j $(nproc) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 64226fe..0b22209 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -.sconsign.dblite +THIS SHOULD BE A LINTER ERROR.sconsign.dblite pkt2flow *.o diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a363a64..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: c - -before_script: - - sudo apt-get install libpcap-dev - -script: scons diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..925d4ef --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,146 @@ +cmake_minimum_required(VERSION 3.16) + +project(pkt2flow + VERSION 1.2.0 + DESCRIPTION "A simple utility to classify packets into flows" + LANGUAGES C CXX) + +# Set C and C++ standards +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Enable compile commands for better IDE support +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Set default build type to Release if not specified +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Compiler flags +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") +set(CMAKE_C_FLAGS_DEBUG "-g -O0") +set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG") + +# Platform-specific definitions +if(APPLE) + add_definitions(-Ddarwin) +endif() + +# Find required packages +find_package(PkgConfig REQUIRED) + +# Find libpcap +pkg_check_modules(PCAP REQUIRED libpcap) +if(NOT PCAP_FOUND) + find_library(PCAP_LIBRARIES pcap) + if(NOT PCAP_LIBRARIES) + message(FATAL_ERROR "libpcap not found") + endif() + set(PCAP_INCLUDE_DIRS "") +endif() + +# Find glog +find_package(glog REQUIRED) + +# Source files +set(PKT2FLOW_SOURCES + pkt2flow.c + flow_db.c + utilities.c +) + +# Create the main executable +add_executable(pkt2flow ${PKT2FLOW_SOURCES}) + +# Link libraries +target_link_libraries(pkt2flow + ${PCAP_LIBRARIES} + glog::glog +) + +# Include directories +target_include_directories(pkt2flow PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${PCAP_INCLUDE_DIRS} +) + +# Set compile definitions +target_compile_definitions(pkt2flow PRIVATE _GNU_SOURCE) + +# Installation +include(GNUInstallDirs) +install(TARGETS pkt2flow + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +# Enable testing +enable_testing() + +# Build tests if requested +option(BUILD_TESTS "Build unit tests" ON) +if(BUILD_TESTS) + # Find GoogleTest + find_package(GTest REQUIRED) + + # Test source files + set(TEST_SOURCES + tests/test_flow_db.cpp + tests/test_utilities.cpp + tests/test_main.cpp + ) + + # Create test executable + add_executable(pkt2flow_tests ${TEST_SOURCES}) + + # Link test libraries + target_link_libraries(pkt2flow_tests + ${PCAP_LIBRARIES} + glog::glog + GTest::gtest + GTest::gtest_main + ) + + # Include directories for tests + target_include_directories(pkt2flow_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${PCAP_INCLUDE_DIRS} + ) + + # Set compile definitions for tests + target_compile_definitions(pkt2flow_tests PRIVATE _GNU_SOURCE) + + # Add test + add_test(NAME pkt2flow_unit_tests COMMAND pkt2flow_tests) + + # Create a library from the source files for testing + add_library(pkt2flow_lib STATIC + flow_db.c + utilities.c + ) + + target_include_directories(pkt2flow_lib PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${PCAP_INCLUDE_DIRS} + ) + + target_link_libraries(pkt2flow_lib + ${PCAP_LIBRARIES} + glog::glog + ) + + target_compile_definitions(pkt2flow_lib PRIVATE _GNU_SOURCE) + + # Link the library to tests + target_link_libraries(pkt2flow_tests pkt2flow_lib) +endif() + +# CPack configuration for packaging +include(CPack) +set(CPACK_PACKAGE_NAME "pkt2flow") +set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}") +set(CPACK_PACKAGE_VENDOR "chenxm") +set(CPACK_PACKAGE_CONTACT "chenxm35@gmail.com") \ No newline at end of file diff --git a/README.md b/README.md index 2940b6c..ace6938 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ pkt2flow by chenxm, Shanghai Jiao Tong Univ. chenxm35@gmail.com -2012-2019 +2012-2024 **©MIT LICENSED** @@ -24,43 +24,90 @@ file named with 4-tuple and the timestamp of the first packet of the flow. The p saved in the order as read from the source. Any further processing like TCP resembling is not performed. The flow timeout is considered as 30 minutes which can be changed in pkt2flow.h. +## Features + +- **Cross-platform**: Supports both Linux and macOS +- **Modern build system**: Uses CMake instead of SCons +- **Structured logging**: Integrated with Google glog for better debugging +- **Unit testing**: Comprehensive test suite using Google Test +- **CI/CD**: Automated testing with GitHub Actions +- **Static analysis**: Code quality checks with cppcheck How to compile ---------- +This program now uses CMake as the build system. You can follow these steps to compile: -This program is structured and compiled with a tool called SCons (http://www.scons.org/). -You can follow simple steps to make a compile (e.g. Ubuntu): +### Prerequisites -1. Make sure you have library `libpcap` in your system. +**Ubuntu/Debian:** ```bash -sudo apt install -y libpcap-dev +sudo apt-get update +sudo apt-get install -y \ + libpcap-dev \ + libgoogle-glog-dev \ + libgtest-dev \ + cmake \ + build-essential ``` -2. Install "Scons" that can be downloaded from its official website given above. +**macOS:** ```bash -sudo apt install -y scons +brew install \ + libpcap \ + glog \ + googletest \ + cmake ``` -3. Get source code and run `scons` under the project folder: +### Building + +1. Clone the repository: ```bash git clone https://github.com/caesar0301/pkt2flow.git cd pkt2flow -scons # You got binary pkt2flow -```` +``` -How to install (optional) ----------- +2. Configure and build: +```bash +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) +``` -You can optionally let scons automatically handle the installation for you by -providing an installation prefix, e.g.: +3. Run tests (optional): +```bash +ctest --verbose +``` - $ PREFIX=/usr/local - $ scons --prefix=$PREFIX install +4. Install (optional): +```bash +sudo make install +``` + +### Build Options + +- `BUILD_TESTS`: Enable/disable unit tests (default: ON) +- `CMAKE_BUILD_TYPE`: Set build type (Debug, Release, RelWithDebInfo, MinSizeRel) + +Example: +```bash +cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=OFF +``` -This will build pkt2flow and install the binary to /usr/local/bin/pkt2flow. -Depending on where you want to install it, you might need to use sudo or -become the appropriate user. +## Logging + +The application now uses Google glog for structured logging. Logs are written to: +- Console (stderr) for important messages +- Log files in `./logs/` directory for detailed debugging + +You can control logging verbosity with environment variables: +```bash +export GLOG_v=2 # Increase verbosity +export GLOG_log_dir=/custom/log/path +./pkt2flow input.pcap +``` Usage -------- @@ -75,6 +122,38 @@ Usage: ./pkt2flow [-huvx] [-o outdir] pcapfile -o (o)utput directory ``` +## Development + +### Running Tests + +```bash +# Run all tests +cd build && ctest + +# Run specific test +./build/pkt2flow_tests --gtest_filter="FlowDbTest.*" +``` + +### Code Quality + +The project includes static analysis tools: + +```bash +# Run cppcheck +cppcheck --enable=all *.c *.h + +# Check formatting +clang-format --dry-run *.c *.h +``` + +### Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes with appropriate tests +4. Ensure all tests pass and code follows the style guide +5. Submit a pull request + Contributors -------- diff --git a/SConstruct b/SConstruct deleted file mode 100755 index 80744aa..0000000 --- a/SConstruct +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python - -import sys -env = Environment(CCFLAGS='-Wall -g', CPPFLAGS='-D_GNU_SOURCE') - -AddOption('--prefix', - dest='prefix', - nargs=1, type='string', - action='store', - metavar='DIR', - help='installation prefix') -env = Environment(PREFIX = GetOption('prefix')) - -idir_prefix = '$PREFIX' -idir_bin = '$PREFIX/bin' - -Export('env idir_prefix idir_bin') - -platform = sys.platform -lib_path = ['/usr/local/lib', '/usr/lib'] -libs = Glob('./*.a') + ['pcap'] -cpp_path=['.'] - -if platform == 'darwin': - env.Append(CPPFLAGS=['-Ddarwin']) - -# Compile the programs -pkt2flow = env.Program(target = './pkt2flow', - source = Glob('./*.c'), - LIBPATH = lib_path, - LIBS = libs, - CPPPATH = cpp_path) - -# install the program -env.Install(dir = idir_bin, source = pkt2flow) - -# create an install alias -env.Alias('install', idir_prefix) diff --git a/flow_db.c b/flow_db.c index 2fce5c7..6540772 100644 --- a/flow_db.c +++ b/flow_db.c @@ -37,12 +37,14 @@ #include #include #include +#include #include "pkt2flow.h" struct ip_pair *pairs [HASH_TBL_SIZE]; void init_hash_table(void) { + VLOG(1) << "Initializing hash table with size " << HASH_TBL_SIZE; memset(pairs, 0, sizeof(struct ip_pair *) * HASH_TBL_SIZE); } @@ -50,16 +52,20 @@ void free_hash_table(void) { size_t b; struct ip_pair *curp; + int total_pairs = 0; + VLOG(1) << "Freeing hash table"; for (b = 0; b < HASH_TBL_SIZE; b++) { while (pairs[b]) { curp = pairs[b]; pairs[b] = pairs[b]->next; reset_pdf(&curp->pdf); free(curp); + total_pairs++; } } + LOG(INFO) << "Freed " << total_pairs << " flow pairs from hash table"; init_hash_table(); } @@ -186,7 +192,7 @@ struct ip_pair *register_ip_pair(struct af_6tuple af_6tuple) newp = (struct ip_pair *)malloc(sizeof(struct ip_pair)); if (!newp) { - fprintf(stderr, "not enough memory to allocate another IP pair\n"); + LOG(FATAL) << "Not enough memory to allocate another IP pair"; exit(1); } @@ -196,5 +202,8 @@ struct ip_pair *register_ip_pair(struct af_6tuple af_6tuple) pairs [hash] = newp; reset_pdf((struct pkt_dump_file *) & (newp->pdf)); + VLOG(2) << "Registered new IP pair in hash bucket " << hash + << " for protocol " << af_6tuple.protocol; + return newp; } diff --git a/pkt2flow.c b/pkt2flow.c index 63573ec..53e22ce 100644 --- a/pkt2flow.c +++ b/pkt2flow.c @@ -49,6 +49,7 @@ #include #include #include +#include #include "pkt2flow.h" static uint32_t dump_allowed; @@ -114,12 +115,13 @@ static void open_trace_file(void) { char errbuf [PCAP_ERRBUF_SIZE]; + LOG(INFO) << "Opening trace file: " << readfile; inputp = pcap_open_offline(readfile, errbuf); if (!inputp) { - fprintf(stderr, "error opening tracefile %s: %s\n", readfile, - errbuf); + LOG(FATAL) << "Error opening tracefile " << readfile << ": " << errbuf; exit(1); } + LOG(INFO) << "Successfully opened trace file"; } static char *resemble_file_path(struct pkt_dump_file *pdf) @@ -164,8 +166,7 @@ static char *resemble_file_path(struct pkt_dump_file *pdf) if (!(ret != -1 && S_ISDIR(statBuff.st_mode))) { check = mkdir(folder, S_IRWXU); if (check != 0) { - fprintf(stderr, "making directory error: %s\n", - folder); + LOG(ERROR) << "Failed to create directory: " << folder; exit(-1); } } @@ -459,7 +460,7 @@ static void process_trace(void) fname = resemble_file_path(&(pair->pdf)); FILE *f = fopen(fname, "ab"); if (!f) { - fprintf(stderr, "Failed to open output file '%s'\n", fname); + LOG(ERROR) << "Failed to open output file: " << fname; goto skip_dump_write; } @@ -490,11 +491,30 @@ static void close_trace_files(void) int main(int argc, char *argv[]) { + // Initialize Google Logging + google::InitGoogleLogging(argv[0]); + + // Set logging to stderr and files + FLAGS_alsologtostderr = true; + FLAGS_log_dir = "./logs"; + + LOG(INFO) << "Starting " << __GLOBAL_NAME__ << " version " << __SOURCE_VERSION__; + parseargs(argc, argv); open_trace_file(); init_hash_table(); + + LOG(INFO) << "Processing trace file: " << readfile; + LOG(INFO) << "Output directory: " << outputdir; + process_trace(); close_trace_files(); free_hash_table(); + + LOG(INFO) << "Processing completed successfully"; + + // Cleanup Google Logging + google::ShutdownGoogleLogging(); + exit(0); } diff --git a/tests/test_flow_db.cpp b/tests/test_flow_db.cpp new file mode 100644 index 0000000..c684be5 --- /dev/null +++ b/tests/test_flow_db.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include + +extern "C" { +#include "pkt2flow.h" +} + +class FlowDbTest : public ::testing::Test { +protected: + void SetUp() override { + init_hash_table(); + } + + void TearDown() override { + free_hash_table(); + } + + struct af_6tuple create_test_tuple_ipv4(const char* src_ip, uint16_t src_port, + const char* dst_ip, uint16_t dst_port, + int protocol = IPPROTO_TCP) { + struct af_6tuple tuple; + memset(&tuple, 0, sizeof(tuple)); + + tuple.af_family = AF_INET; + tuple.protocol = protocol; + tuple.port1 = src_port; + tuple.port2 = dst_port; + tuple.is_vlan = 0; + + inet_pton(AF_INET, src_ip, &tuple.ip1.v4); + inet_pton(AF_INET, dst_ip, &tuple.ip2.v4); + + return tuple; + } +}; + +TEST_F(FlowDbTest, InitHashTable) { + // Hash table should be initialized with NULL pointers + for (int i = 0; i < HASH_TBL_SIZE; i++) { + extern struct ip_pair *pairs[]; + EXPECT_EQ(pairs[i], nullptr); + } +} + +TEST_F(FlowDbTest, RegisterAndFindIpPair) { + // Create a test tuple + auto tuple = create_test_tuple_ipv4("192.168.1.1", 80, "192.168.1.2", 8080); + + // Initially, the pair should not exist + struct ip_pair *pair = find_ip_pair(tuple); + EXPECT_EQ(pair, nullptr); + + // Register the pair + pair = register_ip_pair(tuple); + EXPECT_NE(pair, nullptr); + EXPECT_EQ(pair->af_6tuple.af_family, AF_INET); + EXPECT_EQ(pair->af_6tuple.protocol, IPPROTO_TCP); + EXPECT_EQ(pair->af_6tuple.port1, 80); + EXPECT_EQ(pair->af_6tuple.port2, 8080); + + // Now we should be able to find it + struct ip_pair *found_pair = find_ip_pair(tuple); + EXPECT_EQ(found_pair, pair); +} + +TEST_F(FlowDbTest, BidirectionalFlow) { + // Create forward tuple + auto forward_tuple = create_test_tuple_ipv4("192.168.1.1", 80, "192.168.1.2", 8080); + + // Create reverse tuple (swapped IPs and ports) + auto reverse_tuple = create_test_tuple_ipv4("192.168.1.2", 8080, "192.168.1.1", 80); + + // Register forward flow + struct ip_pair *forward_pair = register_ip_pair(forward_tuple); + EXPECT_NE(forward_pair, nullptr); + + // Finding with reverse tuple should return the same pair (bidirectional) + struct ip_pair *reverse_pair = find_ip_pair(reverse_tuple); + EXPECT_EQ(forward_pair, reverse_pair); +} + +TEST_F(FlowDbTest, MultipleFlows) { + // Create multiple different flows + auto tuple1 = create_test_tuple_ipv4("192.168.1.1", 80, "192.168.1.2", 8080); + auto tuple2 = create_test_tuple_ipv4("192.168.1.3", 443, "192.168.1.4", 9090); + auto tuple3 = create_test_tuple_ipv4("10.0.0.1", 22, "10.0.0.2", 2222, IPPROTO_UDP); + + // Register all flows + struct ip_pair *pair1 = register_ip_pair(tuple1); + struct ip_pair *pair2 = register_ip_pair(tuple2); + struct ip_pair *pair3 = register_ip_pair(tuple3); + + EXPECT_NE(pair1, nullptr); + EXPECT_NE(pair2, nullptr); + EXPECT_NE(pair3, nullptr); + + // All pairs should be different + EXPECT_NE(pair1, pair2); + EXPECT_NE(pair1, pair3); + EXPECT_NE(pair2, pair3); + + // Find each flow + EXPECT_EQ(find_ip_pair(tuple1), pair1); + EXPECT_EQ(find_ip_pair(tuple2), pair2); + EXPECT_EQ(find_ip_pair(tuple3), pair3); +} + +TEST_F(FlowDbTest, ResetPdf) { + struct pkt_dump_file pdf; + pdf.pkts = 100; + pdf.start_time = 12345; + pdf.status = STS_TCP_SYN; + pdf.file_name = strdup("test_file.pcap"); + + reset_pdf(&pdf); + + EXPECT_EQ(pdf.pkts, 0); + EXPECT_EQ(pdf.start_time, 0); + EXPECT_EQ(pdf.status, STS_UNSET); + EXPECT_EQ(pdf.file_name, nullptr); +} + +TEST_F(FlowDbTest, UdpFlow) { + auto udp_tuple = create_test_tuple_ipv4("192.168.1.1", 53, "8.8.8.8", 53, IPPROTO_UDP); + + struct ip_pair *pair = register_ip_pair(udp_tuple); + EXPECT_NE(pair, nullptr); + EXPECT_EQ(pair->af_6tuple.protocol, IPPROTO_UDP); + + struct ip_pair *found_pair = find_ip_pair(udp_tuple); + EXPECT_EQ(found_pair, pair); +} \ No newline at end of file diff --git a/tests/test_main.cpp b/tests/test_main.cpp new file mode 100644 index 0000000..8e2f686 --- /dev/null +++ b/tests/test_main.cpp @@ -0,0 +1,18 @@ +#include +#include + +int main(int argc, char **argv) { + // Initialize Google Logging + google::InitGoogleLogging(argv[0]); + + // Initialize Google Test + ::testing::InitGoogleTest(&argc, argv); + + // Run all tests + int result = RUN_ALL_TESTS(); + + // Cleanup Google Logging + google::ShutdownGoogleLogging(); + + return result; +} \ No newline at end of file diff --git a/tests/test_utilities.cpp b/tests/test_utilities.cpp new file mode 100644 index 0000000..770a560 --- /dev/null +++ b/tests/test_utilities.cpp @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "pkt2flow.h" +} + +class UtilitiesTest : public ::testing::Test { +protected: + struct af_6tuple create_test_tuple_ipv4(const char* src_ip, uint16_t src_port, + const char* dst_ip, uint16_t dst_port, + int protocol = IPPROTO_TCP, + uint8_t is_vlan = 0) { + struct af_6tuple tuple; + memset(&tuple, 0, sizeof(tuple)); + + tuple.af_family = AF_INET; + tuple.protocol = protocol; + tuple.port1 = src_port; + tuple.port2 = dst_port; + tuple.is_vlan = is_vlan; + + inet_pton(AF_INET, src_ip, &tuple.ip1.v4); + inet_pton(AF_INET, dst_ip, &tuple.ip2.v4); + + return tuple; + } + + struct af_6tuple create_test_tuple_ipv6(const char* src_ip, uint16_t src_port, + const char* dst_ip, uint16_t dst_port, + int protocol = IPPROTO_TCP, + uint8_t is_vlan = 0) { + struct af_6tuple tuple; + memset(&tuple, 0, sizeof(tuple)); + + tuple.af_family = AF_INET6; + tuple.protocol = protocol; + tuple.port1 = src_port; + tuple.port2 = dst_port; + tuple.is_vlan = is_vlan; + + inet_pton(AF_INET6, src_ip, &tuple.ip1.v6); + inet_pton(AF_INET6, dst_ip, &tuple.ip2.v6); + + return tuple; + } +}; + +TEST_F(UtilitiesTest, NewFileNameIPv4) { + auto tuple = create_test_tuple_ipv4("192.168.1.1", 80, "192.168.1.2", 8080); + unsigned long timestamp = 1234567890; + + char *filename = new_file_name(tuple, timestamp); + ASSERT_NE(filename, nullptr); + + // Check that filename contains expected components + EXPECT_TRUE(strstr(filename, "192.168.1.1") != nullptr); + EXPECT_TRUE(strstr(filename, "192.168.1.2") != nullptr); + EXPECT_TRUE(strstr(filename, "80") != nullptr); + EXPECT_TRUE(strstr(filename, "8080") != nullptr); + EXPECT_TRUE(strstr(filename, "1234567890") != nullptr); + EXPECT_TRUE(strstr(filename, ".pcap") != nullptr); + + // Should not contain vlan suffix for non-VLAN traffic + EXPECT_FALSE(strstr(filename, "_vlan.pcap") != nullptr); + + free(filename); +} + +TEST_F(UtilitiesTest, NewFileNameIPv4WithVlan) { + auto tuple = create_test_tuple_ipv4("10.0.0.1", 443, "10.0.0.2", 9090, IPPROTO_TCP, 1); + unsigned long timestamp = 9876543210; + + char *filename = new_file_name(tuple, timestamp); + ASSERT_NE(filename, nullptr); + + // Check that filename contains expected components including VLAN + EXPECT_TRUE(strstr(filename, "10.0.0.1") != nullptr); + EXPECT_TRUE(strstr(filename, "10.0.0.2") != nullptr); + EXPECT_TRUE(strstr(filename, "443") != nullptr); + EXPECT_TRUE(strstr(filename, "9090") != nullptr); + EXPECT_TRUE(strstr(filename, "9876543210") != nullptr); + EXPECT_TRUE(strstr(filename, "_vlan.pcap") != nullptr); + + free(filename); +} + +TEST_F(UtilitiesTest, NewFileNameIPv6) { + auto tuple = create_test_tuple_ipv6("2001:db8::1", 80, "2001:db8::2", 8080); + unsigned long timestamp = 1111111111; + + char *filename = new_file_name(tuple, timestamp); + ASSERT_NE(filename, nullptr); + + // Check that filename contains expected components + EXPECT_TRUE(strstr(filename, "2001:db8::1") != nullptr); + EXPECT_TRUE(strstr(filename, "2001:db8::2") != nullptr); + EXPECT_TRUE(strstr(filename, "80") != nullptr); + EXPECT_TRUE(strstr(filename, "8080") != nullptr); + EXPECT_TRUE(strstr(filename, "1111111111") != nullptr); + EXPECT_TRUE(strstr(filename, ".pcap") != nullptr); + + free(filename); +} + +TEST_F(UtilitiesTest, NewFileNameIPv6WithVlan) { + auto tuple = create_test_tuple_ipv6("fe80::1", 22, "fe80::2", 2222, IPPROTO_TCP, 1); + unsigned long timestamp = 5555555555; + + char *filename = new_file_name(tuple, timestamp); + ASSERT_NE(filename, nullptr); + + // Check that filename contains expected components including VLAN + EXPECT_TRUE(strstr(filename, "fe80::1") != nullptr); + EXPECT_TRUE(strstr(filename, "fe80::2") != nullptr); + EXPECT_TRUE(strstr(filename, "22") != nullptr); + EXPECT_TRUE(strstr(filename, "2222") != nullptr); + EXPECT_TRUE(strstr(filename, "5555555555") != nullptr); + EXPECT_TRUE(strstr(filename, "_vlan.pcap") != nullptr); + + free(filename); +} + +TEST_F(UtilitiesTest, NewFileNameZeroPorts) { + auto tuple = create_test_tuple_ipv4("172.16.0.1", 0, "172.16.0.2", 0, IPPROTO_UDP); + unsigned long timestamp = 7777777777; + + char *filename = new_file_name(tuple, timestamp); + ASSERT_NE(filename, nullptr); + + // Should handle zero ports gracefully + EXPECT_TRUE(strstr(filename, "172.16.0.1") != nullptr); + EXPECT_TRUE(strstr(filename, "172.16.0.2") != nullptr); + EXPECT_TRUE(strstr(filename, "7777777777") != nullptr); + EXPECT_TRUE(strstr(filename, ".pcap") != nullptr); + + free(filename); +} + +TEST_F(UtilitiesTest, FileNameUniqueness) { + auto tuple1 = create_test_tuple_ipv4("192.168.1.1", 80, "192.168.1.2", 8080); + auto tuple2 = create_test_tuple_ipv4("192.168.1.1", 80, "192.168.1.3", 8080); + unsigned long timestamp = 1234567890; + + char *filename1 = new_file_name(tuple1, timestamp); + char *filename2 = new_file_name(tuple2, timestamp); + + ASSERT_NE(filename1, nullptr); + ASSERT_NE(filename2, nullptr); + + // Different tuples should generate different filenames + EXPECT_STRNE(filename1, filename2); + + free(filename1); + free(filename2); +} + +TEST_F(UtilitiesTest, FileNameTimestampDifference) { + auto tuple = create_test_tuple_ipv4("192.168.1.1", 80, "192.168.1.2", 8080); + unsigned long timestamp1 = 1234567890; + unsigned long timestamp2 = 1234567891; + + char *filename1 = new_file_name(tuple, timestamp1); + char *filename2 = new_file_name(tuple, timestamp2); + + ASSERT_NE(filename1, nullptr); + ASSERT_NE(filename2, nullptr); + + // Different timestamps should generate different filenames + EXPECT_STRNE(filename1, filename2); + + free(filename1); + free(filename2); +} \ No newline at end of file From e5562d9d7258dee2d0a0428b46fcc519617c6106 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Jul 2025 01:27:33 +0000 Subject: [PATCH 2/2] Migrate pkt2flow from C to C++, update build system and headers Co-authored-by: chenxm35 --- .gitignore | 61 +++++++++++++++++++++++++++++++++++++- CMakeLists.txt | 9 ++++-- flow_db.c => flow_db.cpp | 2 +- pkt2flow.c => pkt2flow.cpp | 3 +- pkt2flow.h | 13 ++++++++ 5 files changed, 81 insertions(+), 7 deletions(-) rename flow_db.c => flow_db.cpp (98%) rename pkt2flow.c => pkt2flow.cpp (99%) diff --git a/.gitignore b/.gitignore index 0b22209..694ac55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,62 @@ -THIS SHOULD BE A LINTER ERROR.sconsign.dblite +# Build directories +build/ +build-*/ +_builds/ + +# CMake +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake +Makefile +*.cmake +!CMakeLists.txt + +# Compiled binaries pkt2flow +pkt2flow_tests +*.exe + +# Object files *.o +*.obj + +# Libraries +*.a +*.so +*.dylib +*.dll + +# Logs +logs/ +*.log + +# IDE files +.vscode/ +.vs/ +.idea/ +*.swp +*.swo +*~ + +# OS specific +.DS_Store +Thumbs.db + +# Test artifacts +Testing/ +CTestTestfile.cmake +DartConfiguration.tcl + +# Package files +*.deb +*.rpm +*.tar.gz +*.zip + +# Coverage files +*.gcov +*.gcda +*.gcno + +# Static analysis +cppcheck-result.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index 925d4ef..f47355c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,8 +21,11 @@ endif() # Compiler flags set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") set(CMAKE_C_FLAGS_DEBUG "-g -O0") set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG") +set(CMAKE_CXX_FLAGS_DEBUG "-g -O0") +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") # Platform-specific definitions if(APPLE) @@ -47,8 +50,8 @@ find_package(glog REQUIRED) # Source files set(PKT2FLOW_SOURCES - pkt2flow.c - flow_db.c + pkt2flow.cpp + flow_db.cpp utilities.c ) @@ -117,7 +120,7 @@ if(BUILD_TESTS) # Create a library from the source files for testing add_library(pkt2flow_lib STATIC - flow_db.c + flow_db.cpp utilities.c ) diff --git a/flow_db.c b/flow_db.cpp similarity index 98% rename from flow_db.c rename to flow_db.cpp index 6540772..ba4e313 100644 --- a/flow_db.c +++ b/flow_db.cpp @@ -73,7 +73,7 @@ static unsigned int hashf(const void *key, size_t sz, unsigned int hash) { unsigned int h; unsigned int i; - const unsigned char *array = key; + const unsigned char *array = static_cast(key); h = hash; for (i = 0; i < sz; i++) diff --git a/pkt2flow.c b/pkt2flow.cpp similarity index 99% rename from pkt2flow.c rename to pkt2flow.cpp index 53e22ce..0aac307 100644 --- a/pkt2flow.c +++ b/pkt2flow.cpp @@ -55,9 +55,8 @@ static uint32_t dump_allowed; static char *readfile = NULL; //char *interface = NULL; -static char *outputdir = "pkt2flow.out"; +static const char *outputdir = "pkt2flow.out"; static pcap_t *inputp = NULL; -struct ip_pair *pairs[HASH_TBL_SIZE]; static void usage(char *progname) { diff --git a/pkt2flow.h b/pkt2flow.h index 7142555..e79c0cf 100644 --- a/pkt2flow.h +++ b/pkt2flow.h @@ -32,10 +32,17 @@ * SOFTWARE. */ +#ifndef PKT2FLOW_H +#define PKT2FLOW_H + #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define __SOURCE_VERSION__ "1.2" #define __AUTHOR__ "X. Chen (chenxm35@gmail.com)" #define __GLOBAL_NAME__ "pkt2flow" @@ -139,3 +146,9 @@ struct ip_pair *register_ip_pair(struct af_6tuple af_6tuple); */ void reset_pdf(struct pkt_dump_file *f); +#ifdef __cplusplus +} +#endif + +#endif /* PKT2FLOW_H */ +