diff --git a/repos/os/run/ping.run b/repos/os/run/ping.run new file mode 100644 index 0000000000..bd20208a06 --- /dev/null +++ b/repos/os/run/ping.run @@ -0,0 +1,100 @@ +# +# Build +# + +if {![have_include power_on/qemu]} { + puts "Run script is only supported on Qemu" + return 0 +} + +set build_components { + core + init + drivers/timer + drivers/nic + app/ping +} + +source ${genode_dir}/repos/base/run/platform_drv.inc +append_platform_drv_build_components + +build $build_components + +create_boot_directory + +# +# Generate config +# + +append config { + + + + + + + + + + + + + + + } + +append_platform_drv_config + +append config { + + + + + + + + + + + + + + + + + +} + +install_config $config + +# +# Boot modules +# + +# generic modules +append boot_modules { + core init + timer + } [nic_drv_binary] { + ping + ld.lib.so +} + +# platform-specific modules +lappend_if [have_spec linux] boot_modules fb_sdl + +append_platform_drv_boot_modules + +build_boot_image $boot_modules + +append_if [have_spec x86] qemu_args " -net nic,model=e1000 " +append_if [have_spec lan9118] qemu_args " -net nic,model=lan9118 " + +append qemu_args " -net user -nographic " + +run_genode_until "\"ping\" exited with exit value 0.*\n" 25 diff --git a/repos/os/src/app/ping/README b/repos/os/src/app/ping/README new file mode 100644 index 0000000000..c6594a8528 --- /dev/null +++ b/repos/os/src/app/ping/README @@ -0,0 +1,58 @@ + + =================================================== + Perform and evaluate ICMP Echo with another IP host + =================================================== + + +Brief +##### + +The 'ping' component continuously sends ICMP Echo requests to a given IP host +and waits for the corresponding ICMP Echo replies. For each successfull ICMP +Echo handshake it prints a short statistic. By now, it can be used only with a +static IP configuration. The size of the ICMP data field can be configured. It +gets filled with the letters of the alphabet ('a' to 'z') repeatedly. + + +Configuration +############# + +This is an example configuration of the component which shows the default +value for each attribute except 'config.dst_ip' and 'config.src_ip': + +! + + +This is a short description of the tags and attributes: + +:config.src_ip: + Mandatory. IP address of the component. + +:config.dst_ip: + Mandatory. IP address of the target host. + +:config.data_size: + Optional. Size of the ICMP data field. + +:config.period_sec: + Optional. Length of send interval in seconds. + +:config.verbose: + Optional. Toggles wether the component shall log debugging information. + +:config.count: + Optional. After how many successful pings the component exits successfully. + + +Sessions +######## + +This is an overview of the sessions required and provided by the +component apart from the environment sessions: + +* Requires one Timer session. diff --git a/repos/os/src/app/ping/config.xsd b/repos/os/src/app/ping/config.xsd new file mode 100644 index 0000000000..35d6c6a8a3 --- /dev/null +++ b/repos/os/src/app/ping/config.xsd @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/os/src/app/ping/main.cc b/repos/os/src/app/ping/main.cc new file mode 100644 index 0000000000..58f49a9e57 --- /dev/null +++ b/repos/os/src/app/ping/main.cc @@ -0,0 +1,542 @@ +/* + * \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. + */ + +/* local includes */ +#include + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Net; +using namespace Genode; + +namespace Net { + + using Packet_descriptor = ::Nic::Packet_descriptor; + using Packet_stream_sink = ::Nic::Packet_stream_sink< ::Nic::Session::Policy>; + using Packet_stream_source = ::Nic::Packet_stream_source< ::Nic::Session::Policy>; +} + +Microseconds read_sec_attr(Xml_node const node, + char const *name, + unsigned long const default_sec) +{ + unsigned long sec = node.attribute_value(name, 0UL); + if (!sec) { + sec = default_sec; + } + return Microseconds(sec * 1000 * 1000); +} + + +class Main +{ + private: + + using Signal_handler = Genode::Signal_handler
; + using Periodic_timeout = Timer::Periodic_timeout
; + + enum { IPV4_TIME_TO_LIVE = 64 }; + enum { ICMP_ID = 1166 }; + enum { DEFAULT_ICMP_DATA_SZ = 56 }; + enum { DEFAULT_COUNT = 5 }; + enum { DEFAULT_PERIOD_SEC = 5 }; + enum { PKT_SIZE = Nic::Packet_allocator::DEFAULT_PACKET_SIZE }; + enum { BUF_SIZE = Nic::Session::QUEUE_SIZE * PKT_SIZE }; + + Env &_env; + Attached_rom_dataspace _config_rom { _env, "config" }; + Xml_node _config { _config_rom.xml() }; + Timer::Connection _timer { _env }; + Microseconds _send_time { 0 }; + Periodic_timeout _period { _timer, *this, &Main::_send_ping, + read_sec_attr(_config, "period_sec", DEFAULT_PERIOD_SEC) }; + Heap _heap { &_env.ram(), &_env.rm() }; + Nic::Packet_allocator _pkt_alloc { &_heap }; + Nic::Connection _nic { _env, &_pkt_alloc, BUF_SIZE, BUF_SIZE }; + Signal_handler _sink_ack { _env.ep(), *this, &Main::_ack_avail }; + Signal_handler _sink_submit { _env.ep(), *this, &Main::_ready_to_submit }; + Signal_handler _source_ack { _env.ep(), *this, &Main::_ready_to_ack }; + Signal_handler _source_submit { _env.ep(), *this, &Main::_packet_avail }; + bool const _verbose { _config.attribute_value("verbose", false) }; + Ipv4_address const _src_ip { _config.attribute_value("src_ip", Ipv4_address()) }; + Ipv4_address const _dst_ip { _config.attribute_value("dst_ip", Ipv4_address()) }; + Mac_address const _src_mac { _nic.mac_address() }; + Mac_address _dst_mac { }; + uint16_t _ip_id { 1 }; + uint16_t _icmp_seq { 1 }; + size_t const _icmp_data_sz { _config.attribute_value("data_size", (size_t)DEFAULT_ICMP_DATA_SZ) }; + unsigned long _count { _config.attribute_value("count", (unsigned long)DEFAULT_COUNT) }; + + void _handle_eth(void *const eth_base, + size_t const eth_size, + Packet_descriptor const &pkt); + + void _handle_ip(Ethernet_frame ð, + size_t const eth_size); + + void _handle_icmp(Ipv4_packet &ip, + size_t const ip_size); + + void _handle_icmp_echo_reply(Ipv4_packet &ip, + Icmp_packet &icmp, + size_t icmp_data_sz); + + void _handle_icmp_dst_unreachbl(Ipv4_packet &ip, + Icmp_packet &icmp, + size_t icmp_data_sz); + + void _handle_arp(Ethernet_frame ð, + size_t const eth_size); + + void _broadcast_arp_request(); + + void _send_arp_reply(Ethernet_frame &req_eth, + Arp_packet &req_arp); + + template + void _send(size_t pkt_size, + FUNC && write_to_pkt) + { + try { + Packet_descriptor pkt = _source().alloc_packet(pkt_size); + void *pkt_base = _source().packet_content(pkt); + write_to_pkt(pkt_base); + _source().submit_packet(pkt); + if (_verbose) { + log("snd ", *reinterpret_cast(pkt_base)); } + } + catch (Net::Packet_stream_source::Packet_alloc_failed) { + warning("failed to allocate packet"); } + } + + void _send_ping(Duration not_used = Duration(Microseconds(0))); + + Net::Packet_stream_sink &_sink() { return *_nic.rx(); } + Net::Packet_stream_source &_source() { return *_nic.tx(); } + + + /*********************************** + ** Packet-stream signal handlers ** + ***********************************/ + + void _ready_to_submit(); + void _ack_avail() { } + void _ready_to_ack(); + void _packet_avail() { } + + public: + + struct Invalid_arguments : Exception { }; + struct Send_buffer_too_small : Exception { }; + + Main(Env &env); +}; + + +Main::Main(Env &env) : _env(env) +{ + /* exit unsuccessful if parameters are invalid */ + if (_src_ip == Ipv4_address() || + _dst_ip == Ipv4_address() || + _count == 0) + { + throw Invalid_arguments(); + } + /* install packet stream signals */ + _nic.rx_channel()->sigh_ready_to_ack(_sink_ack); + _nic.rx_channel()->sigh_packet_avail(_sink_submit); + _nic.tx_channel()->sigh_ack_avail(_source_ack); + _nic.tx_channel()->sigh_ready_to_submit(_source_submit); +} + + +void Main::_handle_eth(void *const eth_base, + size_t const eth_size, + Packet_descriptor const &) +{ + /* print receipt message */ + Ethernet_frame ð = *reinterpret_cast(eth_base); + if (_verbose) { + log("rcv ", eth); } + + /* drop packet if ETH does not target us */ + if (eth.dst() != _src_mac && + eth.dst() != Ethernet_frame::BROADCAST) + { + if (_verbose) { + log("bad ETH destination"); } + return; + } + /* select ETH sub-protocol */ + switch (eth.type()) { + case Ethernet_frame::Type::ARP: _handle_arp(eth, eth_size); break; + case Ethernet_frame::Type::IPV4: _handle_ip(eth, eth_size); break; + default: ; } +} + + +void Main::_handle_ip(Ethernet_frame ð, + size_t const eth_size) +{ + /* drop packet if IP does not target us */ + size_t const ip_size = eth_size - sizeof(Ethernet_frame); + Ipv4_packet &ip = *eth.data(ip_size); + if (ip.dst() != _src_ip && + ip.dst() != Ipv4_packet::BROADCAST) + { + if (_verbose) { + log("bad IP destination"); } + return; + } + /* drop packet if IP checksum is invalid */ + if (Ipv4_packet::calculate_checksum(ip) != ip.checksum()) { + if (_verbose) { + log("bad IP checksum"); } + return; + } + /* select IP sub-protocol */ + switch (ip.protocol()) { + case Ipv4_packet::Protocol::ICMP: _handle_icmp(ip, ip_size); + default: ; } +} + + +void Main::_handle_icmp_echo_reply(Ipv4_packet &ip, + Icmp_packet &icmp, + size_t icmp_data_sz) +{ + /* check IP source */ + if (ip.src() != _dst_ip) { + if (_verbose) { + log("bad IP source"); } + return; + } + /* check ICMP code */ + if (icmp.code() != Icmp_packet::Code::ECHO_REPLY) { + if (_verbose) { + log("bad ICMP type/code"); } + return; + } + /* check ICMP identifier */ + uint16_t const icmp_id = icmp.query_id(); + if (icmp_id != ICMP_ID) { + if (_verbose) { + log("bad ICMP identifier"); } + return; + } + /* check ICMP sequence number */ + uint16_t const icmp_seq = icmp.query_seq(); + if (icmp_seq != _icmp_seq) { + if (_verbose) { + log("bad ICMP sequence number"); } + return; + } + /* check ICMP data size */ + if (icmp_data_sz != _icmp_data_sz) { + if (_verbose) { + log("bad ICMP data size"); } + return; + } + /* check ICMP data */ + struct Data { char chr[0]; }; + Data &data = icmp.data(_icmp_data_sz); + char chr = 'a'; + for (addr_t chr_id = 0; chr_id < icmp_data_sz; chr_id++) { + if (data.chr[chr_id] != chr) { + if (_verbose) { + log("bad ICMP data"); } + return; + } + chr = chr < 'z' ? chr + 1 : 'a'; + } + /* calculate time since the request was sent */ + unsigned long time_us = _timer.curr_time().trunc_to_plain_us().value - _send_time.value; + unsigned long const time_ms = time_us / 1000UL; + time_us = time_us - time_ms * 1000UL; + + /* print success message */ + log(icmp_data_sz + sizeof(Icmp_packet), " bytes from ", ip.src(), + ": icmp_seq=", icmp_seq, " ttl=", (unsigned long)IPV4_TIME_TO_LIVE, + " time=", time_ms, ".", time_us ," ms"); + + /* raise ICMP sequence number and check exit condition */ + _icmp_seq++; + _count--; + if (!_count) { + _env.parent().exit(0); } +} + + +void Main::_handle_icmp_dst_unreachbl(Ipv4_packet &ip, + Icmp_packet &icmp, + size_t icmp_data_sz) +{ + /* drop packet if embedded IP checksum is invalid */ + Ipv4_packet &embed_ip = icmp.data(icmp_data_sz); + if (Ipv4_packet::calculate_checksum(embed_ip) != embed_ip.checksum()) { + if (_verbose) { + log("bad IP checksum in payload of ICMP error"); } + return; + } + /* drop packet if the ICMP error is not about ICMP */ + if (embed_ip.protocol() != Ipv4_packet::Protocol::ICMP) { + if (_verbose) { + log("bad IP protocol in payload of ICMP error"); } + return; + } + /* drop packet if embedded ICMP identifier is invalid */ + size_t const embed_icmp_sz = embed_ip.total_length() - sizeof(Ipv4_packet); + Icmp_packet &embed_icmp = *embed_ip.data(embed_icmp_sz); + if (embed_icmp.query_id() != ICMP_ID) { + if (_verbose) { + log("bad ICMP identifier in payload of ICMP error"); } + return; + } + /* drop packet if embedded ICMP sequence number is invalid */ + uint16_t const embed_icmp_seq = embed_icmp.query_seq(); + if (embed_icmp_seq != _icmp_seq) { + if (_verbose) { + log("bad ICMP sequence number in payload of ICMP error"); } + return; + } + log("From ", ip.src(), " icmp_seq=", embed_icmp_seq, " Destination Unreachable"); +} + + +void Main::_handle_icmp(Ipv4_packet &ip, + size_t const ip_size) +{ + /* drop packet if ICMP checksum is invalid */ + size_t const icmp_sz = ip_size - sizeof(Ipv4_packet); + Icmp_packet &icmp = *ip.data(icmp_sz); + size_t const icmp_data_sz = icmp_sz - sizeof(Icmp_packet); + if (icmp.calc_checksum(icmp_data_sz) != icmp.checksum()) { + if (_verbose) { + log("bad ICMP checksum"); } + return; + } + /* select ICMP type */ + switch (icmp.type()) { + case Icmp_packet::Type::ECHO_REPLY: _handle_icmp_echo_reply(ip, icmp, icmp_data_sz); break; + case Icmp_packet::Type::DST_UNREACHABLE: _handle_icmp_dst_unreachbl(ip, icmp, icmp_data_sz); break; + default: + if (_verbose) { + log("bad ICMP type"); } + return; + } +} + + +void Main::_handle_arp(Ethernet_frame ð, + size_t const eth_size) +{ + /* check ARP protocol- and hardware address type */ + Arp_packet &arp = *eth.data(eth_size - sizeof(Ethernet_frame)); + if (!arp.ethernet_ipv4()) { + error("ARP for unknown protocol"); } + + /* check ARP operation */ + switch (arp.opcode()) { + case Arp_packet::REPLY: + + /* check whether we waited for this ARP reply */ + if (_dst_mac != Mac_address() || arp.src_ip() != _dst_ip) { + return; } + + /* set destination MAC address and retry to ping */ + _dst_mac = arp.src_mac(); + _send_ping(); + return; + + case Arp_packet::REQUEST: + + /* check whether the ARP request targets us */ + if (arp.dst_ip() != _src_ip) { + return; } + + _send_arp_reply(eth, arp); + + default: ; } +} + + +void Main::_ready_to_submit() +{ + while (_sink().packet_avail()) { + + Packet_descriptor const pkt = _sink().get_packet(); + if (!pkt.size()) { + continue; } + + _handle_eth(_sink().packet_content(pkt), pkt.size(), pkt); + + if (!_sink().ready_to_ack()) { + error("ack state FULL"); + return; + } + _sink().acknowledge_packet(pkt); + } +} + + +void Main::_ready_to_ack() +{ + while (_source().ack_avail()) { + _source().release_packet(_source().get_acked_packet()); } +} + + +void Main::_send_arp_reply(Ethernet_frame &req_eth, + Arp_packet &req_arp) +{ + enum { + ETH_HDR_SZ = sizeof(Ethernet_frame), + ETH_DAT_SZ = sizeof(Arp_packet) + ETH_HDR_SZ >= Ethernet_frame::MIN_SIZE ? + sizeof(Arp_packet) : + Ethernet_frame::MIN_SIZE - ETH_HDR_SZ, + ETH_CRC_SZ = sizeof(uint32_t), + PKT_SIZE = ETH_HDR_SZ + ETH_DAT_SZ + ETH_CRC_SZ, + }; + _send(PKT_SIZE, [&] (void *pkt_base) { + + /* write Ethernet header */ + Ethernet_frame ð = *reinterpret_cast(pkt_base); + eth.dst(req_eth.src()); + eth.src(_src_mac); + eth.type(Ethernet_frame::Type::ARP); + + /* write ARP header */ + Arp_packet &arp = *eth.data(PKT_SIZE - sizeof(Ethernet_frame)); + arp.hardware_address_type(Arp_packet::ETHERNET); + arp.protocol_address_type(Arp_packet::IPV4); + arp.hardware_address_size(sizeof(Mac_address)); + arp.protocol_address_size(sizeof(Ipv4_address)); + arp.opcode(Arp_packet::REPLY); + arp.src_mac(_src_mac); + arp.src_ip(_src_ip); + arp.dst_mac(req_eth.src()); + arp.dst_ip(req_arp.src_ip()); + }); +} + + +void Main::_broadcast_arp_request() +{ + enum { + ETH_HDR_SZ = sizeof(Ethernet_frame), + ETH_DAT_SZ = sizeof(Arp_packet) + ETH_HDR_SZ >= Ethernet_frame::MIN_SIZE ? + sizeof(Arp_packet) : + Ethernet_frame::MIN_SIZE - ETH_HDR_SZ, + ETH_CRC_SZ = sizeof(uint32_t), + PKT_SIZE = ETH_HDR_SZ + ETH_DAT_SZ + ETH_CRC_SZ, + }; + _send(PKT_SIZE, [&] (void *pkt_base) { + + /* write Ethernet header */ + Ethernet_frame ð = *reinterpret_cast(pkt_base); + eth.dst(Mac_address(0xff)); + eth.src(_src_mac); + eth.type(Ethernet_frame::Type::ARP); + + /* write ARP header */ + Arp_packet &arp = *eth.data(PKT_SIZE - sizeof(Ethernet_frame)); + arp.hardware_address_type(Arp_packet::ETHERNET); + arp.protocol_address_type(Arp_packet::IPV4); + arp.hardware_address_size(sizeof(Mac_address)); + arp.protocol_address_size(sizeof(Ipv4_address)); + arp.opcode(Arp_packet::REQUEST); + arp.src_mac(_src_mac); + arp.src_ip(_src_ip); + arp.dst_mac(Mac_address(0xff)); + arp.dst_ip(_dst_ip); + }); +} + + +void Main::_send_ping(Duration) +{ + if (_dst_mac == Mac_address()) { + _broadcast_arp_request(); + return; + } + size_t const buf_sz = sizeof(Ethernet_frame) + sizeof(Ipv4_packet) + + sizeof(Icmp_packet) + _icmp_data_sz; + + _send(buf_sz, [&] (void *pkt_base) { + + /* create ETH header */ + Size_guard size(buf_sz); + size.add(sizeof(Ethernet_frame)); + Ethernet_frame ð = *reinterpret_cast(pkt_base); + eth.dst(_dst_mac); + eth.src(_src_mac); + eth.type(Ethernet_frame::Type::IPV4); + + /* create IP header */ + size_t const ip_off = size.curr(); + Ipv4_packet &ip = *eth.data(size.left()); + size.add(sizeof(Ipv4_packet)); + ip.header_length(sizeof(Ipv4_packet) / 4); + ip.version(4); + ip.diff_service(0); + ip.ecn(0); + ip.identification(0); + ip.flags(0); + ip.fragment_offset(0); + ip.time_to_live(IPV4_TIME_TO_LIVE); + ip.protocol(Ipv4_packet::Protocol::ICMP); + ip.src(_src_ip); + ip.dst(_dst_ip); + + /* create ICMP header */ + Icmp_packet &icmp = *ip.data(size.left()); + size.add(sizeof(Icmp_packet) + _icmp_data_sz); + icmp.type(Icmp_packet::Type::ECHO_REQUEST); + icmp.code(Icmp_packet::Code::ECHO_REQUEST); + icmp.query_id(ICMP_ID); + icmp.query_seq(_icmp_seq); + + /* fill ICMP data with characters from 'a' to 'w' */ + struct Data { char chr[0]; }; + Data &data = icmp.data(_icmp_data_sz); + char chr = 'a'; + for (addr_t chr_id = 0; chr_id < _icmp_data_sz; chr_id++) { + data.chr[chr_id] = chr; + chr = chr < 'z' ? chr + 1 : 'a'; + } + /* fill in header values that require the packet to be complete */ + icmp.checksum(icmp.calc_checksum(_icmp_data_sz)); + ip.total_length(size.curr() - ip_off); + ip.checksum(Ipv4_packet::calculate_checksum(ip)); + }); + _send_time = _timer.curr_time().trunc_to_plain_us(); +} + + +void Component::construct(Env &env) +{ + /* XXX execute constructors of global statics */ + env.exec_static_constructors(); + + static Main main(env); +} diff --git a/repos/os/src/app/ping/size_guard.h b/repos/os/src/app/ping/size_guard.h new file mode 100644 index 0000000000..bb2205db18 --- /dev/null +++ b/repos/os/src/app/ping/size_guard.h @@ -0,0 +1,43 @@ +/* + * \brief Utility to ensure that a size value doesn't exceed a limit + * \author Martin Stein + * \date 2016-08-24 + */ + +/* + * Copyright (C) 2016-2017 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. + */ + +#ifndef _SIZE_GUARD_H_ +#define _SIZE_GUARD_H_ + +/* Genode includes */ +#include + +template +class Size_guard +{ + private: + + Genode::size_t _curr { 0 }; + Genode::size_t const _max; + + public: + + Size_guard(Genode::size_t max) : _max(max) { } + + void add(Genode::size_t size) + { + Genode::size_t const new_size = _curr + size; + if (new_size > _max) { throw EXCEPTION(); } + _curr = new_size; + } + + Genode::size_t curr() const { return _curr; } + Genode::size_t left() const { return _max - _curr; } +}; + +#endif /* _SIZE_GUARD_H_ */ diff --git a/repos/os/src/app/ping/target.mk b/repos/os/src/app/ping/target.mk new file mode 100644 index 0000000000..f212a61f0f --- /dev/null +++ b/repos/os/src/app/ping/target.mk @@ -0,0 +1,9 @@ +TARGET = ping + +LIBS += base net + +SRC_CC += main.cc + +INC_DIR += $(PRG_DIR) + +CONFIG_XSD = config.xsd