From 5b163e772ecc75c631f4ab7a55c0c61771c18fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Emil=20Schulz=20=C3=98stergaard?= Date: Wed, 18 Mar 2026 08:56:58 +0100 Subject: [PATCH] no-pad: add toplevel -p option to disable frame padding This enables sending runt frames. This is useful for testing on NPI ports and frame injection where IFH is prepended, so the wire length is above 60 bytes, while the frame data may be below. --- CMakeLists.txt | 1 + src/ef-args.c | 9 +++- src/ef.c | 4 +- src/ef.h | 1 + test/test-padding.cxx | 98 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 test/test-padding.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index e489537..e81ea57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ add_executable(ef-tests test/ef-tests.cxx test/test-ef-parse-bytes.cxx test/ifh-ignore.cxx + test/test-padding.cxx ) target_link_libraries(ef-tests libef) diff --git a/src/ef-args.c b/src/ef-args.c index d42bae6..abfeff4 100644 --- a/src/ef-args.c +++ b/src/ef-args.c @@ -90,6 +90,8 @@ void print_help() { po("Options:\n"); po(" -v Print version.\n"); po(" -h Top level help message.\n"); + po(" -p No pad. Skip padding frames to 60 bytes,\n"); + po(" allowing runt frames to be sent or matched as-is.\n"); po(" -t When listening on an interface (rx),\n"); po(" When listening on an interface (rx), the tool will always\n"); po(" listen during the entire timeout period. This is needed,\n"); @@ -320,13 +322,18 @@ int argc_cmds(int argc, const char *argv[]) { return res; } +int NO_PAD = 0; int TIME_OUT_MS = 100; int main_(int argc, const char *argv[]) { int opt; - while ((opt = getopt(argc, (char * const*)argv, "vht:c:")) != -1) { + while ((opt = getopt(argc, (char * const*)argv, "pvht:c:")) != -1) { switch (opt) { + case 'p': + NO_PAD = 1; + break; + case 'v': print_version(); return 0; diff --git a/src/ef.c b/src/ef.c index 5a1d5d9..ae3c147 100644 --- a/src/ef.c +++ b/src/ef.c @@ -477,7 +477,7 @@ buf_t *frame_to_buf(frame_t *f) { frame_size += f->stack[i]->size; } - if (frame_size < 60) + if (frame_size < 60 && !NO_PAD) frame_size = 60; buf = balloc(frame_size); @@ -503,7 +503,7 @@ buf_t *frame_mask_to_buf(frame_t *f) { } frame_size_no_padding = frame_size; - if (frame_size < 60) + if (frame_size < 60 && !NO_PAD) frame_size = 60; buf = balloc(frame_size); diff --git a/src/ef.h b/src/ef.h index 10a3863..af31deb 100644 --- a/src/ef.h +++ b/src/ef.h @@ -16,6 +16,7 @@ extern "C" { #define DIV_ROUND(a, b) (1 + ((a - 1) / b)) #define BIT_TO_BYTE(x) (DIV_ROUND(x, 8)) +extern int NO_PAD; extern int TIME_OUT_MS; /////////////////////////////////////////////////////////////////////////////// diff --git a/test/test-padding.cxx b/test/test-padding.cxx new file mode 100644 index 0000000..f24a3b5 --- /dev/null +++ b/test/test-padding.cxx @@ -0,0 +1,98 @@ +#include "ef.h" +#include "ef-test.h" +#include "catch_single_include.hxx" + +#include + +// RAII guard to set NO_PAD for the duration of a scope +struct NoPadGuard { + NoPadGuard() { NO_PAD = 1; } + ~NoPadGuard() { NO_PAD = 0; } +}; + +// Build a frame and its expected buf/mask from a frame spec +static void build_frame(std::vector spec, + buf_t **out_buf, buf_t **out_mask, + frame_t **out_frame) +{ + frame_t *f = parse_frame_wrap(spec); + REQUIRE(f != NULL); + *out_buf = frame_to_buf(f); + REQUIRE(*out_buf != NULL); + *out_mask = f->has_mask ? frame_mask_to_buf(f) : NULL; + *out_frame = f; +} + +TEST_CASE("no-pad: eth-only frame is 14 bytes", "[nopad]") { + NoPadGuard g; + buf_t *buf, *mask; + frame_t *f; + build_frame({"eth", "dmac", "::1", "smac", "::2"}, &buf, &mask, &f); + + // eth header = 14 bytes, no padding to 60 + CHECK(buf->size == 14); + + bfree(buf); + frame_free(f); +} + +TEST_CASE("no-pad: default pads to 60 bytes", "[nopad]") { + CHECK(NO_PAD == 0); + buf_t *buf, *mask; + frame_t *f; + build_frame({"eth", "dmac", "::1", "smac", "::2"}, &buf, &mask, &f); + + CHECK(buf->size == 60); + + bfree(buf); + frame_free(f); +} + +TEST_CASE("no-pad: mask buf also skips padding", "[nopad]") { + NoPadGuard g; + + // Use 'ign' on smac to force mask generation + frame_t *f = parse_frame_wrap({"eth", "dmac", "::1", "smac", "ign"}); + REQUIRE(f != NULL); + buf_t *buf = frame_to_buf(f); + buf_t *mask = frame_mask_to_buf(f); + REQUIRE(buf != NULL); + REQUIRE(mask != NULL); + + CHECK(buf->size == 14); + CHECK(mask->size == 14); + + bfree(buf); + bfree(mask); + frame_free(f); +} + +TEST_CASE("no-pad: frame with payload stays exact size", "[nopad]") { + NoPadGuard g; + buf_t *buf, *mask; + frame_t *f; + + // eth(14) + data pattern cnt 4 = 18 bytes, well under 60 + build_frame({"eth", "dmac", "::1", "smac", "::2", + "data", "pattern", "cnt", "4"}, &buf, &mask, &f); + + CHECK(buf->size == 18); + + bfree(buf); + frame_free(f); +} + +TEST_CASE("no-pad: large frame unaffected", "[nopad]") { + NoPadGuard g; + buf_t *buf, *mask; + frame_t *f; + + // eth(14) + data pattern cnt 100 = 114 bytes, already > 60 + build_frame({"eth", "dmac", "::1", "smac", "::2", + "data", "pattern", "cnt", "100"}, &buf, &mask, &f); + + CHECK(buf->size == 114); + + bfree(buf); + frame_free(f); +}