diff --git a/repos/os/run/ping.run b/repos/os/run/ping.run index eab17bf537..5e736b03b8 100644 --- a/repos/os/run/ping.run +++ b/repos/os/run/ping.run @@ -57,8 +57,7 @@ append config { - @@ -96,4 +95,4 @@ append_if [have_spec lan9118] qemu_args " -net nic,model=lan9118 " append qemu_args " -net user -nographic " -run_genode_until "\"ping\" exited with exit value 0.*\n" 25 +run_genode_until ".*\"ping\" exited with exit value 0.*\n" 25 diff --git a/repos/os/run/ping_nic_router.run b/repos/os/run/ping_nic_router.run index 1976e616ed..a40a1067df 100644 --- a/repos/os/run/ping_nic_router.run +++ b/repos/os/run/ping_nic_router.run @@ -119,7 +119,8 @@ append config { - @@ -132,7 +133,8 @@ append config { - @@ -145,7 +147,8 @@ append config { - @@ -158,7 +161,8 @@ append config { - diff --git a/repos/os/src/app/ping/README b/repos/os/src/app/ping/README index eb6cf21630..97253783cb 100644 --- a/repos/os/src/app/ping/README +++ b/repos/os/src/app/ping/README @@ -1,17 +1,16 @@ The 'ping' component continuously sends ICMP Echo requests to a given IP host and waits for the corresponding ICMP Echo replies. For each successfull ICMP -Echo handshake it prints a short statistic. By now, it can be used only with a -static IP configuration. The ICMP data field gets filled with the letters of -the alphabet ('a' to 'z') repeatedly. +Echo handshake it prints a short statistic. The ICMP data field gets filled +with the letters of the alphabet ('a' to 'z') repeatedly. Configuration ~~~~~~~~~~~~~ This is an example configuration of the component which shows the default -value for each attribute except 'config.dst_ip' and 'config.src_ip': +value for each attribute except 'config.dst_ip' and 'config.interface': -! + + + + + + - + + diff --git a/repos/os/src/app/ping/dhcp_client.cc b/repos/os/src/app/ping/dhcp_client.cc new file mode 100644 index 0000000000..ceda41604e --- /dev/null +++ b/repos/os/src/app/ping/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/app/ping/dhcp_client.h b/repos/os/src/app/ping/dhcp_client.h new file mode 100644 index 0000000000..24094d7d5d --- /dev/null +++ b/repos/os/src/app/ping/dhcp_client.h @@ -0,0 +1,107 @@ +/* + * \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 = 10 }; + enum { REQUEST_TIMEOUT_SEC = 10 }; + enum { OFFER_TIMEOUT_SEC = 10 }; + + 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 }; + Genode::Microseconds const _offer_timeout { OFFER_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/app/ping/ipv4_address_prefix.cc b/repos/os/src/app/ping/ipv4_address_prefix.cc new file mode 100644 index 0000000000..bf46d115e3 --- /dev/null +++ b/repos/os/src/app/ping/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/app/ping/ipv4_address_prefix.h b/repos/os/src/app/ping/ipv4_address_prefix.h new file mode 100644 index 0000000000..4fb378332c --- /dev/null +++ b/repos/os/src/app/ping/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/app/ping/ipv4_config.cc b/repos/os/src/app/ping/ipv4_config.cc new file mode 100644 index 0000000000..5aea166f58 --- /dev/null +++ b/repos/os/src/app/ping/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/app/ping/ipv4_config.h b/repos/os/src/app/ping/ipv4_config.h new file mode 100644 index 0000000000..6b3f011074 --- /dev/null +++ b/repos/os/src/app/ping/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/app/ping/main.cc b/repos/os/src/app/ping/main.cc index f1b87935e1..e9106c651d 100644 --- a/repos/os/src/app/ping/main.cc +++ b/repos/os/src/app/ping/main.cc @@ -11,6 +11,11 @@ * under the terms of the GNU Affero General Public License version 3. */ +/* local includes */ +#include +#include +#include + /* Genode includes */ #include #include @@ -20,18 +25,10 @@ #include #include #include -#include -#include using namespace Net; using namespace Genode; -namespace Net { - - using Packet_descriptor = ::Nic::Packet_descriptor; - using Packet_stream_sink = ::Nic::Packet_stream_sink< ::Nic::Session::Policy>; - using Packet_stream_source = ::Nic::Packet_stream_source< ::Nic::Session::Policy>; -} Microseconds read_sec_attr(Xml_node const node, char const *name, @@ -45,7 +42,8 @@ Microseconds read_sec_attr(Xml_node const node, } -class Main +class Main : public Nic_handler, + public Dhcp_client_handler { private: @@ -57,34 +55,26 @@ class Main enum { ICMP_DATA_SIZE = 56 }; enum { DEFAULT_COUNT = 5 }; enum { DEFAULT_PERIOD_SEC = 5 }; - enum { PKT_SIZE = Nic::Packet_allocator::DEFAULT_PACKET_SIZE }; - enum { BUF_SIZE = Nic::Session::QUEUE_SIZE * PKT_SIZE }; - Env &_env; - Attached_rom_dataspace _config_rom { _env, "config" }; - Xml_node _config { _config_rom.xml() }; - Timer::Connection _timer { _env }; - Microseconds _send_time { 0 }; - Periodic_timeout _period { _timer, *this, &Main::_send_ping, - read_sec_attr(_config, "period_sec", DEFAULT_PERIOD_SEC) }; - Heap _heap { &_env.ram(), &_env.rm() }; - Nic::Packet_allocator _pkt_alloc { &_heap }; - Nic::Connection _nic { _env, &_pkt_alloc, BUF_SIZE, BUF_SIZE }; - Signal_handler _sink_ack { _env.ep(), *this, &Main::_ack_avail }; - Signal_handler _sink_submit { _env.ep(), *this, &Main::_ready_to_submit }; - Signal_handler _source_ack { _env.ep(), *this, &Main::_ready_to_ack }; - Signal_handler _source_submit { _env.ep(), *this, &Main::_packet_avail }; - bool const _verbose { _config.attribute_value("verbose", false) }; - Ipv4_address const _src_ip { _config.attribute_value("src_ip", Ipv4_address()) }; - Ipv4_address const _dst_ip { _config.attribute_value("dst_ip", Ipv4_address()) }; - Mac_address const _src_mac { _nic.mac_address() }; - Mac_address _dst_mac { }; - uint16_t _ip_id { 1 }; - uint16_t _icmp_seq { 1 }; - unsigned long _count { _config.attribute_value("count", (unsigned long)DEFAULT_COUNT) }; - - void _handle_eth(void *const eth_base, - Size_guard &size_guard); + Env &_env; + Attached_rom_dataspace _config_rom { _env, "config" }; + Xml_node _config { _config_rom.xml() }; + Timer::Connection _timer { _env }; + Microseconds _send_time { 0 }; + Microseconds _period_us { read_sec_attr(_config, "period_sec", DEFAULT_PERIOD_SEC) }; + 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 { }; + uint16_t _ip_id { 1 }; + uint16_t _icmp_seq { 1 }; + unsigned long _count { _config.attribute_value("count", (unsigned long)DEFAULT_COUNT) }; + Constructible _dhcp_client { }; + Reconstructible _ip_config { _config.attribute_value("interface", Ipv4_address_prefix()), + _config.attribute_value("gateway", Ipv4_address()), + Ipv4_address() }; void _handle_ip(Ethernet_frame ð, Size_guard &size_guard); @@ -103,94 +93,93 @@ class Main void _handle_arp(Ethernet_frame ð, Size_guard &size_guard); - void _broadcast_arp_request(); + void _broadcast_arp_request(Ipv4_address const &ip); void _send_arp_reply(Ethernet_frame &req_eth, Arp_packet &req_arp); - template - void _send(size_t pkt_size, - FUNC && write_to_pkt) - { - try { - Packet_descriptor pkt = _source().alloc_packet(pkt_size); - void *pkt_base = _source().packet_content(pkt); - Size_guard size_guard(pkt_size); - write_to_pkt(pkt_base, size_guard); - _source().submit_packet(pkt); - if (_verbose) { - try { - Size_guard size_guard(pkt_size); - log("snd ", Ethernet_frame::cast_from(pkt_base, size_guard)); - } - catch (Size_guard::Exceeded) { log("snd ?"); } - } - } - catch (Net::Packet_stream_source::Packet_alloc_failed) { - warning("failed to allocate packet"); } - } - void _send_ping(Duration not_used = Duration(Microseconds(0))); - Net::Packet_stream_sink &_sink() { return *_nic.rx(); } - Net::Packet_stream_source &_source() { return *_nic.tx(); } - - - /*********************************** - ** Packet-stream signal handlers ** - ***********************************/ - - void _ready_to_submit(); - void _ack_avail() { } - void _ready_to_ack(); - void _packet_avail() { } - public: struct Invalid_arguments : Exception { }; 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 (_src_ip == Ipv4_address() || - _dst_ip == Ipv4_address() || - _count == 0) - { - throw Invalid_arguments(); - } - /* install packet stream signals */ - _nic.rx_channel()->sigh_ready_to_ack(_sink_ack); - _nic.rx_channel()->sigh_packet_avail(_sink_submit); - _nic.tx_channel()->sigh_ack_avail(_source_ack); - _nic.tx_channel()->sigh_ready_to_submit(_source_submit); + if (_dst_ip == Ipv4_address() || _count == 0) { + 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(void *const eth_base, - Size_guard &size_guard) +void Main::handle_eth(Ethernet_frame ð, + Size_guard &size_guard) { - /* print receipt message */ - Ethernet_frame ð = Ethernet_frame::cast_from(eth_base, size_guard); - if (_verbose) { - log("rcv ", eth); } - - /* drop packet if ETH does not target us */ - if (eth.dst() != _src_mac && - eth.dst() != Ethernet_frame::broadcast()) - { + try { + /* print receipt message */ if (_verbose) { - log("bad ETH destination"); } - return; + 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; + case Ethernet_frame::Type::IPV4: _handle_ip(eth, size_guard); break; + default: ; } + } + catch (Drop_packet_inform exception) { + if (_verbose) { + log("drop packet: ", exception.msg); } } - /* select ETH sub-protocol */ - 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: ; } } @@ -199,7 +188,7 @@ void Main::_handle_ip(Ethernet_frame ð, { /* drop packet if IP does not target us */ Ipv4_packet &ip = eth.data(size_guard); - if (ip.dst() != _src_ip && + if (ip.dst() != ip_config().interface.address && ip.dst() != Ipv4_packet::broadcast()) { if (_verbose) { @@ -355,9 +344,16 @@ void Main::_handle_arp(Ethernet_frame ð, case Arp_packet::REPLY: /* check whether we waited for this ARP reply */ - if (_dst_mac != Mac_address() || arp.src_ip() != _dst_ip) { + 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(); _send_ping(); @@ -366,7 +362,7 @@ void Main::_handle_arp(Ethernet_frame ð, case Arp_packet::REQUEST: /* check whether the ARP request targets us */ - if (arp.dst_ip() != _src_ip) { + if (arp.dst_ip() != ip_config().interface.address) { return; } _send_arp_reply(eth, arp); @@ -375,40 +371,16 @@ void Main::_handle_arp(Ethernet_frame ð, } -void Main::_ready_to_submit() -{ - while (_sink().packet_avail()) { - - Packet_descriptor const pkt = _sink().get_packet(); - Size_guard size_guard(pkt.size()); - _handle_eth(_sink().packet_content(pkt), size_guard); - - if (!_sink().ready_to_ack()) { - error("ack state FULL"); - return; - } - _sink().acknowledge_packet(pkt); - } -} - - -void Main::_ready_to_ack() -{ - while (_source().ack_avail()) { - _source().release_packet(_source().get_acked_packet()); } -} - - void Main::_send_arp_reply(Ethernet_frame &req_eth, Arp_packet &req_arp) { - _send(sizeof(Ethernet_frame) + sizeof(Arp_packet), - [&] (void *pkt_base, Size_guard &size_guard) + _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(_src_mac); + eth.src(_nic.mac()); eth.type(Ethernet_frame::Type::ARP); /* write ARP header */ @@ -418,23 +390,23 @@ void Main::_send_arp_reply(Ethernet_frame &req_eth, arp.hardware_address_size(sizeof(Mac_address)); arp.protocol_address_size(sizeof(Ipv4_address)); arp.opcode(Arp_packet::REPLY); - arp.src_mac(_src_mac); - arp.src_ip(_src_ip); + arp.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() +void Main::_broadcast_arp_request(Ipv4_address const &dst_ip) { - _send(sizeof(Ethernet_frame) + sizeof(Arp_packet), - [&] (void *pkt_base, Size_guard &size_guard) + _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(_src_mac); + eth.src(_nic.mac()); eth.type(Ethernet_frame::Type::ARP); /* write ARP header */ @@ -444,28 +416,32 @@ void Main::_broadcast_arp_request() arp.hardware_address_size(sizeof(Mac_address)); arp.protocol_address_size(sizeof(Ipv4_address)); arp.opcode(Arp_packet::REQUEST); - arp.src_mac(_src_mac); - arp.src_ip(_src_ip); + arp.src_mac(_nic.mac()); + arp.src_ip(ip_config().interface.address); arp.dst_mac(Mac_address(0xff)); - arp.dst_ip(_dst_ip); + 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()) { - _broadcast_arp_request(); + if (ip_config().interface.prefix_matches(_dst_ip)) { + _broadcast_arp_request(_dst_ip); } + else { + _broadcast_arp_request(ip_config().gateway); } return; } - _send(sizeof(Ethernet_frame) + sizeof(Ipv4_packet) + - sizeof(Icmp_packet) + ICMP_DATA_SIZE, - [&] (void *pkt_base, Size_guard &size_guard) + _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(_src_mac); + eth.src(_nic.mac()); eth.type(Ethernet_frame::Type::IPV4); /* create IP header */ @@ -475,7 +451,7 @@ void Main::_send_ping(Duration) ip.version(4); ip.time_to_live(IPV4_TIME_TO_LIVE); ip.protocol(Ipv4_packet::Protocol::ICMP); - ip.src(_src_ip); + ip.src(ip_config().interface.address); ip.dst(_dst_ip); /* create ICMP header */ diff --git a/repos/os/src/app/ping/nic.cc b/repos/os/src/app/ping/nic.cc new file mode 100644 index 0000000000..dd835d63e3 --- /dev/null +++ b/repos/os/src/app/ping/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/app/ping/nic.h b/repos/os/src/app/ping/nic.h new file mode 100644 index 0000000000..f29d337c2c --- /dev/null +++ b/repos/os/src/app/ping/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 = ::Nic::Session::QUEUE_SIZE * 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/app/ping/target.mk b/repos/os/src/app/ping/target.mk index f212a61f0f..b50be39100 100644 --- a/repos/os/src/app/ping/target.mk +++ b/repos/os/src/app/ping/target.mk @@ -2,7 +2,8 @@ TARGET = ping LIBS += base net -SRC_CC += main.cc +SRC_CC += main.cc dhcp_client.cc xml_node.cc ipv4_address_prefix.cc +SRC_CC += nic.cc ipv4_config.cc INC_DIR += $(PRG_DIR) diff --git a/repos/os/src/app/ping/xml_node.cc b/repos/os/src/app/ping/xml_node.cc new file mode 100644 index 0000000000..be7d7b80cb --- /dev/null +++ b/repos/os/src/app/ping/xml_node.cc @@ -0,0 +1,29 @@ +/* + * \brief Genode XML nodes plus local utilities + * \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. + */ + +/* local includes */ +#include + +using namespace Genode; + + +Microseconds Genode::read_sec_attr(Xml_node const node, + char const *name, + unsigned long const default_sec) +{ + unsigned long sec = node.attribute_value(name, 0UL); + if (!sec) { + sec = default_sec; + } + return Microseconds(sec * 1000 * 1000); +} diff --git a/repos/os/src/app/ping/xml_node.h b/repos/os/src/app/ping/xml_node.h new file mode 100644 index 0000000000..86f3840a62 --- /dev/null +++ b/repos/os/src/app/ping/xml_node.h @@ -0,0 +1,29 @@ +/* + * \brief Genode XML nodes plus local utilities + * \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 _XML_NODE_H_ +#define _XML_NODE_H_ + +/* Genode includes */ +#include +#include + + +namespace Genode { + + Microseconds read_sec_attr(Xml_node const node, + char const *name, + unsigned long const default_sec); +} + +#endif /* _XML_NODE_H_ */