diff --git a/repos/os/src/server/nic_perf/README b/repos/os/src/server/nic_perf/README new file mode 100644 index 0000000000..e49bf2032f --- /dev/null +++ b/repos/os/src/server/nic_perf/README @@ -0,0 +1,46 @@ +The 'nic_perf' component is a benchmark component for the Nic and Uplink +service. It can act as a Nic/Uplink server and a Nic client. The component +periodically logs the number of transmitted/received packets and the resulting +data rate. When enabled, it transmits continuous stream of UDP packets to a +predefined receiver as a test stimulus. + + +Basics +~~~~~~ + +This is an example configuration: + +! +! +! +! +! +! +! +! + +The 'period_ms' attribute specifies the logging intervall (in milliseconds). By +default, logging is disabled. The 'count' attribute defines after how may +periods the component exits. Session policies for connecting Nic/Uplink clients +are specified by '' nodes resp. a '' node. The component +opens a single Nic connection if a '' node is provided. + +All sub-nodes comprise an optional '' node and an optional '' +node. This is an overview of their attributes: + +:interface.ip: + Optional. Specifies the own IP address. If not specified, the component will + send DHCP requests to acquire an IP. + +:interface.dhcp_client_ip: + Optional. If specified, the component responds to DHCP requests with this IP + address. + +:tx.mtu: + Optional. Sets the size of the transmitted test packets. + +:tx.to: + Mandatory. Specifies the destination IP address. + +:tx.udp_port: + Mandatory. Specifies the destination port. diff --git a/repos/os/src/server/nic_perf/dhcp_client.cc b/repos/os/src/server/nic_perf/dhcp_client.cc new file mode 100644 index 0000000000..a26de4ee66 --- /dev/null +++ b/repos/os/src/server/nic_perf/dhcp_client.cc @@ -0,0 +1,245 @@ +/* + * \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 + +/* 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(Timer::Connection &timer, + Nic_perf::Interface &interface) +: + _timeout(timer, *this, &Dhcp_client::_handle_timeout), + _interface(interface) +{ + _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 = _interface.ip(); + _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 }; + uint64_t 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 * 1000 * 1000); +} + + +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_dhcp(Dhcp_packet &dhcp, Ethernet_frame ð, Size_guard &) +{ + if (eth.dst() != _interface.mac() && + eth.dst() != Mac_address(0xff)) + { + throw Drop_packet_inform("DHCP client expects Ethernet targeting the router"); + } + + if (dhcp.client_mac() != _interface.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) { } + + _interface.ip(dhcp.yiaddr()); + log("Got IP address ", _interface.ip()); + 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) +{ + _interface.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(_interface.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(_interface.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(_interface.mac()); + dhcp_opts.append_option(PKT_SIZE - dhcp_off); + break; + + case Message_type::REQUEST: + append_param_req_list(dhcp_opts); + dhcp_opts.append_option(_interface.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/server/nic_perf/dhcp_client.h b/repos/os/src/server/nic_perf/dhcp_client.h new file mode 100644 index 0000000000..0cb8a08622 --- /dev/null +++ b/repos/os/src/server/nic_perf/dhcp_client.h @@ -0,0 +1,89 @@ +/* + * \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 Nic_perf { class Interface; } + +namespace Net { + + /* external definition */ + class Ethernet_frame; + + /* local definition */ + class Dhcp_client; + 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 +{ + 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 }; + + State _state { State::INIT }; + Timer::One_shot_timeout _timeout; + unsigned long _lease_time_sec = 0; + Genode::Microseconds const _discover_timeout { (Genode::uint64_t)DISCOVER_TIMEOUT_SEC * 1000 * 1000 }; + Genode::Microseconds const _request_timeout { (Genode::uint64_t)REQUEST_TIMEOUT_SEC * 1000 * 1000 }; + Nic_perf::Interface &_interface; + + 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(Timer::Connection &timer, + Nic_perf::Interface &interface); + + void handle_dhcp(Dhcp_packet &dhcp, + Ethernet_frame ð, + Size_guard &size_guard); + +}; + +#endif /* _DHCP_CLIENT_H_ */ diff --git a/repos/os/src/server/nic_perf/interface.cc b/repos/os/src/server/nic_perf/interface.cc new file mode 100644 index 0000000000..3c1bab3e83 --- /dev/null +++ b/repos/os/src/server/nic_perf/interface.cc @@ -0,0 +1,254 @@ +/* + * \brief Base class for Nic/Uplink session components + * \author Johannes Schlatow + * \date 2022-06-15 + */ + +/* + * Copyright (C) 2022 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 + + +void Nic_perf::Interface::_handle_eth(void * pkt_base, size_t size) +{ + try { + + Size_guard size_guard(size); + Ethernet_frame ð = Ethernet_frame::cast_from(pkt_base, size_guard); + + switch (eth.type()) { + case Ethernet_frame::Type::ARP: + _handle_arp(eth, size_guard); + break; + case Ethernet_frame::Type::IPV4: + _handle_ip(eth, size_guard); + break; + default: + ; + } + } catch (Size_guard::Exceeded) { + warning("Size guard exceeded"); + } catch (Net::Drop_packet_inform e) { + error(e.msg); + } + + _stats.rx_packet(size); +} + + +void Nic_perf::Interface::_handle_arp(Ethernet_frame & eth, Size_guard & size_guard) +{ + Arp_packet &arp = eth.data(size_guard); + if (!arp.ethernet_ipv4()) + return; + + Ipv4_address old_src_ip { }; + + switch (arp.opcode()) { + case Arp_packet::REPLY: + _generator.handle_arp_reply(arp); + + break; + + case Arp_packet::REQUEST: + /* check whether the request targets us */ + if (arp.dst_ip() != _ip) + return; + + old_src_ip = arp.src_ip(); + arp.opcode(Arp_packet::REPLY); + arp.dst_mac(arp.src_mac()); + arp.src_mac(_mac); + arp.src_ip(arp.dst_ip()); + arp.dst_ip(old_src_ip); + eth.dst(arp.dst_mac()); + eth.src(_mac); + + send(size_guard.total_size(), [&] (void * pkt_base, Size_guard & size_guard) { + memcpy(pkt_base, (void*)ð, size_guard.total_size()); + }); + break; + + default: + ; + } +} + + +void Nic_perf::Interface::_handle_ip(Ethernet_frame & eth, Size_guard & size_guard) +{ + Ipv4_packet &ip = eth.data(size_guard); + if (ip.protocol() == Ipv4_packet::Protocol::UDP) { + + Udp_packet &udp = ip.data(size_guard); + if (Dhcp_packet::is_dhcp(&udp)) { + Dhcp_packet &dhcp = udp.data(size_guard); + switch (dhcp.op()) { + case Dhcp_packet::REQUEST: + _handle_dhcp_request(eth, dhcp); + break; + case Dhcp_packet::REPLY: + if (_dhcp_client.constructed()) { + _dhcp_client->handle_dhcp(dhcp, eth, size_guard); + } + break; + } + } + } +} + + +void Nic_perf::Interface::_handle_dhcp_request(Ethernet_frame & eth, Dhcp_packet & dhcp) +{ + Dhcp_packet::Message_type const msg_type = + dhcp.option().value(); + + switch (msg_type) { + case Dhcp_packet::Message_type::DISCOVER: + _send_dhcp_reply(eth, dhcp, Dhcp_packet::Message_type::OFFER); + break; + case Dhcp_packet::Message_type::REQUEST: + _send_dhcp_reply(eth, dhcp, Dhcp_packet::Message_type::ACK); + break; + default: + ; + } +} + + +void Nic_perf::Interface::_send_dhcp_reply(Ethernet_frame const & eth_req, + Dhcp_packet const & dhcp_req, + Dhcp_packet::Message_type msg_type) +{ + if (_ip == Ipv4_address()) + return; + + if (_dhcp_client_ip == Ipv4_address()) + return; + + enum { PKT_SIZE = 512 }; + send(PKT_SIZE, [&] (void *pkt_base, Size_guard &size_guard) { + + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + if (msg_type == Dhcp_packet::Message_type::OFFER) { + eth.dst(Ethernet_frame::broadcast()); } + else { + eth.dst(eth_req.src()); } + eth.src(_mac); + eth.type(Ethernet_frame::Type::IPV4); + + /* create IP header of the reply */ + 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(64); + ip.protocol(Ipv4_packet::Protocol::UDP); + ip.src(_ip); + ip.dst(_dhcp_client_ip); + + /* create UDP header of the reply */ + size_t const udp_off = size_guard.head_size(); + Udp_packet &udp = ip.construct_at_data(size_guard); + udp.src_port(Port(Dhcp_packet::BOOTPS)); + udp.dst_port(Port(Dhcp_packet::BOOTPC)); + + /* create mandatory DHCP fields of the reply */ + Dhcp_packet &dhcp = udp.construct_at_data(size_guard); + dhcp.op(Dhcp_packet::REPLY); + dhcp.htype(Dhcp_packet::Htype::ETH); + dhcp.hlen(sizeof(Mac_address)); + dhcp.xid(dhcp_req.xid()); + if (msg_type == Dhcp_packet::Message_type::INFORM) { + dhcp.ciaddr(_dhcp_client_ip); } + else { + dhcp.yiaddr(_dhcp_client_ip); } + dhcp.siaddr(_ip); + dhcp.client_mac(dhcp_req.client_mac()); + dhcp.default_magic_cookie(); + + /* append DHCP option fields to the reply */ + Dhcp_packet::Options_aggregator dhcp_opts(dhcp, size_guard); + dhcp_opts.append_option(msg_type); + dhcp_opts.append_option(_ip); + dhcp_opts.append_option(86400); + dhcp_opts.append_option(_subnet_mask()); + dhcp_opts.append_option(_ip); + + dhcp_opts.append_dns_server([&] (Dhcp_options::Dns_server_data &data) { + data.append_address(_ip); + }); + dhcp_opts.append_option(Ipv4_packet::broadcast()); + 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(); + }); +} + + +void Nic_perf::Interface::handle_packet_stream() +{ + /* handle acks from client */ + while (_source.ack_avail()) + _source.release_packet(_source.try_get_acked_packet()); + + /* loop while we can make Rx progress */ + for (;;) { + if (!_sink.ready_to_ack()) + break; + + if (!_sink.packet_avail()) + break; + + Packet_descriptor const packet_from_client = _sink.try_get_packet(); + + if (_sink.packet_valid(packet_from_client)) { + _handle_eth(_sink.packet_content(packet_from_client), packet_from_client.size()); + if (!_sink.try_ack_packet(packet_from_client)) + break; + } + } + + /* skip sending if disabled or IP address is not set */ + if (!_generator.enabled() || _ip == Ipv4_address()) { + _sink.wakeup(); + _source.wakeup(); + return; + } + + /* loop while we can make Tx progress */ + for (;;) { + /* + * The client fails to pick up the packets from the rx channel. So we + * won't try to submit new packets. + */ + if (!_source.ready_to_submit()) + break; + + bool okay = + send(_generator.size(), [&] (void * pkt_base, Size_guard & size_guard) { + _generator.generate(pkt_base, size_guard, _mac, _ip); + }); + + if (!okay) + break; + } + + _sink.wakeup(); + _source.wakeup(); +} diff --git a/repos/os/src/server/nic_perf/interface.h b/repos/os/src/server/nic_perf/interface.h new file mode 100644 index 0000000000..4e2fab2ed0 --- /dev/null +++ b/repos/os/src/server/nic_perf/interface.h @@ -0,0 +1,153 @@ +/* + * \brief Base class for Nic/Uplink session components + * \author Johannes Schlatow + * \date 2022-06-15 + */ + +/* + * Copyright (C) 2022 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 _INTERFACE_H_ +#define _INTERFACE_H_ + +/* local includes */ +#include +#include +#include + +/* Genode includes */ +#include +#include +#include +#include +#include + +namespace Nic_perf { + using namespace Genode; + using namespace Net; + + using Dhcp_options = Dhcp_packet::Options_aggregator; + + class Interface; + + using Interface_registry = Registry; +} + +class Nic_perf::Interface +{ + protected: + + using Sink = Nic::Packet_stream_sink; + using Source = Nic::Packet_stream_source; + + Interface_registry::Element _element; + Session_label _label; + + Packet_stats _stats; + Packet_generator _generator; + + bool _mac_from_policy; + + Mac_address _mac { }; + Mac_address const _default_mac; + Ipv4_address _ip { }; + Ipv4_address _dhcp_client_ip { }; + + Source &_source; + Sink &_sink; + + Constructible _dhcp_client { }; + Timer::Connection &_timer; + + static Ipv4_address _subnet_mask() + { + uint8_t buf[] = { 0xff, 0xff, 0xff, 0 }; + return Ipv4_address((void*)buf); + } + + void _handle_eth(void *, size_t); + void _handle_ip(Ethernet_frame &, Size_guard &); + void _handle_arp(Ethernet_frame &, Size_guard &); + void _handle_dhcp_request(Ethernet_frame &, Dhcp_packet &); + void _send_dhcp_reply(Ethernet_frame const &, Dhcp_packet const &, Dhcp_packet::Message_type); + + public: + + Interface(Interface_registry ®istry, + Session_label const &label, + Xml_node const &policy, + bool mac_from_policy, + Mac_address mac, + Source &source, + Sink &sink, + Timer::Connection &timer) + : _element(registry, *this), + _label(label), + _stats(_label), + _generator(timer, *this), + _mac_from_policy(mac_from_policy), + _default_mac(mac), + _source(source), + _sink(sink), + _timer(timer) + { apply_config(policy); } + + void apply_config(Xml_node const &config) + { + _generator.apply_config(config); + + /* restore defaults when applied to empty/incomplete config */ + _mac = _default_mac; + _ip = Ipv4_address(); + _dhcp_client_ip = Ipv4_address(); + + _dhcp_client.destruct(); + + config.with_sub_node("interface", [&] (Xml_node node) { + _ip = node.attribute_value("ip", _ip); + _dhcp_client_ip = node.attribute_value("dhcp_client_ip", _dhcp_client_ip); + + if (_mac_from_policy) + _mac = node.attribute_value("mac", _mac); + }); + + if (_ip == Ipv4_address()) + _dhcp_client.construct(_timer, *this); + } + + Session_label const &label() const { return _label; } + Packet_stats &packet_stats() { return _stats; } + + Mac_address const &mac() const { return _mac; } + Ipv4_address const &ip() const { return _ip; } + void ip(Ipv4_address const &ip) { _ip = ip; } + + void handle_packet_stream(); + + template + bool send(size_t pkt_size, FUNC && write_to_pkt) + { + if (!pkt_size) + return false; + + 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.try_submit_packet(pkt); + } catch (...) { return false; } + + _stats.tx_packet(pkt_size); + + return true; + } +}; + +#endif /* _INTERFACE_H_ */ diff --git a/repos/os/src/server/nic_perf/main.cc b/repos/os/src/server/nic_perf/main.cc new file mode 100644 index 0000000000..44373ef1f4 --- /dev/null +++ b/repos/os/src/server/nic_perf/main.cc @@ -0,0 +1,123 @@ +/* + * \brief Throughput benchmark component for Nic and Uplink sessions + * \author Johannes Schlatow + * \date 2022-06-14 + * + * This component continously sends/receives UDP packets via a Nic or Uplink + * session in order to benchmark the throughput. + */ + +/* + * Copyright (C) 2022 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 + +namespace Nic_perf { + class Main; + + using namespace Genode; +} + + +struct Nic_perf::Main +{ + using Periodic_timeout = Timer::Periodic_timeout
; + + Env &_env; + + Heap _heap { _env.ram(), _env.rm() }; + + Timer::Connection _timer { _env }; + + Attached_rom_dataspace _config { _env, "config" }; + + unsigned _period_ms { 5000 }; + + unsigned _count { 10000 }; + + Interface_registry _registry { }; + + Nic_perf::Nic_root _nic_root { _env, _heap, _registry, _config, _timer }; + + Nic_perf::Uplink_root _uplink_root { _env, _heap, _registry, _config, _timer }; + + Constructible _nic_client { }; + + Genode::Signal_handler
_config_handler = + { _env.ep(), *this, &Main::_handle_config }; + + Constructible _timeout { }; + + void _handle_config() + { + _config.update(); + + _registry.for_each([&] (Interface &interface) { + with_matching_policy(interface.label(), _config.xml(), + [&] (Xml_node const &policy) { + interface.apply_config(policy); + }, + [&] () { /* no matches */ + interface.apply_config(Xml_node("")); + } + ); + }); + + if (_nic_client.constructed()) + _nic_client.destruct(); + + if (_config.xml().has_sub_node("nic-client")) + _nic_client.construct(_env, _heap, _config.xml().sub_node("nic-client"), _registry, _timer); + + _period_ms = _config.xml().attribute_value("period_ms", _period_ms); + _count = _config.xml().attribute_value("count", _count); + + _timeout.conditional(_count && _period_ms, + _timer, *this, &Main::_handle_timeout, Microseconds(_period_ms*1000)); + } + + void _handle_timeout(Genode::Duration) + { + _registry.for_each([&] (Interface &interface) { + Packet_stats &stats = interface.packet_stats(); + + stats.calculate_throughput(_period_ms); + log(stats); + stats.reset(); + }); + + _count--; + if (!_count) + _env.parent().exit(0); + } + + Main(Env &env) : _env(env) + { + _env.parent().announce(_env.ep().manage(_nic_root)); + _env.parent().announce(_env.ep().manage(_uplink_root)); + + _config.sigh(_config_handler); + + _handle_config(); + } +}; + + +void Component::construct(Genode::Env &env) { static Nic_perf::Main main(env); } + diff --git a/repos/os/src/server/nic_perf/nic_client.h b/repos/os/src/server/nic_perf/nic_client.h new file mode 100644 index 0000000000..c88c07b9d8 --- /dev/null +++ b/repos/os/src/server/nic_perf/nic_client.h @@ -0,0 +1,71 @@ +/* + * \brief Nic client + * \author Johannes Schlatow + * \date 2022-06-16 + */ + +/* + * Copyright (C) 2022 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_CLIENT_H_ +#define _NIC_CLIENT_H_ + +/* local includes */ +#include + +/* Genode includes */ +#include +#include +#include + +namespace Nic_perf { + class Nic_client; + + using namespace Genode; +} + + +class Nic_perf::Nic_client +{ + private: + enum { BUF_SIZE = Nic::Session::QUEUE_SIZE * Nic::Packet_allocator::DEFAULT_PACKET_SIZE }; + + Env &_env; + Nic::Packet_allocator _pkt_alloc; + Nic::Connection _nic { _env, &_pkt_alloc, BUF_SIZE, BUF_SIZE }; + Interface _interface; + + Signal_handler _packet_stream_handler + { _env.ep(), *this, &Nic_client::_handle_packet_stream }; + + void _handle_packet_stream() { + _interface.handle_packet_stream(); } + + public: + + Nic_client(Env &env, + Genode::Allocator &alloc, + Xml_node const &policy, + Interface_registry ®istry, + Timer::Connection &timer) + : + _env(env), + _pkt_alloc(&alloc), + _interface(registry, "nic-client", policy, false, Mac_address(), + *_nic.tx(), *_nic.rx(), timer) + { + _nic.rx_channel()->sigh_ready_to_ack(_packet_stream_handler); + _nic.rx_channel()->sigh_packet_avail(_packet_stream_handler); + _nic.tx_channel()->sigh_ack_avail(_packet_stream_handler); + _nic.tx_channel()->sigh_ready_to_submit(_packet_stream_handler); + + _interface.handle_packet_stream(); + } +}; + + +#endif /* _NIC_CLIENT_H_ */ diff --git a/repos/os/src/server/nic_perf/nic_component.h b/repos/os/src/server/nic_perf/nic_component.h new file mode 100644 index 0000000000..66898cf148 --- /dev/null +++ b/repos/os/src/server/nic_perf/nic_component.h @@ -0,0 +1,142 @@ +/* + * \brief Nic root and session component + * \author Johannes Schlatow + * \date 2022-06-16 + */ + +/* + * Copyright (C) 2022 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_ROOT_H_ +#define _NIC_ROOT_H_ + +/* local includes */ +#include + +/* Genode includes */ +#include +#include +#include +#include + +namespace Nic_perf { + class Nic_session_component; + class Nic_root; + + using namespace Genode; +} + + +class Nic_perf::Nic_session_component : public Nic::Session_component +{ + private: + + Interface _interface; + + static Mac_address _default_mac_address() + { + char buf[] = {2,3,4,5,6,7}; + Mac_address result((void*)buf); + return result; + } + + void _handle_packet_stream() override { + _interface.handle_packet_stream(); } + + public: + + Nic_session_component(size_t const tx_buf_size, + size_t const rx_buf_size, + Allocator &rx_block_md_alloc, + Env &env, + Session_label const &label, + Xml_node const &policy, + Interface_registry ®istry, + Timer::Connection &timer) + : + Nic::Session_component(tx_buf_size, rx_buf_size, CACHED, + rx_block_md_alloc, env), + _interface(registry, label, policy, true, _default_mac_address(), + *_rx.source(), *_tx.sink(), timer) + { _interface.handle_packet_stream(); } + + /***************************** + * Session_component methods * + *****************************/ + + Nic::Mac_address mac_address() override { + char buf[] = {2,3,4,5,6,8}; + Mac_address result((void*)buf); + return result; + } + + bool link_state() override + { + /* XXX always return true, for now */ + return true; + } +}; + + +class Nic_perf::Nic_root : public Root_component +{ + private: + Env &_env; + Attached_rom_dataspace &_config; + Interface_registry &_registry; + Timer::Connection &_timer; + + protected: + + Nic_session_component *_create_session(char const *args) override + { + size_t ram_quota = Arg_string::find_arg(args, "ram_quota" ).ulong_value(0); + size_t tx_buf_size = Arg_string::find_arg(args, "tx_buf_size").ulong_value(0); + size_t rx_buf_size = Arg_string::find_arg(args, "rx_buf_size").ulong_value(0); + + /* deplete ram quota by the memory needed for the session structure */ + size_t session_size = max(4096UL, (size_t)sizeof(Nic_session_component)); + if (ram_quota < session_size) + throw Insufficient_ram_quota(); + + /* + * Check if donated ram quota suffices for both communication + * buffers and check for overflow + */ + if (tx_buf_size + rx_buf_size < tx_buf_size || + tx_buf_size + rx_buf_size > ram_quota - session_size) { + error("insufficient 'ram_quota', got ", ram_quota, ", " + "need ", tx_buf_size + rx_buf_size + session_size); + throw Insufficient_ram_quota(); + } + + Session_label label = label_from_args(args); + + Session_policy policy(label, _config.xml()); + + return new (md_alloc()) Nic_session_component(tx_buf_size, rx_buf_size, + *md_alloc(), _env, label, policy, + _registry, _timer); + } + + public: + + Nic_root(Env &env, + Allocator &md_alloc, + Interface_registry ®istry, + Attached_rom_dataspace &config, + Timer::Connection &timer) + : + Root_component(&env.ep().rpc_ep(), &md_alloc), + _env(env), + _config(config), + _registry(registry), + _timer(timer) + { } +}; + +#endif /* _NIC_ROOT_H_ */ diff --git a/repos/os/src/server/nic_perf/packet_generator.cc b/repos/os/src/server/nic_perf/packet_generator.cc new file mode 100644 index 0000000000..8825f63e44 --- /dev/null +++ b/repos/os/src/server/nic_perf/packet_generator.cc @@ -0,0 +1,134 @@ +/* + * \brief Packet generator + * \author Johannes Schlatow + * \date 2022-06-14 + */ + +/* + * Copyright (C) 2022 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 + +void Nic_perf::Packet_generator::_handle_timeout(Genode::Duration) +{ + /* re-issue ARP request */ + if (_state == WAIT_ARP_REPLY) + _state = NEED_ARP_REQUEST; + + _interface.handle_packet_stream(); +} + +void Nic_perf::Packet_generator::handle_arp_reply(Arp_packet const & arp) +{ + if (arp.src_ip() != _dst_ip) + return; + + if (_state != WAIT_ARP_REPLY) + return; + + _timeout.discard(); + + _dst_mac = arp.src_mac(); + _state = READY; +} + + +void Nic_perf::Packet_generator::_generate_arp_request(void * pkt_base, + Size_guard & size_guard, + Mac_address const & from_mac, + Ipv4_address const & from_ip) +{ + if (from_ip == Ipv4_address()) { + error("Ip address not set"); + throw Ip_address_not_set(); + } + + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(Mac_address(0xff)); + eth.src(from_mac); + eth.type(Ethernet_frame::Type::ARP); + + 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(from_mac); + arp.src_ip(from_ip); + arp.dst_mac(Mac_address(0xff)); + arp.dst_ip(_dst_ip); +} + + +void Nic_perf::Packet_generator::_generate_test_packet(void * pkt_base, + Size_guard & size_guard, + Mac_address const & from_mac, + Ipv4_address const & from_ip) +{ + if (from_ip == Ipv4_address()) { + error("Ip address not set"); + throw Ip_address_not_set(); + } + + if (_dst_port == Port(0)) { + error("Udp port not set"); + throw Udp_port_not_set(); + } + + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(_dst_mac); + eth.src(from_mac); + eth.type(Ethernet_frame::Type::IPV4); + + 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(64); + ip.protocol(Ipv4_packet::Protocol::UDP); + ip.src(from_ip); + ip.dst(_dst_ip); + + size_t udp_off = size_guard.head_size(); + Udp_packet &udp = ip.construct_at_data(size_guard); + udp.src_port(Port(0)); + udp.dst_port(_dst_port); + + /* inflate packet up to _mtu */ + size_guard.consume_head(size_guard.unconsumed()); + + /* fill in length fields and checksums */ + 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(); +} + + +void Nic_perf::Packet_generator::generate(void * pkt_base, + Size_guard & size_guard, + Mac_address const & from_mac, + Ipv4_address const & from_ip) +{ + switch (_state) { + case READY: + _generate_test_packet(pkt_base, size_guard, from_mac, from_ip); + break; + case NEED_ARP_REQUEST: + _generate_arp_request(pkt_base, size_guard, from_mac, from_ip); + _state = WAIT_ARP_REPLY; + _timeout.schedule(Microseconds { 1000 * 1000 }); + break; + case MUTED: + case WAIT_ARP_REPLY: + throw Not_ready(); + break; + } +} diff --git a/repos/os/src/server/nic_perf/packet_generator.h b/repos/os/src/server/nic_perf/packet_generator.h new file mode 100644 index 0000000000..ffb73fe1d2 --- /dev/null +++ b/repos/os/src/server/nic_perf/packet_generator.h @@ -0,0 +1,116 @@ +/* + * \brief Packet generator + * \author Johannes Schlatow + * \date 2022-06-14 + */ + +/* + * Copyright (C) 2022 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 _PACKET_GENERATOR_H_ +#define _PACKET_GENERATOR_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include + +namespace Nic_perf { + using namespace Genode; + using namespace Net; + + class Interface; + class Packet_generator; +} + +class Nic_perf::Packet_generator +{ + public: + struct Not_ready : Exception { }; + struct Ip_address_not_set : Exception { }; + struct Udp_port_not_set : Exception { }; + + private: + + enum State { MUTED, NEED_ARP_REQUEST, WAIT_ARP_REPLY, READY }; + + size_t _mtu { 1024 }; + bool _enable { false }; + Ipv4_address _dst_ip { }; + Port _dst_port { 0 }; + Mac_address _dst_mac { }; + State _state { MUTED }; + + Timer::One_shot_timeout _timeout; + Nic_perf::Interface &_interface; + + void _generate_arp_request(void *, Size_guard &, Mac_address const &, Ipv4_address const &); + + void _generate_test_packet(void *, Size_guard &, Mac_address const &, Ipv4_address const &); + + void _handle_timeout(Genode::Duration); + + public: + + Packet_generator(Timer::Connection &timer, Nic_perf::Interface &interface) + : _timeout(timer, *this, &Packet_generator::_handle_timeout), + _interface(interface) + { } + + void apply_config(Xml_node const &config) + { + Ipv4_address old_ip = _dst_ip; + + /* restore defaults */ + _dst_ip = Ipv4_address(); + _dst_port = Port(0); + _enable = false; + _state = MUTED; + + config.with_sub_node("tx", [&] (Xml_node node) { + _mtu = node.attribute_value("mtu", _mtu); + _dst_ip = node.attribute_value("to", _dst_ip); + _dst_port = node.attribute_value("udp_port", _dst_port); + _enable = true; + _state = READY; + }); + + /* redo ARP resolution if dst ip changed */ + if (old_ip != _dst_ip) { + _dst_mac = Mac_address(); + + if (_enable) + _state = NEED_ARP_REQUEST; + } + } + + bool enabled() const { return _enable; } + + size_t size() const + { + switch (_state) { + case READY: + return _mtu; + case NEED_ARP_REQUEST: + return Ethernet_frame::MIN_SIZE + sizeof(uint32_t); + case WAIT_ARP_REPLY: + case MUTED: + return 0; + } + + return 0; + } + + void handle_arp_reply(Arp_packet const & arp); + + void generate(void *, Size_guard &, Mac_address const &, Ipv4_address const &); +}; + +#endif /* _PACKET_GENERATOR_H_ */ diff --git a/repos/os/src/server/nic_perf/packet_stats.h b/repos/os/src/server/nic_perf/packet_stats.h new file mode 100644 index 0000000000..f61835a069 --- /dev/null +++ b/repos/os/src/server/nic_perf/packet_stats.h @@ -0,0 +1,90 @@ +/* + * \brief Packet statistics + * \author Johannes Schlatow + * \date 2022-06-14 + */ + +/* + * Copyright (C) 2022 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 _PACKET_STATS_H_ +#define _PACKET_STATS_H_ + +/* Genode includes */ +#include + +namespace Nic_perf { + using namespace Genode; + + class Packet_stats; +} + +class Nic_perf::Packet_stats +{ + private: + + Session_label const &_label; + + size_t _sent_cnt { 0 }; + size_t _recv_cnt { 0 }; + size_t _sent_bytes { 0 }; + size_t _recv_bytes { 0 }; + unsigned _period_ms { 0 }; + float _rx_mbit_sec { 0.0 }; + float _tx_mbit_sec { 0.0 }; + + public: + + Packet_stats(Session_label const & label) + : _label(label) + { } + + void reset() + { + _sent_cnt = 0; + _recv_cnt = 0; + _sent_bytes = 0; + _recv_bytes = 0; + _rx_mbit_sec = 0; + _tx_mbit_sec = 0; + } + + void rx_packet(size_t bytes) + { + _recv_cnt++; + _recv_bytes += bytes; + } + + void tx_packet(size_t bytes) + { + _sent_cnt++; + _sent_bytes += bytes; + } + + void calculate_throughput(unsigned period_ms) + { + _period_ms = period_ms; + + if (_period_ms == 0) return; + + _rx_mbit_sec = (float)(_recv_bytes * 8ULL) / (float)(period_ms*1000ULL); + _tx_mbit_sec = (float)(_sent_bytes * 8ULL) / (float)(period_ms*1000ULL); + } + + void print(Output &out) const + { + Genode::print(out, "# Stats for session ", _label, "\n"); + Genode::print(out, " Received ", _recv_cnt, " packets in ", + _period_ms, "ms at ", _rx_mbit_sec, "Mbit/s\n"); + Genode::print(out, " Sent ", _sent_cnt, " packets in ", + _period_ms, "ms at ", _tx_mbit_sec, "Mbit/s\n"); + } + +}; + + +#endif /* _PACKET_STATS_H_ */ diff --git a/repos/os/src/server/nic_perf/target.mk b/repos/os/src/server/nic_perf/target.mk new file mode 100644 index 0000000000..5445365b49 --- /dev/null +++ b/repos/os/src/server/nic_perf/target.mk @@ -0,0 +1,7 @@ +TARGET = nic_perf +SRC_CC = main.cc interface.cc packet_generator.cc dhcp_client.cc +LIBS = base net + +INC_DIR += $(PRG_DIR) + +CC_CXX_WARN_STRICT_CONVERSION = diff --git a/repos/os/src/server/nic_perf/uplink_component.h b/repos/os/src/server/nic_perf/uplink_component.h new file mode 100644 index 0000000000..48098f5eca --- /dev/null +++ b/repos/os/src/server/nic_perf/uplink_component.h @@ -0,0 +1,195 @@ +/* + * \brief Uplink root and session component + * \author Johannes Schlatow + * \date 2022-06-17 + */ + +/* + * Copyright (C) 2022 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 _UPLINK_ROOT_H_ +#define _UPLINK_ROOT_H_ + +/* local includes */ +#include + +/* Genode includes */ +#include +#include +#include +#include +#include + +namespace Nic_perf { + class Uplink_session_base; + class Uplink_session_component; + class Uplink_root; + + using namespace Genode; +} + + +class Nic_perf::Uplink_session_base +{ + friend class Uplink_session_component; + + private: + + class Buffer + { + private: + + Ram_allocator &_ram_alloc; + Ram_dataspace_capability _ram_ds; + + public: + + Buffer(Ram_allocator &ram_alloc, + size_t const size) + : + _ram_alloc { ram_alloc }, + _ram_ds { ram_alloc.alloc(size) } + { } + + ~Buffer() { _ram_alloc.free(_ram_ds); } + + Dataspace_capability ds() const { return _ram_ds; } + }; + + Env &_env; + Allocator &_alloc; + Nic::Packet_allocator _packet_alloc; + Buffer _tx_buf; + Buffer _rx_buf; + + public: + + Uplink_session_base(Env &env, + size_t tx_buf_size, + size_t rx_buf_size, + Allocator &alloc) + : + _env { env }, + _alloc { alloc }, + _packet_alloc { &_alloc }, + _tx_buf { _env.ram(), tx_buf_size }, + _rx_buf { _env.ram(), rx_buf_size } + { } +}; + + +class Nic_perf::Uplink_session_component : private Uplink_session_base, + public Uplink::Session_rpc_object +{ + private: + + Interface _interface; + Signal_handler _packet_stream_handler + { _env.ep(), *this, &Uplink_session_component::_handle_packet_stream }; + + void _handle_packet_stream() { + _interface.handle_packet_stream(); } + + public: + + Uplink_session_component(size_t const tx_buf_size, + size_t const rx_buf_size, + Allocator &alloc, + Env &env, + Session_label const &label, + Xml_node const &policy, + Interface_registry ®istry, + Mac_address mac, + Timer::Connection &timer) + : + Uplink_session_base(env, tx_buf_size, rx_buf_size, alloc), + Uplink::Session_rpc_object(env.rm(), _tx_buf.ds(), _rx_buf.ds(), + &_packet_alloc, env.ep().rpc_ep()), + _interface(registry, label, policy, false, mac, + *_rx.source(), *_tx.sink(), timer) + { + _interface.handle_packet_stream(); + + _tx.sigh_ready_to_ack (_packet_stream_handler); + _tx.sigh_packet_avail (_packet_stream_handler); + _rx.sigh_ack_avail (_packet_stream_handler); + _rx.sigh_ready_to_submit(_packet_stream_handler); + } +}; + + +class Nic_perf::Uplink_root : public Root_component +{ + private: + Env &_env; + Attached_rom_dataspace &_config; + Interface_registry &_registry; + Timer::Connection &_timer; + + protected: + + Uplink_session_component *_create_session(char const *args) override + { + size_t ram_quota = Arg_string::find_arg(args, "ram_quota" ).ulong_value(0); + size_t tx_buf_size = Arg_string::find_arg(args, "tx_buf_size").ulong_value(0); + size_t rx_buf_size = Arg_string::find_arg(args, "rx_buf_size").ulong_value(0); + + /* deplete ram quota by the memory needed for the session structure */ + size_t session_size = max(4096UL, (size_t)sizeof(Uplink_session_component)); + if (ram_quota < session_size) + throw Insufficient_ram_quota(); + + /* + * Check if donated ram quota suffices for both communication + * buffers and check for overflow + */ + if (tx_buf_size + rx_buf_size < tx_buf_size || + tx_buf_size + rx_buf_size > ram_quota - session_size) { + error("insufficient 'ram_quota', got ", ram_quota, ", " + "need ", tx_buf_size + rx_buf_size + session_size); + throw Insufficient_ram_quota(); + } + + enum { MAC_STR_LENGTH = 19 }; + char mac_str [MAC_STR_LENGTH]; + Arg mac_arg { Arg_string::find_arg(args, "mac_address") }; + + if (!mac_arg.valid()) + throw Service_denied(); + + mac_arg.string(mac_str, MAC_STR_LENGTH, ""); + Mac_address mac { }; + ascii_to(mac_str, mac); + if (mac == Mac_address { }) + throw Service_denied(); + + Session_label label = label_from_args(args); + + Session_policy policy(label, _config.xml()); + + return new (md_alloc()) Uplink_session_component(tx_buf_size, rx_buf_size, + *md_alloc(), _env, label, policy, + _registry, mac, _timer); + } + + public: + + Uplink_root(Env &env, + Allocator &md_alloc, + Interface_registry ®istry, + Attached_rom_dataspace &config, + Timer::Connection &timer) + : + Root_component(&env.ep().rpc_ep(), &md_alloc), + _env(env), + _config(config), + _registry(registry), + _timer(timer) + { } +}; + +#endif /* _UPLINK_ROOT_H_ */