os: add nic_perf component

The nic_perf component is used for benchmarking the throughput of Nic
and Uplink sessions.

genodelabs/genode#4555
This commit is contained in:
Johannes Schlatow 2022-06-16 14:26:12 +02:00 committed by Christian Helmuth
parent 8df8f78fe2
commit 3e562bc9bb
13 changed files with 1665 additions and 0 deletions

View File

@ -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:
! <config period_ms="5000" count="10000">
! <default-policy>
! <tx mtu="1500" to="10.0.1.2" udp_port="12345"/>
! </default-policy>
! <nic-client>
! <interface ip="10.0.2.1" dhcp_client_ip="10.0.2.2"/>
! </nic-client>
! </config>
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 '<policy>' nodes resp. a '<default-policy>' node. The component
opens a single Nic connection if a '<nic-client>' node is provided.
All sub-nodes comprise an optional '<interface>' node and an optional '<tx>'
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.

View File

@ -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 <dhcp_client.h>
#include <interface.h>
/* Genode includes */
#include <util/xml_node.h>
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<Size_guard>;
/***************
** 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<Dhcp_packet::Message_type_option>();
data.append_param_req<Dhcp_packet::Server_ipv4>();
data.append_param_req<Dhcp_packet::Ip_lease_time>();
data.append_param_req<Dhcp_packet::Dns_server_ipv4>();
data.append_param_req<Dhcp_packet::Subnet_mask>();
data.append_param_req<Dhcp_packet::Router_ipv4>();
});
}
/*****************
** 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 &eth, 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<Dhcp_packet::Message_type_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<Dhcp_packet::Server_ipv4>().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<Dhcp_packet::Ip_lease_time>().value();
_set_state(State::BOUND, _rerequest_timeout(1));
Ipv4_address dns_server;
try { dns_server = dhcp.option<Dhcp_packet::Dns_server_ipv4>().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<Dhcp_packet::Ip_lease_time>().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 &eth = 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<Ipv4_packet>(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<Udp_packet>(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<Dhcp_packet>(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<Dhcp_packet::Message_type_option>(msg_type);
switch (msg_type) {
case Message_type::DISCOVER:
append_param_req_list(dhcp_opts);
dhcp_opts.append_option<Dhcp_packet::Client_id>(_interface.mac());
dhcp_opts.append_option<Dhcp_packet::Max_msg_size>(PKT_SIZE - dhcp_off);
break;
case Message_type::REQUEST:
append_param_req_list(dhcp_opts);
dhcp_opts.append_option<Dhcp_packet::Client_id>(_interface.mac());
dhcp_opts.append_option<Dhcp_packet::Max_msg_size>(PKT_SIZE - dhcp_off);
if (_state == State::REQUEST) {
dhcp_opts.append_option<Dhcp_packet::Requested_addr>(requested_ip);
dhcp_opts.append_option<Dhcp_packet::Server_ipv4>(server_ip);
}
break;
default:
throw Bad_send_dhcp_args();
}
dhcp_opts.append_option<Dhcp_packet::Options_end>();
/* 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();
});
}

View File

@ -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 <net/dhcp.h>
#include <timer_session/connection.h>
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<Dhcp_client> _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 &eth,
Size_guard &size_guard);
};
#endif /* _DHCP_CLIENT_H_ */

View File

