internet_checksum.run: test net checksum alorithms

See repos/os/src/test/internet_checksum/README for more detail.

Ref #4636
This commit is contained in:
Martin Stein 2024-04-25 14:31:33 +02:00 committed by Christian Helmuth
parent 3a0ded3bdd
commit e350dc27e9
6 changed files with 455 additions and 0 deletions

View File

@ -0,0 +1,92 @@
assert_spec linux
set tshark [installed_command tshark]
set trafgen [installed_command trafgen]
set lx_fs_root "internet_checksum_dir"
set lx_fs_dir "bin/$lx_fs_root"
set input_file_name "input.pcap"
set input_file "bin/$input_file_name"
rename exit run_tool_exit
proc exit {{code 0}} {
global lx_fs_dir
global input_file
if {[get_cmd_switch --autopilot]} { exec rm -rf $input_file $lx_fs_dir }
run_tool_exit $code
}
build { core init lib/ld lib/vfs test/internet_checksum server/lx_fs }
create_boot_directory
set seed ""
if {[info exists ::env(SEED)]} {
set seed $::env(SEED)
} else {
set min_seed 0
set max_seed [expr 2**32]
set seed [expr int($min_seed + floor(rand() * $max_seed))]
}
puts "\nUse script with SEED=$seed in order to get reproducible results\n"
install_config {
<config verbose="yes">
<parent-provides>
<service name="ROM"/>
<service name="LOG"/>
<service name="CPU"/>
<service name="PD"/>
</parent-provides>
<start name="lx_fs" ld="no" caps="100">
<resource name="RAM" quantum="4M"/>
<provides> <service name="File_system"/> </provides>
<config> <policy label="test-internet_checksum -> " root="/} $lx_fs_root {" writeable="yes"/> </config>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="test-internet_checksum" caps="100">
<resource name="RAM" quantum="1M"/>
<config seed="} $seed {"> <vfs> <fs/> </vfs> </config>
<route>
<service name="File_system"> <child name="lx_fs"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</config> }
exec rm -rf $lx_fs_dir
exec mkdir -p $lx_fs_dir
exec $trafgen --rand --seed $seed --in [genode_dir]/repos/os/src/test/internet_checksum/trafgen.cfg --num 10000 --out $input_file
proc assert_no_bad_checksums_in {pcap_file} {
global tshark
set view_filter "ip.checksum_bad.expert || tcp.checksum_bad.expert || udp.checksum.bad || icmp.checksum_bad"
set out "<invalid>"
set out [exec $tshark -o ip.check_checksum:TRUE -o tcp.check_checksum:TRUE -o udp.check_checksum:TRUE -Y "$view_filter" -r $pcap_file]
if {$out != ""} {
puts "\nError: bad checksums found in $pcap_file\n"
exit -1
}
}
assert_no_bad_checksums_in $input_file
build_boot_image [list {*}[build_artifacts] $lx_fs_root $input_file_name]
append qemu_args " -nographic "
run_genode_until {\[init\] child "test-internet_checksum" exited.*?\n} 30
set output_file "$lx_fs_dir/output.pcap"
assert_no_bad_checksums_in $output_file
set num_checked_checksums ""
set string_buf ""
regexp {checked [0-9]+ checksums} $output string_buf
regexp {[0-9]+} $string_buf num_checked_checksums
set tmp_file "$lx_fs_dir/tmp"
exec $tshark -o ip.check_checksum:TRUE -o tcp.check_checksum:TRUE -o udp.check_checksum:TRUE -V -r $output_file | grep -e "Checksum Status:" -e "Header checksum status:" > $tmp_file
set num_output_checksums [exec wc -l < $tmp_file]
if {$num_checked_checksums != $num_output_checksums} {
puts "\nError: number of checksums in $output_file differs from number of checked checksums\n"
exit -1
}
grep_output {\[init\] child "test-internet_checksum" exited}
compare_output_to {[init] child "test-internet_checksum" exited with exit value 0}

View File

