diff --git a/repos/os/run/nic_router_flood.run b/repos/os/run/nic_router_flood.run new file mode 100644 index 0000000000..1bd87f4281 --- /dev/null +++ b/repos/os/run/nic_router_flood.run @@ -0,0 +1,197 @@ +# +# Build +# + +if {![have_include power_on/qemu] || + [have_spec foc] || [have_spec odroid_xu] || + [expr [have_spec imx53] && [have_spec trustzone]]} { + + puts "Run script is not supported on this platform." + exit 0 +} + + +set build_components { + core + init + drivers/timer + drivers/nic + server/nic_router + test/net_flood + app/ping +} + +proc gpio_drv { } { if {[have_spec rpi] && [have_spec hw]} { return hw_gpio_drv } + if {[have_spec rpi] && [have_spec foc]} { return foc_gpio_drv } + return gpio_drv } + +proc good_dst_ip { } { return "10.0.2.2" } +proc bad_dst_ip { } { return "10.0.0.123" } + +lappend_if [have_spec gpio] build_components drivers/gpio + +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_if [have_spec gpio] config " + + + + + " + +append config { + + + + + + + + + + } [nic_drv_config] { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +# +# Boot modules +# + +# generic modules +append boot_modules { + core init + timer + } [nic_drv_binary] { + test-net_flood + ld.lib.so + nic_router + ping +} + +# platform-specific modules +lappend_if [have_spec linux] boot_modules fb_sdl +lappend_if [have_spec gpio] boot_modules [gpio_drv] + +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" 60 diff --git a/repos/os/src/test/net_flood/config.xsd b/repos/os/src/test/net_flood/config.xsd new file mode 100644 index 0000000000..e558a90718 --- /dev/null +++ b/repos/os/src/test/net_flood/config.xsd @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/os/src/test/net_flood/dhcp_client.cc b/repos/os/src/test/net_flood/dhcp_client.cc new file mode 100644 index 0000000000..ceda41604e --- /dev/null +++ b/repos/os/src/test/net_flood/dhcp_client.cc @@ -0,0 +1,267 @@ +/* + * \brief DHCP client state model + * \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. + */ + +/* local includes */ +#include +#include +#include + +/* Genode includes */ +#include + +enum { PKT_SIZE = 1024 }; + +struct Send_buffer_too_small : Genode::Exception { }; +struct Bad_send_dhcp_args : Genode::Exception { }; + +using namespace Genode; +using namespace Net; +using Message_type = Dhcp_packet::Message_type; +using Dhcp_options = Dhcp_packet::Options_aggregator; + + +/*************** + ** Utilities ** + ***************/ + +void append_param_req_list(Dhcp_options &dhcp_opts) +{ + dhcp_opts.append_param_req_list([&] (Dhcp_options::Parameter_request_list_data &data) { + data.append_param_req(); + data.append_param_req(); + data.append_param_req(); + data.append_param_req(); + data.append_param_req(); + data.append_param_req(); + }); +} + + +/***************** + ** Dhcp_client ** + *****************/ + +Dhcp_client::Dhcp_client(Genode::Allocator &alloc, + Timer::Connection &timer, + Nic &nic, + Dhcp_client_handler &handler) +: + _alloc (alloc), + _timeout (timer, *this, &Dhcp_client::_handle_timeout), + _nic (nic), + _handler (handler) +{ + _discover(); +} + + +void Dhcp_client::_discover() +{ + _set_state(State::SELECT, _discover_timeout); + _send(Message_type::DISCOVER, Ipv4_address(), Ipv4_address(), + Ipv4_address()); +} + + +void Dhcp_client::_rerequest(State next_state) +{ + _set_state(next_state, _rerequest_timeout(2)); + Ipv4_address const client_ip = _handler.ip_config().interface.address; + _send(Message_type::REQUEST, client_ip, Ipv4_address(), client_ip); +} + + +void Dhcp_client::_set_state(State state, Microseconds timeout) +{ + _state = state; + _timeout.schedule(timeout); +} + + +Microseconds Dhcp_client::_rerequest_timeout(unsigned lease_time_div_log2) +{ + /* FIXME limit the time because of shortcomings in timeout framework */ + enum { MAX_TIMEOUT_SEC = 3600 }; + unsigned long timeout_sec = _lease_time_sec >> lease_time_div_log2; + + if (timeout_sec > MAX_TIMEOUT_SEC) { + timeout_sec = MAX_TIMEOUT_SEC; + warning("Had to prune the state timeout of DHCP client"); + } + return Microseconds(timeout_sec * 1000UL * 1000UL); +} + + +void Dhcp_client::_handle_timeout(Duration) +{ + switch (_state) { + case State::BOUND: _rerequest(State::RENEW); break; + case State::RENEW: _rerequest(State::REBIND); break; + default: _discover(); + } +} + + +void Dhcp_client::handle_eth(Ethernet_frame ð, Size_guard &size_guard) +{ + if (eth.dst() != _nic.mac() && + eth.dst() != Mac_address(0xff)) + { + throw Drop_packet_inform("DHCP client expects Ethernet targeting the router"); + } + Ipv4_packet &ip = eth.data(size_guard); + if (ip.protocol() != Ipv4_packet::Protocol::UDP) { + throw Drop_packet_inform("DHCP client expects UDP packet"); } + + Udp_packet &udp = ip.data(size_guard); + if (!Dhcp_packet::is_dhcp(&udp)) { + throw Drop_packet_inform("DHCP client expects DHCP packet"); } + + Dhcp_packet &dhcp = udp.data(size_guard); + if (dhcp.op() != Dhcp_packet::REPLY) { + throw Drop_packet_inform("DHCP client expects DHCP reply"); } + + if (dhcp.client_mac() != _nic.mac()) { + throw Drop_packet_inform("DHCP client expects DHCP targeting the router"); } + + try { _handle_dhcp_reply(dhcp); } + catch (Dhcp_packet::Option_not_found) { + throw Drop_packet_inform("DHCP client misses DHCP option"); } +} + + +void Dhcp_client::_handle_dhcp_reply(Dhcp_packet &dhcp) +{ + Message_type const msg_type = + dhcp.option().value(); + + switch (_state) { + case State::SELECT: + + if (msg_type != Message_type::OFFER) { + throw Drop_packet_inform("DHCP client expects an offer"); + } + _set_state(State::REQUEST, _request_timeout); + _send(Message_type::REQUEST, Ipv4_address(), + dhcp.option().value(), + dhcp.yiaddr()); + break; + + case State::REQUEST: + { + if (msg_type != Message_type::ACK) { + throw Drop_packet_inform("DHCP client expects an acknowledgement"); + } + _lease_time_sec = dhcp.option().value(); + _set_state(State::BOUND, _rerequest_timeout(1)); + Ipv4_address dns_server; + try { dns_server = dhcp.option().value(); } + catch (Dhcp_packet::Option_not_found) { } + + Ipv4_config ip_config( + Ipv4_address_prefix( + dhcp.yiaddr(), + dhcp.option().value()), + dhcp.option().value(), + dns_server); + + _handler.ip_config(ip_config); + break; + } + case State::RENEW: + case State::REBIND: + + if (msg_type != Message_type::ACK) { + throw Drop_packet_inform("DHCP client expects an acknowledgement"); + } + _set_state(State::BOUND, _rerequest_timeout(1)); + _lease_time_sec = dhcp.option().value(); + break; + + default: throw Drop_packet_inform("DHCP client doesn't expect a packet"); + } +} + + +void Dhcp_client::_send(Message_type msg_type, + Ipv4_address client_ip, + Ipv4_address server_ip, + Ipv4_address requested_ip) +{ + _nic.send(PKT_SIZE, [&] (void *pkt_base, Size_guard &size_guard) { + + /* create ETH header of the request */ + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(Mac_address(0xff)); + eth.src(_nic.mac()); + eth.type(Ethernet_frame::Type::IPV4); + + /* create IP header of the request */ + enum { IPV4_TIME_TO_LIVE = 64 }; + size_t const ip_off = size_guard.head_size(); + Ipv4_packet &ip = eth.construct_at_data(size_guard); + ip.header_length(sizeof(Ipv4_packet) / 4); + ip.version(4); + ip.time_to_live(IPV4_TIME_TO_LIVE); + ip.protocol(Ipv4_packet::Protocol::UDP); + ip.src(client_ip); + ip.dst(Ipv4_address(0xff)); + + /* create UDP header of the request */ + size_t const udp_off = size_guard.head_size(); + Udp_packet &udp = ip.construct_at_data(size_guard); + udp.src_port(Port(Dhcp_packet::BOOTPC)); + udp.dst_port(Port(Dhcp_packet::BOOTPS)); + + /* create mandatory DHCP fields of the request */ + size_t const dhcp_off = size_guard.head_size(); + Dhcp_packet &dhcp = udp.construct_at_data(size_guard); + dhcp.op(Dhcp_packet::REQUEST); + dhcp.htype(Dhcp_packet::Htype::ETH); + dhcp.hlen(sizeof(Mac_address)); + dhcp.ciaddr(client_ip); + dhcp.client_mac(_nic.mac()); + dhcp.default_magic_cookie(); + + /* append DHCP option fields to the request */ + Dhcp_options dhcp_opts(dhcp, size_guard); + dhcp_opts.append_option(msg_type); + switch (msg_type) { + case Message_type::DISCOVER: + append_param_req_list(dhcp_opts); + dhcp_opts.append_option(_nic.mac()); + dhcp_opts.append_option(PKT_SIZE - dhcp_off); + break; + + case Message_type::REQUEST: + append_param_req_list(dhcp_opts); + dhcp_opts.append_option(_nic.mac()); + dhcp_opts.append_option(PKT_SIZE - dhcp_off); + if (_state == State::REQUEST) { + dhcp_opts.append_option(requested_ip); + dhcp_opts.append_option(server_ip); + } + break; + + default: + throw Bad_send_dhcp_args(); + } + dhcp_opts.append_option(); + + /* fill in header values that need the packet to be complete already */ + udp.length(size_guard.head_size() - udp_off); + udp.update_checksum(ip.src(), ip.dst()); + ip.total_length(size_guard.head_size() - ip_off); + ip.update_checksum(); + }); +} diff --git a/repos/os/src/test/net_flood/dhcp_client.h b/repos/os/src/test/net_flood/dhcp_client.h new file mode 100644 index 0000000000..0f52d6ba9d --- /dev/null +++ b/repos/os/src/test/net_flood/dhcp_client.h @@ -0,0 +1,105 @@ +/* + * \brief DHCP client state model + * \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 _DHCP_CLIENT_H_ +#define _DHCP_CLIENT_H_ + +/* Genode includes */ +#include +#include + +namespace Net { + + /* external definition */ + class Nic_peer; + class Ipv4_config; + class Ethernet_frame; + + /* local definition */ + class Dhcp_client; + class Dhcp_client_handler; + class Drop_packet_inform; +} + + +struct Net::Drop_packet_inform : Genode::Exception +{ + char const *msg; + + Drop_packet_inform(char const *msg) : msg(msg) { } +}; + + +class Net::Dhcp_client_handler +{ + public: + + virtual void ip_config(Ipv4_config const &ip_config) = 0; + + virtual Ipv4_config const &ip_config() const = 0; + + virtual ~Dhcp_client_handler() { } +}; + + +class Net::Dhcp_client +{ + private: + + enum class State + { + INIT = 0, SELECT = 1, REQUEST = 2, BOUND = 3, RENEW = 4, REBIND = 5 + }; + + enum { DISCOVER_TIMEOUT_SEC = 2 }; + enum { REQUEST_TIMEOUT_SEC = 2 }; + + Genode::Allocator &_alloc; + State _state { State::INIT }; + Timer::One_shot_timeout _timeout; + unsigned long _lease_time_sec = 0; + Genode::Microseconds const _discover_timeout { DISCOVER_TIMEOUT_SEC * 1000 * 1000 }; + Genode::Microseconds const _request_timeout { REQUEST_TIMEOUT_SEC * 1000 * 1000 }; + Nic &_nic; + Dhcp_client_handler &_handler; + + void _handle_dhcp_reply(Dhcp_packet &dhcp); + + void _handle_timeout(Genode::Duration); + + void _rerequest(State next_state); + + Genode::Microseconds _rerequest_timeout(unsigned lease_time_div_log2); + + void _set_state(State state, Genode::Microseconds timeout); + + void _send(Dhcp_packet::Message_type msg_type, + Ipv4_address client_ip, + Ipv4_address server_ip, + Ipv4_address requested_ip); + + void _discover(); + + public: + + Dhcp_client(Genode::Allocator &alloc, + Timer::Connection &timer, + Nic &nic, + Dhcp_client_handler &handler); + + void handle_eth(Ethernet_frame ð, + Size_guard &size_guard); + +}; + +#endif /* _DHCP_CLIENT_H_ */ diff --git a/repos/os/src/test/net_flood/ipv4_address_prefix.cc b/repos/os/src/test/net_flood/ipv4_address_prefix.cc new file mode 100644 index 0000000000..bf46d115e3 --- /dev/null +++ b/repos/os/src/test/net_flood/ipv4_address_prefix.cc @@ -0,0 +1,103 @@ +/* + * \brief Ipv4 address combined with a subnet prefix length + * \author Martin Stein + * \date 2017-10-12 + */ + +/* + * Copyright (C) 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. + */ + +/* local includes */ +#include + +using namespace Genode; +using namespace Net; + + +Ipv4_address Ipv4_address_prefix::subnet_mask() const +{ + Ipv4_address result; + if (prefix >= 8) { + + result.addr[0] = 0xff; + + if (prefix >= 16) { + + result.addr[1] = 0xff; + + if (prefix >= 24) { + + result.addr[2] = 0xff; + result.addr[3] = 0xff << (32 - prefix); + } else { + result.addr[2] = 0xff << (24 - prefix); + } + } else { + result.addr[1] = 0xff << (16 - prefix); + } + } else { + result.addr[0] = 0xff << (8 - prefix); + } + return result; +} + + +void Ipv4_address_prefix::print(Genode::Output &output) const +{ + Genode::print(output, address, "/", prefix); +} + + +bool Ipv4_address_prefix::prefix_matches(Ipv4_address const &ip) const +{ + uint8_t prefix_left = prefix; + uint8_t byte = 0; + for (; prefix_left >= 8; prefix_left -= 8, byte++) { + if (ip.addr[byte] != address.addr[byte]) { + return false; } + } + if (prefix_left == 0) { + return true; } + + uint8_t const mask = ~(0xff >> prefix_left); + return !((ip.addr[byte] ^ address.addr[byte]) & mask); +} + + +Ipv4_address Ipv4_address_prefix::broadcast_address() const +{ + Ipv4_address result = address; + Ipv4_address const mask = subnet_mask(); + for (unsigned i = 0; i < 4; i++) { + result.addr[i] |= ~mask.addr[i]; + } + return result; +} + + +Ipv4_address_prefix::Ipv4_address_prefix(Ipv4_address address, + Ipv4_address subnet_mask) +: + address(address), prefix(0) +{ + Genode::uint8_t rest; + if (subnet_mask.addr[0] != 0xff) { + rest = subnet_mask.addr[0]; + prefix = 0; + } else if (subnet_mask.addr[1] != 0xff) { + rest = subnet_mask.addr[1]; + prefix = 8; + } else if (subnet_mask.addr[2] != 0xff) { + rest = subnet_mask.addr[2]; + prefix = 16; + } else { + rest = subnet_mask.addr[3]; + prefix = 24; + } + for (Genode::uint8_t mask = 1 << 7; rest & mask; mask >>= 1) + prefix++; +} diff --git a/repos/os/src/test/net_flood/ipv4_address_prefix.h b/repos/os/src/test/net_flood/ipv4_address_prefix.h new file mode 100644 index 0000000000..4fb378332c --- /dev/null +++ b/repos/os/src/test/net_flood/ipv4_address_prefix.h @@ -0,0 +1,82 @@ +/* + * \brief Ipv4 address combined with a subnet prefix length + * \author Martin Stein + * \date 2017-10-12 + */ + +/* + * Copyright (C) 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 _IPV4_ADDRESS_PREFIX_H_ +#define _IPV4_ADDRESS_PREFIX_H_ + +/* Genode includes */ +#include + +namespace Net { class Ipv4_address_prefix; } + + +struct Net::Ipv4_address_prefix +{ + Ipv4_address address { }; + Genode::uint8_t prefix; + + Ipv4_address_prefix(Ipv4_address address, + Ipv4_address subnet_mask); + + Ipv4_address_prefix() : prefix(32) { } + + bool valid() const { return address.valid() || prefix == 0; } + + void print(Genode::Output &output) const; + + bool prefix_matches(Ipv4_address const &ip) const; + + Ipv4_address subnet_mask() const; + + Ipv4_address broadcast_address() const; + + bool operator != (Ipv4_address_prefix const &other) const + { + return prefix != other.prefix || + address != other.address; + } +}; + + +namespace Genode { + + inline size_t ascii_to(char const *s, Net::Ipv4_address_prefix &result); +} + + +Genode::size_t Genode::ascii_to(char const *s, Net::Ipv4_address_prefix &result) +{ + /* read the leading IPv4 address, fail if there's no address */ + Net::Ipv4_address_prefix buf; + size_t read_len = ascii_to(s, buf.address); + if (!read_len) { + return 0; } + + /* check for the following slash */ + s += read_len; + if (*s != '/') { + return 0; } + read_len++; + s++; + + /* read the prefix, fail if there's no prefix */ + size_t prefix_len = ascii_to_unsigned(s, buf.prefix, 10); + if (!prefix_len) { + return 0; } + + /* fill result and return read length */ + result = buf; + return read_len + prefix_len; +} + +#endif /* _IPV4_ADDRESS_PREFIX_H_ */ diff --git a/repos/os/src/test/net_flood/ipv4_config.cc b/repos/os/src/test/net_flood/ipv4_config.cc new file mode 100644 index 0000000000..5aea166f58 --- /dev/null +++ b/repos/os/src/test/net_flood/ipv4_config.cc @@ -0,0 +1,43 @@ +/* + * \brief IPv4 peer configuration + * \author Martin Stein + * \date 2016-08-19 + */ + +/* + * 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. + */ + +/* Genode includes */ +#include + +/* local includes */ +#include + +using namespace Genode; +using namespace Net; + +Ipv4_config::Ipv4_config(Ipv4_address_prefix interface, + Ipv4_address gateway, + Ipv4_address dns_server) +: + interface(interface), gateway(gateway), dns_server(dns_server) +{ + if (!valid && (interface_valid || gateway_valid)) { + error("Bad IP configuration"); + } +} + + +void Ipv4_config::print(Genode::Output &output) const +{ + Genode::print(output, "interface ", interface); + if (gateway.valid()) { + Genode::print(output, ", gateway ", gateway); } + + if (dns_server.valid()) { + Genode::print(output, ", DNS server ", dns_server); } +} diff --git a/repos/os/src/test/net_flood/ipv4_config.h b/repos/os/src/test/net_flood/ipv4_config.h new file mode 100644 index 0000000000..6b3f011074 --- /dev/null +++ b/repos/os/src/test/net_flood/ipv4_config.h @@ -0,0 +1,49 @@ +/* + * \brief IPv4 peer configuration + * \author Martin Stein + * \date 2016-08-19 + */ + +/* + * 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 _IPV4_CONFIG_H_ +#define _IPV4_CONFIG_H_ + +/* local includes */ +#include + +namespace Net { class Ipv4_config; } + +struct Net::Ipv4_config +{ + Ipv4_address_prefix const interface { }; + bool const interface_valid { interface.valid() }; + Ipv4_address const gateway { }; + bool const gateway_valid { gateway.valid() }; + Ipv4_address const dns_server { }; + bool const valid { interface_valid && + (!gateway_valid || + interface.prefix_matches(gateway)) }; + + Ipv4_config(Ipv4_address_prefix interface, + Ipv4_address gateway, + Ipv4_address dns_server); + + Ipv4_config() { } + + bool operator != (Ipv4_config const &other) const + { + return interface != other.interface || + gateway != other.gateway || + dns_server != other.dns_server; + } + + void print(Genode::Output &output) const; +}; + +#endif /* _IPV4_CONFIG_H_ */ diff --git a/repos/os/src/test/net_flood/main.cc b/repos/os/src/test/net_flood/main.cc new file mode 100644 index 0000000000..7ea142cb36 --- /dev/null +++ b/repos/os/src/test/net_flood/main.cc @@ -0,0 +1,355 @@ +/* + * \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 +#include +#include +#include + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Net; +using namespace Genode; + + +class Main : public Nic_handler, + public Dhcp_client_handler +{ + private: + + using Periodic_timeout = Timer::Periodic_timeout
; + + enum { IPV4_TIME_TO_LIVE = 64 }; + enum { ICMP_DATA_SIZE = 56 }; + enum { ICMP_SEQ = 1 }; + enum { SRC_PORT = 50000 }; + enum { FIRST_DST_PORT = 49152 }; + enum { LAST_DST_PORT = 65535 }; + + Env &_env; + Attached_rom_dataspace _config_rom { _env, "config" }; + Xml_node _config { _config_rom.xml() }; + Timer::Connection _timer { _env }; + Microseconds _period_us { 100 }; + Constructible _period { }; + Heap _heap { &_env.ram(), &_env.rm() }; + bool const _verbose { _config.attribute_value("verbose", false) }; + Net::Nic _nic { _env, _heap, *this, _verbose }; + Ipv4_address const _dst_ip { _config.attribute_value("dst_ip", Ipv4_address()) }; + Mac_address _dst_mac { }; + Constructible _dhcp_client { }; + Reconstructible _ip_config { _config.attribute_value("interface", Ipv4_address_prefix()), + _config.attribute_value("gateway", Ipv4_address()), + Ipv4_address() }; + Protocol const _protocol { _config.attribute_value("protocol", Protocol::ICMP) }; + Port _dst_port { FIRST_DST_PORT }; + + void _handle_arp(Ethernet_frame ð, + Size_guard &size_guard); + + void _broadcast_arp_request(Ipv4_address const &ip); + + void _send_arp_reply(Ethernet_frame &req_eth, + Arp_packet &req_arp); + + void _send_ping(Duration not_used = Duration(Microseconds(0))); + + public: + + struct Invalid_arguments : Exception { }; + + Main(Env &env); + + + /***************** + ** Nic_handler ** + *****************/ + + void handle_eth(Ethernet_frame ð, + Size_guard &size_guard) override; + + + /************************* + ** Dhcp_client_handler ** + *************************/ + + void ip_config(Ipv4_config const &ip_config) override; + + Ipv4_config const &ip_config() const override { return *_ip_config; } +}; + + +void Main::ip_config(Ipv4_config const &ip_config) +{ + if (_verbose) { + log("IP config: ", ip_config); } + + _ip_config.construct(ip_config); + _period.construct(_timer, *this, &Main::_send_ping, _period_us); +} + + +Main::Main(Env &env) : _env(env) +{ + /* exit unsuccessful if parameters are invalid */ + if (_dst_ip == Ipv4_address()) { + throw Invalid_arguments(); } + + /* if there is a static IP config, start sending pings periodically */ + if (ip_config().valid) { + _period.construct(_timer, *this, &Main::_send_ping, _period_us); } + + /* else, start the DHCP client for requesting an IP config */ + else { + _dhcp_client.construct(_heap, _timer, _nic, *this); } +} + + +void Main::handle_eth(Ethernet_frame ð, + Size_guard &size_guard) +{ + try { + /* print receipt message */ + if (_verbose) { + log("rcv ", eth); } + + if (!ip_config().valid) { + _dhcp_client->handle_eth(eth, size_guard); } + + /* drop packet if ETH does not target us */ + if (eth.dst() != _nic.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, size_guard); break; + default: ; } + } + catch (Drop_packet_inform exception) { + if (_verbose) { + log("drop packet: ", exception.msg); } + } +} + + +void Main::_handle_arp(Ethernet_frame ð, + Size_guard &size_guard) +{ + /* check ARP protocol- and hardware address type */ + Arp_packet &arp = eth.data(size_guard); + 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()) { + return; } + + if (ip_config().interface.prefix_matches(_dst_ip)) { + if (arp.src_ip() != _dst_ip) { + return; } + } else { + if (arp.src_ip() != ip_config().gateway) { + return; } + } + /* set destination MAC address and retry to ping */ + _dst_mac = arp.src_mac(); + return; + + case Arp_packet::REQUEST: + + /* check whether the ARP request targets us */ + if (arp.dst_ip() != ip_config().interface.address) { + return; } + + _send_arp_reply(eth, arp); + + default: ; } +} + + +void Main::_send_arp_reply(Ethernet_frame &req_eth, + Arp_packet &req_arp) +{ + _nic.send(sizeof(Ethernet_frame) + sizeof(Arp_packet), + [&] (void *pkt_base, Size_guard &size_guard) + { + /* write Ethernet header */ + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(req_eth.src()); + eth.src(_nic.mac()); + eth.type(Ethernet_frame::Type::ARP); + + /* write ARP header */ + Arp_packet &arp = eth.construct_at_data(size_guard); + 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(_nic.mac()); + arp.src_ip(ip_config().interface.address); + arp.dst_mac(req_eth.src()); + arp.dst_ip(req_arp.src_ip()); + }); +} + + +void Main::_broadcast_arp_request(Ipv4_address const &dst_ip) +{ + _nic.send(sizeof(Ethernet_frame) + sizeof(Arp_packet), + [&] (void *pkt_base, Size_guard &size_guard) + { + /* write Ethernet header */ + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(Mac_address(0xff)); + eth.src(_nic.mac()); + eth.type(Ethernet_frame::Type::ARP); + + /* write ARP header */ + Arp_packet &arp = eth.construct_at_data(size_guard); + 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(_nic.mac()); + arp.src_ip(ip_config().interface.address); + arp.dst_mac(Mac_address(0xff)); + arp.dst_ip(dst_ip); + }); +} + + +void Main::_send_ping(Duration) +{ + /* if we do not yet know the Ethernet destination, request it via ARP */ + if (_dst_mac == Mac_address()) { + if (ip_config().interface.prefix_matches(_dst_ip)) { + _broadcast_arp_request(_dst_ip); } + else { + _broadcast_arp_request(ip_config().gateway); } + return; + } + _nic.send(sizeof(Ethernet_frame) + sizeof(Ipv4_packet) + + sizeof(Icmp_packet) + ICMP_DATA_SIZE, + [&] (void *pkt_base, Size_guard &size_guard) + { + /* create ETH header */ + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(_dst_mac); + eth.src(_nic.mac()); + eth.type(Ethernet_frame::Type::IPV4); + + /* create IP header */ + size_t const ip_off = size_guard.head_size(); + Ipv4_packet &ip = eth.construct_at_data(size_guard); + ip.header_length(sizeof(Ipv4_packet) / 4); + ip.version(4); + ip.time_to_live(IPV4_TIME_TO_LIVE); + ip.src(ip_config().interface.address); + ip.dst(_dst_ip); + + /* select IP-encapsulated protocol */ + switch (_protocol) { + case Protocol::ICMP: + { + /* adapt IP header to ICMP */ + ip.protocol(Ipv4_packet::Protocol::ICMP); + + /* create ICMP header */ + Icmp_packet &icmp = ip.construct_at_data(size_guard); + icmp.type(Icmp_packet::Type::ECHO_REQUEST); + icmp.code(Icmp_packet::Code::ECHO_REQUEST); + icmp.query_id(_dst_port.value); + icmp.query_seq(ICMP_SEQ); + + /* finish ICMP header */ + icmp.update_checksum(ICMP_DATA_SIZE); + + /* prepare next ICMP ping */ + if (_dst_port.value == LAST_DST_PORT) { + _dst_port.value = FIRST_DST_PORT; } + else { + _dst_port.value++; } + break; + } + case Protocol::UDP: + { + /* adapt IP header to UDP */ + ip.protocol(Ipv4_packet::Protocol::UDP); + + /* create UDP header */ + size_t const udp_off = size_guard.head_size(); + Udp_packet &udp = ip.construct_at_data(size_guard); + udp.src_port(Port(SRC_PORT)); + udp.dst_port(_dst_port); + + /* finish UDP header */ + udp.length(size_guard.head_size() - udp_off); + udp.update_checksum(ip.src(), ip.dst()); + + /* prepare next ping */ + if (_dst_port.value == LAST_DST_PORT) { + _dst_port.value = FIRST_DST_PORT; } + else { + _dst_port.value++; } + break; + } + case Protocol::TCP: + { + /* adapt IP header to TCP */ + ip.protocol(Ipv4_packet::Protocol::TCP); + + /* create TCP header */ + size_t const tcp_off = size_guard.head_size(); + Tcp_packet &tcp = ip.construct_at_data(size_guard); + tcp.src_port(Port(SRC_PORT)); + tcp.dst_port(_dst_port); + + /* finish TCP header */ + tcp.update_checksum(ip.src(), ip.dst(), size_guard.head_size() - tcp_off); + + /* prepare next ping */ + if (_dst_port.value == LAST_DST_PORT) { + _dst_port.value = FIRST_DST_PORT; } + else { + _dst_port.value++; } + break; + } + } + /* finish IP header */ + ip.total_length(size_guard.head_size() - ip_off); + ip.update_checksum(); + }); +} + + +void Component::construct(Env &env) { static Main main(env); } diff --git a/repos/os/src/test/net_flood/nic.cc b/repos/os/src/test/net_flood/nic.cc new file mode 100644 index 0000000000..dd835d63e3 --- /dev/null +++ b/repos/os/src/test/net_flood/nic.cc @@ -0,0 +1,46 @@ +/* + * \brief NIC connection wrapper for a more convenient interface + * \author Martin Stein + * \date 2018-04-16 + */ + +/* + * 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 + +using namespace Net; +using namespace Genode; + + +void Net::Nic::_ready_to_ack() +{ + while (_source().ack_avail()) { + _source().release_packet(_source().get_acked_packet()); } +} + + +void Net::Nic::_ready_to_submit() +{ + while (_sink().packet_avail()) { + + Packet_descriptor const pkt = _sink().get_packet(); + if (!pkt.size()) { + continue; } + + Size_guard size_guard(pkt.size()); + _handler.handle_eth(Ethernet_frame::cast_from(_sink().packet_content(pkt), size_guard), + size_guard); + + if (!_sink().ready_to_ack()) { + error("ack state FULL"); + return; + } + _sink().acknowledge_packet(pkt); + } +} diff --git a/repos/os/src/test/net_flood/nic.h b/repos/os/src/test/net_flood/nic.h new file mode 100644 index 0000000000..2f63a8c092 --- /dev/null +++ b/repos/os/src/test/net_flood/nic.h @@ -0,0 +1,129 @@ +/* + * \brief NIC connection wrapper for a more convenient interface + * \author Martin Stein + * \date 2018-04-16 + */ + +/* + * 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. + */ + +#ifndef _NIC_H_ +#define _NIC_H_ + +/* Genode includes */ +#include +#include +#include + +namespace Genode { + + class Env; +} + +namespace Net { + + struct Nic_handler; + class Nic; + + 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>; +} + + +struct Net::Nic_handler +{ + virtual void handle_eth(Ethernet_frame ð, + Size_guard &size_guard) = 0; + + virtual ~Nic_handler() { } +}; + + +class Net::Nic +{ + private: + + using Signal_handler = Genode::Signal_handler; + + enum { PKT_SIZE = ::Nic::Packet_allocator::DEFAULT_PACKET_SIZE }; + enum { BUF_SIZE = 90 * PKT_SIZE }; + + Genode::Env &_env; + Genode::Allocator &_alloc; + Nic_handler &_handler; + bool const &_verbose; + ::Nic::Packet_allocator _pkt_alloc { &_alloc }; + ::Nic::Connection _nic { _env, &_pkt_alloc, BUF_SIZE, BUF_SIZE }; + Signal_handler _sink_ack { _env.ep(), *this, &Nic::_ack_avail }; + Signal_handler _sink_submit { _env.ep(), *this, &Nic::_ready_to_submit }; + Signal_handler _source_ack { _env.ep(), *this, &Nic::_ready_to_ack }; + Signal_handler _source_submit { _env.ep(), *this, &Nic::_packet_avail }; + Mac_address const _mac { _nic.mac_address() }; + + 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: + + Nic(Genode::Env &env, + Genode::Allocator &alloc, + Nic_handler &handler, + bool const &verbose) + : + _env (env), + _alloc (alloc), + _handler (handler), + _verbose (verbose) + { + /* 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); + } + + template + void send(Genode::size_t pkt_size, + FUNC && write_to_pkt) + { + try { + Packet_descriptor pkt = _source().alloc_packet(pkt_size); + void *pkt_base = _source().packet_content(pkt); + Size_guard size_guard(pkt_size); + write_to_pkt(pkt_base, size_guard); + _source().submit_packet(pkt); + if (_verbose) { + Size_guard size_guard(pkt_size); + try { Genode::log("snd ", Ethernet_frame::cast_from(pkt_base, size_guard)); } + catch (Size_guard::Exceeded) { Genode::log("snd ?"); } + } + } + catch (Net::Packet_stream_source::Packet_alloc_failed) { + Genode::warning("failed to allocate packet"); } + } + + + /*************** + ** Accessors ** + ***************/ + + Mac_address const &mac() const { return _mac; } +}; + + +#endif /* _NIC_H_ */ diff --git a/repos/os/src/test/net_flood/protocol.h b/repos/os/src/test/net_flood/protocol.h new file mode 100644 index 0000000000..713d7f040c --- /dev/null +++ b/repos/os/src/test/net_flood/protocol.h @@ -0,0 +1,33 @@ +/* + * \brief Supported protocols + * \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. + */ + +#ifndef __PROTOCOL_H_ +#define __PROTOCOL_H_ + +/* Genode includes */ +#include + +namespace Genode { enum class Protocol : uint16_t { ICMP, UDP, TCP }; } + +namespace Genode +{ + inline size_t ascii_to(char const *s, Protocol &result) + { + if (!strcmp(s, "icmp", 4)) { result = Protocol::ICMP; return 4; } + if (!strcmp(s, "udp", 3)) { result = Protocol::UDP; return 3; } + if (!strcmp(s, "tcp", 3)) { result = Protocol::TCP; return 3; } + return 0; + } +} + +#endif /* __PROTOCOL_H_ */ diff --git a/repos/os/src/test/net_flood/target.mk b/repos/os/src/test/net_flood/target.mk new file mode 100644 index 0000000000..9df2191a96 --- /dev/null +++ b/repos/os/src/test/net_flood/target.mk @@ -0,0 +1,10 @@ +TARGET = test-net_flood + +LIBS += base net + +SRC_CC += main.cc dhcp_client.cc ipv4_address_prefix.cc +SRC_CC += nic.cc ipv4_config.cc + +INC_DIR += $(PRG_DIR) + +CONFIG_XSD = config.xsd diff --git a/tool/autopilot.list b/tool/autopilot.list index 003929950d..43080f2a8f 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -53,6 +53,7 @@ nic_bridge nic_dump nic_loopback nic_router +nic_router_flood nic_router_uplinks noux noux_net_netcat