@ -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 <interface.h>
/* Genode includes */
#include <net/mac_address.h>
#include <net/ipv4.h>
#include <net/udp.h>
void Nic_perf::Interface::_handle_eth(void * pkt_base, size_t size)
{
try {
Size_guard size_guard(size);
Ethernet_frame &eth = 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<Arp_packet>(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*)&eth, size_guard.total_size());
});
break;
default:
;
}
}
void Nic_perf::Interface::_handle_ip(Ethernet_frame & eth, Size_guard & size_guard)
{
Ipv4_packet &ip = eth.data<Ipv4_packet>(size_guard);
if (ip.protocol() == Ipv4_packet::Protocol::UDP) {
Udp_packet &udp = ip.data<Udp_packet>(size_guard);
if (Dhcp_packet::is_dhcp(&udp)) {
Dhcp_packet &dhcp = udp.data<Dhcp_packet>(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<Dhcp_packet::Message_type_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 &eth = 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<Ipv4_packet>(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<Udp_packet>(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<Dhcp_packet>(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<Size_guard> dhcp_opts(dhcp, size_guard);
dhcp_opts.append_option<Dhcp_packet::Message_type_option>(msg_type);
dhcp_opts.append_option<Dhcp_packet::Server_ipv4>(_ip);
dhcp_opts.append_option<Dhcp_packet::Ip_lease_time>(86400);
dhcp_opts.append_option<Dhcp_packet::Subnet_mask>(_subnet_mask());
dhcp_opts.append_option<Dhcp_packet::Router_ipv4>(_ip);
dhcp_opts.append_dns_server([&] (Dhcp_options::Dns_server_data &data) {
data.append_address(_ip);
});
dhcp_opts.append_option<Dhcp_packet::Broadcast_addr>(Ipv4_packet::broadcast());
dhcp_opts.append_option<Dhcp_packet::Options_end>();
/* 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();
}

View File

@ -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 <packet_generator.h>
#include <packet_stats.h>
#include <dhcp_client.h>
/* Genode includes */
#include <base/registry.h>
#include <os/packet_stream.h>
#include <net/dhcp.h>
#include <nic_session/nic_session.h>
#include <timer_session/connection.h>
namespace Nic_perf {
using namespace Genode;
using namespace Net;
using Dhcp_options = Dhcp_packet::Options_aggregator<Size_guard>;
class Interface;
using Interface_registry = Registry<Interface>;
}
class Nic_perf::Interface
{
protected:
using Sink = Nic::Packet_stream_sink<Nic::Session::Policy>;
using Source = Nic::Packet_stream_source<Nic::Session::Policy>;
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> _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 &registry,
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 <typename FUNC>
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_ */

View File

@ -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 <interface.h>
#include <uplink_component.h>
#include <nic_component.h>
#include <nic_client.h>
/* Genode includes */
#include <base/component.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <util/arg_string.h>
#include <timer_session/connection.h>
#include <util/reconstructible.h>
namespace Nic_perf {
class Main;
using namespace Genode;
}
struct Nic_perf::Main
{
using Periodic_timeout = Timer::Periodic_timeout<Main>;
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> _nic_client { };
Genode::Signal_handler<Main> _config_handler =
{ _env.ep(), *this, &Main::_handle_config };
Constructible<Periodic_timeout> _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("<config/>"));
}
);
});
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); }

View File

@ -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 <interface.h>
/* Genode includes */
#include <nic_session/connection.h>
#include <nic/packet_allocator.h>
#include <base/attached_rom_dataspace.h>
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<Nic_client> _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 &registry,
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_ */

View File

@ -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 <interface.h>
/* Genode includes */
#include <root/component.h>
#include <nic/component.h>
#include <base/attached_rom_dataspace.h>
#include <os/session_policy.h>
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 &registry,
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<Nic_session_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 &registry,
Attached_rom_dataspace &config,
Timer::Connection &timer)
:
Root_component<Nic_session_component>(&env.ep().rpc_ep(), &md_alloc),
_env(env),
_config(config),
_registry(registry),
_timer(timer)
{ }
};
#endif /* _NIC_ROOT_H_ */

View File

@ -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 <packet_generator.h>
#include <interface.h>
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 &eth = 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<Arp_packet>(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 &eth = 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<Ipv4_packet>(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<Udp_packet>(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;
}
}

View File

@ -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 <util/xml_node.h>
#include <net/mac_address.h>
#include <net/ipv4.h>
#include <net/arp.h>
#include <net/udp.h>
#include <timer_session/connection.h>
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<Packet_generator> _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_ */

View File

@ -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 <base/session_label.h>
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_ */

View File

@ -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 =

View File

@ -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 <interface.h>
/* Genode includes */
#include <root/component.h>
#include <uplink_session/rpc_object.h>
#include <nic/packet_allocator.h>
#include <base/attached_rom_dataspace.h>
#include <os/session_policy.h>
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<Uplink_session_component> _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 &registry,
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<Uplink_session_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 &registry,
Attached_rom_dataspace &config,
Timer::Connection &timer)
:
Root_component<Uplink_session_component>(&env.ep().rpc_ep(), &md_alloc),
_env(env),
_config(config),
_registry(registry),
_timer(timer)
{ }
};
#endif /* _UPLINK_ROOT_H_ */