@ -0,0 +1,19 @@
This test aims for testing the typical use of the internet checksum algorithms
provided with the net library. It is accompanied by a similarly named test
script.
The test script feeds the test component with a sufficiently big input.pcap
file that is randomly generated on each run using trafgen. The test component
then iterates over all packets in the file. For each found IPv4, UDP, TCP or
ICMP header (except the embedded ones in ICMP errors), it typically does the
following:
1. try to validate the initial checksum
2. try to re-calculate the initial checksum and see if it stas the same
3. modify the header randomly, update the checksum incrementally, and write
out the result to a file output.pcap
The checksums in the resulting output.pcap file are then checked by the test
script using tshark. On each run, the test script prints the seed used for
randomization in both, the test component and trafgen. In order to reproduce a
given test result, one can simply run the test script with SEED=<seed>.

View File

@ -0,0 +1,275 @@
/*
* \brief Test the reachability of a host on an IP network
* \author Martin Stein
* \date 2018-03-27
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <net/ipv4.h>
#include <net/ethernet.h>
#include <net/arp.h>
#include <net/icmp.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <net/internet_checksum.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/sleep.h>
#include <base/attached_rom_dataspace.h>
#include <os/vfs.h>
using namespace Net;
using namespace Genode;
#define ASSERT(condition) \
do { \
if (!(condition)) { \
Genode::error(__FILE__, ":", __LINE__, ": ", " assertion \"", #condition, "\" failed "); \
Genode::sleep_forever(); \
} \
} while (false)
#define ASSERT_NEVER_REACHED \
do { \
Genode::error(__FILE__, ":", __LINE__, ": ", " should have never been reached"); \
Genode::sleep_forever(); \
} while (false)
struct Parser
{
char *start;
size_t num_bytes;
void advance_by(size_t num_bytes)
{
ASSERT(num_bytes >= num_bytes);
start += num_bytes;
num_bytes -= num_bytes;
}
template <typename T>
T const &fetch()
{
ASSERT(num_bytes >= sizeof(T));
T const &obj = *(T const *)start;
advance_by(sizeof(T));
return obj;
}
void fetch(Byte_range_ptr const &dst)
{
ASSERT(num_bytes >= dst.num_bytes);
memcpy(dst.start, start, dst.num_bytes);
advance_by(dst.num_bytes);
}
};
struct Pseudo_random_number_generator
{
uint64_t seed;
uint8_t random_byte()
{
seed = (16807 * seed) % 2147483647;
return (uint8_t)seed;
}
};
struct Pcap_file_header
{
static constexpr uint32_t MAGIC_NUMBER = 0xA1B2C3D4;
uint32_t magic_number;
uint32_t unused[5];
bool valid() const { return magic_number == MAGIC_NUMBER; }
};
struct Pcap_packet_record
{
uint32_t unused_0[2];
uint32_t captured_pkt_len;
uint32_t original_pkt_len;
};
struct Main
{
Env &env;
Heap heap { env.ram(), env.rm() };
Attached_rom_dataspace config_rom { env, "config" };
Root_directory root { env, heap, config_rom.xml().sub_node("vfs") };
Reconstructible<Append_file> pcap_file { root, "/output.pcap" };
Attached_rom_dataspace pcap_rom { env, "input.pcap" };
Parser pcap_parser { pcap_rom.local_addr<char>(), pcap_rom.size() };
unsigned long num_errors = 0;
unsigned long num_packets = 0;
unsigned long num_ip4_checksums = 0;
unsigned long num_udp_checksums = 0;
unsigned long num_tcp_checksums = 0;
unsigned long num_icmp_checksums = 0;
Pseudo_random_number_generator prng { config_rom.xml().attribute_value("seed", 0ULL) };
Main(Env &env);
void check_tcp(Tcp_packet &tcp, Ipv4_packet &ip, size_t tcp_size);
void check_udp(Udp_packet &udp, Ipv4_packet &ip);
void check_icmp(Icmp_packet &icmp, size_t icmp_size);
void check_ip4(Ipv4_packet &ip);
void modify_ip4(Ipv4_packet &ip, Internet_checksum_diff &ip_icd);
void check_recalculated_checksum(char const *prot, uint16_t got_checksum, uint16_t expect_checksum)
{
if (got_checksum != expect_checksum) {
error("frame ", num_packets + 1, ": ", prot, ": re-calculating initial checksum failed (got ",
Hex(got_checksum)," expected ", Hex(expect_checksum), " diff ", expect_checksum - got_checksum, ")");
num_errors++;
}
}
void validate_initial_checksum_error(char const *prot)
{
error("frame ", num_packets + 1, ": ", prot, ": validating initial checksum failed");
num_errors++;
}
};
void Main::check_icmp(Icmp_packet &icmp, size_t icmp_size)
{
uint16_t initial_checksum = icmp.checksum();
size_t l5_size = icmp_size - sizeof(Icmp_packet);
if (icmp.checksum_error(l5_size))
validate_initial_checksum_error("icmp");
icmp.update_checksum(l5_size);
check_recalculated_checksum("icmp", icmp.checksum(), initial_checksum);
num_icmp_checksums++;
}
void Main::check_udp(Udp_packet &udp, Ipv4_packet &ip)
{
uint16_t initial_checksum = udp.checksum();
if (udp.checksum_error(ip.src(), ip.dst()))
validate_initial_checksum_error("udp");
udp.update_checksum(ip.src(), ip.dst());
check_recalculated_checksum("udp", udp.checksum(), initial_checksum);
num_udp_checksums++;
}
void Main::check_tcp(Tcp_packet &tcp, Ipv4_packet &ip, size_t tcp_size)
{
uint16_t initial_checksum = tcp.checksum();
tcp.update_checksum(ip.src(), ip.dst(), tcp_size);
check_recalculated_checksum("tcp", tcp.checksum(), initial_checksum);
num_tcp_checksums++;
}
void Main::check_ip4(Ipv4_packet &ip)
{
uint16_t initial_checksum = ip.checksum();
if (ip.checksum_error())
validate_initial_checksum_error("ip");
ip.update_checksum();
check_recalculated_checksum("ip", ip.checksum(), initial_checksum);
num_ip4_checksums++;
}
void Main::modify_ip4(Ipv4_packet &ip, Internet_checksum_diff &ip_icd)
{
Ipv4_address ip_src = ip.src();
ip_src.addr[0] &= prng.random_byte();
ip_src.addr[1] |= prng.random_byte();
ip_src.addr[2] *= prng.random_byte();
ip_src.addr[3] += prng.random_byte();
ip.src(ip_src, ip_icd);
if (prng.random_byte() & 0b11)
return;
Ipv4_address ip_dst = ip.dst();
ip_dst.addr[0] |= prng.random_byte();
ip_dst.addr[1] += prng.random_byte();
ip_dst.addr[2] &= prng.random_byte();
ip_dst.addr[3] *= prng.random_byte();
ip.dst(ip_dst, ip_icd);
}
Main::Main(Env &env) : env(env)
{
using Append_result = Append_file::Append_result;
static constexpr size_t BUF_SIZE = 1024;
char buf[BUF_SIZE];
Pcap_file_header const &header = pcap_parser.fetch<Pcap_file_header>();
ASSERT(header.valid());
ASSERT(pcap_file->append((char *)&header, sizeof(header)) == Append_result::OK);
while(1) {
if (pcap_parser.num_bytes < sizeof(Pcap_packet_record))
break;
Pcap_packet_record const &record = pcap_parser.fetch<Pcap_packet_record>();
if (!record.captured_pkt_len)
break;
ASSERT(pcap_file->append((char *)&record, sizeof(record)) == Append_result::OK);
ASSERT(record.captured_pkt_len == record.original_pkt_len);
ASSERT(record.captured_pkt_len <= BUF_SIZE);
pcap_parser.fetch({buf, record.captured_pkt_len});
Size_guard size_guard(record.captured_pkt_len);
Ethernet_frame &eth = Ethernet_frame::cast_from(buf, size_guard);
ASSERT(eth.type() == Ethernet_frame::Type::IPV4);
Ipv4_packet &ip = eth.data<Ipv4_packet>(size_guard);
check_ip4(ip);
size_t l4_size = ip.total_length() - sizeof(Ipv4_packet);
switch (ip.protocol()) {
case Ipv4_packet::Protocol::TCP: check_tcp(ip.data<Tcp_packet>(size_guard), ip, l4_size); break;
case Ipv4_packet::Protocol::UDP: check_udp(ip.data<Udp_packet>(size_guard), ip); break;
case Ipv4_packet::Protocol::ICMP: check_icmp(ip.data<Icmp_packet>(size_guard), l4_size); break;
default: break;
}
Internet_checksum_diff ip_icd { };
modify_ip4(ip, ip_icd);
switch (ip.protocol()) {
case Ipv4_packet::Protocol::TCP: ip.data<Tcp_packet>(size_guard).update_checksum(ip.src(), ip.dst(), l4_size); break;
case Ipv4_packet::Protocol::UDP: ip.data<Udp_packet>(size_guard).update_checksum(ip.src(), ip.dst()); break;
case Ipv4_packet::Protocol::ICMP: ip.data<Icmp_packet>(size_guard).update_checksum(l4_size - sizeof(Icmp_packet)); break;
default: break;
}
ip.update_checksum(ip_icd);
ip.checksum(ip.checksum());
ASSERT(pcap_file->append((char *)&eth, record.captured_pkt_len) == Append_result::OK);
num_packets++;
}
unsigned long num_checksums = num_ip4_checksums + num_udp_checksums + num_tcp_checksums + num_icmp_checksums;
log("checked ", num_checksums, " checksum", num_checksums == 1 ? "" : "s",
" (ip4 ", num_ip4_checksums, " tcp ", num_tcp_checksums," udp ", num_udp_checksums, " icmp ", num_icmp_checksums,
") in ", num_packets, " packet", num_packets == 1 ? "" : "s", " with ", num_errors, " error", num_errors == 1 ? "" : "s");
pcap_file.destruct();
env.parent().exit(num_errors ? -1 : 0);
}
void Component::construct(Env &env) { static Main main(env); }

View File

@ -0,0 +1,7 @@
TARGET = test-internet_checksum
LIBS += base net vfs
SRC_CC += main.cc
INC_DIR += $(PRG_DIR)

View File

@ -0,0 +1,61 @@
/*
* FIXME: Currently, we skip testing odd ICMP packet lengths because trafgen
* generates bad checksums for such packets.
*/
/* UDP */
{
eth(saddr=drnd(), daddr=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
udp(sport=drnd(), dport=drnd()),
drnd(801)
}
{
eth(saddr=drnd(), daddr=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
udp(sport=drnd(), dport=drnd()),
drnd(80)
}
/* TCP */
{
eth(saddr=drnd(), daddr=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
tcp(sport=drnd(), dport=drnd(), seq=drnd(), ackseq=drnd()),
drnd(701)
}
{
eth(saddr=drnd(), daddr=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
tcp(sport=drnd(), dport=drnd(), seq=drnd(), ackseq=drnd()),
drnd(70)
}
/* ICMP echo */
{
eth(saddr=drnd(), daddr=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
icmp4(echorequest, addr=drnd(), id=drnd(), seq=drnd()),
drnd(600)
}
{
eth(saddr=drnd(), daddr=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
icmp4(echoreply, addr=drnd(), id=drnd(), seq=drnd()),
drnd(60)
}
/* ICMP error + embedded packet */
{
eth(saddr=drnd(), daddr=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
icmp4(type=1, code=drnd(0,16), addr=drnd(), id=drnd(), seq=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
udp(sport=drnd(), dport=drnd()),
drnd(500)
}
{
eth(saddr=drnd(), daddr=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
icmp4(type=1, code=drnd(0,16), addr=drnd(), id=drnd(), seq=drnd()),
ip4(saddr=drnd(), daddr=drnd(), ttl=drnd(), id=drnd()),
tcp(sport=drnd(), dport=drnd(), seq=drnd(), ackseq=drnd()),
drnd(50)
}

View File

@ -22,6 +22,7 @@ hello
ieee754
init_smp
intel_fb
internet_checksum
libc_integration
libc_vfs_fs_ext2
libc_vfs_fs_fat