diff --git a/repos/os/run/internet_checksum.run b/repos/os/run/internet_checksum.run new file mode 100644 index 0000000000..0d4be7cff8 --- /dev/null +++ b/repos/os/run/internet_checksum.run @@ -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 { + + + + + + + + + + + + + + + + + + + + + + + + + + } + +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 "" + 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} diff --git a/repos/os/src/test/internet_checksum/README b/repos/os/src/test/internet_checksum/README new file mode 100644 index 0000000000..3f07af7ca6 --- /dev/null +++ b/repos/os/src/test/internet_checksum/README @@ -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=. diff --git a/repos/os/src/test/internet_checksum/main.cc b/repos/os/src/test/internet_checksum/main.cc new file mode 100644 index 0000000000..67f794b65c --- /dev/null +++ b/repos/os/src/test/internet_checksum/main.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + 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 pcap_file { root, "/output.pcap" }; + Attached_rom_dataspace pcap_rom { env, "input.pcap" }; + Parser pcap_parser { pcap_rom.local_addr(), 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(); + 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(); + 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 ð = Ethernet_frame::cast_from(buf, size_guard); + ASSERT(eth.type() == Ethernet_frame::Type::IPV4); + Ipv4_packet &ip = eth.data(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(size_guard), ip, l4_size); break; + case Ipv4_packet::Protocol::UDP: check_udp(ip.data(size_guard), ip); break; + case Ipv4_packet::Protocol::ICMP: check_icmp(ip.data(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(size_guard).update_checksum(ip.src(), ip.dst(), l4_size); break; + case Ipv4_packet::Protocol::UDP: ip.data(size_guard).update_checksum(ip.src(), ip.dst()); break; + case Ipv4_packet::Protocol::ICMP: ip.data(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 *)ð, 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); } diff --git a/repos/os/src/test/internet_checksum/target.mk b/repos/os/src/test/internet_checksum/target.mk new file mode 100644 index 0000000000..f64c701492 --- /dev/null +++ b/repos/os/src/test/internet_checksum/target.mk @@ -0,0 +1,7 @@ +TARGET = test-internet_checksum + +LIBS += base net vfs + +SRC_CC += main.cc + +INC_DIR += $(PRG_DIR) diff --git a/repos/os/src/test/internet_checksum/trafgen.cfg b/repos/os/src/test/internet_checksum/trafgen.cfg new file mode 100644 index 0000000000..8353c08074 --- /dev/null +++ b/repos/os/src/test/internet_checksum/trafgen.cfg @@ -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) +} diff --git a/tool/autopilot.list b/tool/autopilot.list index 2e463eb8fd..08d7fe84f9 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -22,6 +22,7 @@ hello ieee754 init_smp intel_fb +internet_checksum libc_integration libc_vfs_fs_ext2 libc_vfs_fs_fat