diff --git a/repos/libports/run/sntp_client.run b/repos/libports/run/sntp_client.run new file mode 100644 index 000000000..854d03df4 --- /dev/null +++ b/repos/libports/run/sntp_client.run @@ -0,0 +1,76 @@ +assert_spec x86 + +if { [have_spec linux] } { + puts "Run script is not supported on this platform." + exit 0 +} + +create_boot_directory + +import_from_depot [depot_user]/pkg/[drivers_nic_pkg] + +build { core init timer app/sntp_client server/report_rom } + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config +build_boot_image { core ld.lib.so init timer sntp_client report_rom } + +proc qemu_nic_model {} { + if [have_spec x86] { return e1000 } + if [have_spec lan9118] { return lan9118 } + if [have_spec zynq] { return cadence_gem } + return nic_model_missing +} +append qemu_args " -nographic " +append qemu_args " -netdev user,id=net0 " +append qemu_args " -net nic,model=[qemu_nic_model],netdev=net0 " + +set done_string "report_rom] + +This is a short description of the tags and attributes: + +:config.interface: + Optional. IP address and subnet of the component. If not set, the component + requests and maintains the IP configuration via DHCP. + +:config.gateway: + Optional. IP address of the gateway of the IP subnet. + +:config.dst_ip: + Mandatory. IP address of the target host. + +:config.period_sec: + Optional. Length of send interval in seconds. + +:config.verbose: + Optional. Toggles wether the component shall log debugging information. + +:config.count: + Optional. After how many successful pings the component exits successfully. + +:config.dst_port: + Optional. Destination port resp. ICMP query ID to use. + +:config.protocol: + Optional. Protocol to ping with. Can be one of 'icmp', 'udp'. + + +Sessions +~~~~~~~~ + +This is an overview of the sessions required and provided by the +component apart from the environment sessions: + +* Requires one Timer session. + + +Examples +~~~~~~~~ + +Examples of how to use the ping component can be found in the test scripts +'os/run/ping.run' and 'os/run/ping_nic_router.run'. diff --git a/repos/libports/src/app/sntp_client/config.xsd b/repos/libports/src/app/sntp_client/config.xsd new file mode 100644 index 000000000..3f8cc65a9 --- /dev/null +++ b/repos/libports/src/app/sntp_client/config.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/libports/src/app/sntp_client/dhcp_client.cc b/repos/libports/src/app/sntp_client/dhcp_client.cc new file mode 100644 index 000000000..b9daa5cf9 --- /dev/null +++ b/repos/libports/src/app/sntp_client/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 }; + 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_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/libports/src/app/sntp_client/dhcp_client.h b/repos/libports/src/app/sntp_client/dhcp_client.h new file mode 100644 index 000000000..e1bbd2ee9 --- /dev/null +++ b/repos/libports/src/app/sntp_client/dhcp_client.h @@ -0,0 +1,105 @@ +/* + * \brief DHCP client state model + * \author Martin Stein + * \date 2016-08-24 + */ + +/* + * Copyright (C) 2016-2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _DHCP_CLIENT_H_ +#define _DHCP_CLIENT_H_ + +/* Genode includes */ +#include +#include + +namespace Net { + + /* external definition */ + class Nic_peer; + class Ipv4_config; + class Ethernet_frame; + + /* local definition */ + class Dhcp_client; + class Dhcp_client_handler; + class Drop_packet_inform; +} + + +struct Net::Drop_packet_inform : Genode::Exception +{ + char const *msg; + + Drop_packet_inform(char const *msg) : msg(msg) { } +}; + + +class Net::Dhcp_client_handler +{ + public: + + virtual void ip_config(Ipv4_config const &ip_config) = 0; + + virtual Ipv4_config const &ip_config() const = 0; + + virtual ~Dhcp_client_handler() { } +}; + + +class Net::Dhcp_client +{ + private: + + enum class State + { + INIT = 0, SELECT = 1, REQUEST = 2, BOUND = 3, RENEW = 4, REBIND = 5 + }; + + enum { DISCOVER_TIMEOUT_SEC = 2 }; + enum { REQUEST_TIMEOUT_SEC = 2 }; + + Genode::Allocator &_alloc; + State _state { State::INIT }; + Timer::One_shot_timeout _timeout; + unsigned long _lease_time_sec = 0; + Genode::Microseconds const _discover_timeout { (Genode::uint64_t)DISCOVER_TIMEOUT_SEC * 1000 * 1000 }; + Genode::Microseconds const _request_timeout { (Genode::uint64_t)REQUEST_TIMEOUT_SEC * 1000 * 1000 }; + Nic &_nic; + Dhcp_client_handler &_handler; + + void _handle_dhcp_reply(Dhcp_packet &dhcp); + + void _handle_timeout(Genode::Duration); + + void _rerequest(State next_state); + + Genode::Microseconds _rerequest_timeout(unsigned lease_time_div_log2); + + void _set_state(State state, Genode::Microseconds timeout); + + void _send(Dhcp_packet::Message_type msg_type, + Ipv4_address client_ip, + Ipv4_address server_ip, + Ipv4_address requested_ip); + + void _discover(); + + public: + + Dhcp_client(Genode::Allocator &alloc, + Timer::Connection &timer, + Nic &nic, + Dhcp_client_handler &handler); + + void handle_eth(Ethernet_frame ð, + Size_guard &size_guard); + +}; + +#endif /* _DHCP_CLIENT_H_ */ diff --git a/repos/libports/src/app/sntp_client/ipv4_address_prefix.cc b/repos/libports/src/app/sntp_client/ipv4_address_prefix.cc new file mode 100644 index 000000000..bf46d115e --- /dev/null +++ b/repos/libports/src/app/sntp_client/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/libports/src/app/sntp_client/ipv4_address_prefix.h b/repos/libports/src/app/sntp_client/ipv4_address_prefix.h new file mode 100644 index 000000000..4fb378332 --- /dev/null +++ b/repos/libports/src/app/sntp_client/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/libports/src/app/sntp_client/ipv4_config.cc b/repos/libports/src/app/sntp_client/ipv4_config.cc new file mode 100644 index 000000000..5aea166f5 --- /dev/null +++ b/repos/libports/src/app/sntp_client/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/libports/src/app/sntp_client/ipv4_config.h b/repos/libports/src/app/sntp_client/ipv4_config.h new file mode 100644 index 000000000..6b3f01107 --- /dev/null +++ b/repos/libports/src/app/sntp_client/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/libports/src/app/sntp_client/main.cc b/repos/libports/src/app/sntp_client/main.cc new file mode 100644 index 000000000..ecbfe8d1a --- /dev/null +++ b/repos/libports/src/app/sntp_client/main.cc @@ -0,0 +1,423 @@ +/* + * \brief Test the reachability of a host on an IP network + * \author Martin Stein + * \date 2018-03-27 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* local includes */ +#include +#include +#include +#include + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Net; +using namespace Genode; + + +Microseconds read_min_attr(Xml_node const node, + char const *name, + uint64_t const default_sec) +{ + enum { MAX_MINUTES = (~(uint64_t)0) / 1000 / 1000 / 60 }; + uint64_t min = node.attribute_value(name, (uint64_t)0); + if (!min) { + min = default_sec; + } + if (min > MAX_MINUTES) { + warning("minutes value exceeds maximum"); + min = MAX_MINUTES; + } + return Microseconds(min * 60 * 1000 * 1000); +} + + +class Main : public Nic_handler, + public Dhcp_client_handler +{ + private: + + using Periodic_timeout = Timer::Periodic_timeout
; + + enum { IPV4_TIME_TO_LIVE = 64 }; + enum { DEFAULT_PERIOD_MIN = 60 }; + enum { SRC_PORT = 50000 }; + + Env &_env; + Attached_rom_dataspace _config_rom { _env, "config" }; + Xml_node _config { _config_rom.xml() }; + Timer::Connection _timer { _env }; + Microseconds _period_us { read_min_attr(_config, "period_min", (uint64_t)DEFAULT_PERIOD_MIN) }; + Constructible _period { }; + Heap _heap { &_env.ram(), &_env.rm() }; + bool const _verbose { _config.attribute_value("verbose", false) }; + Net::Nic _nic { _env, _heap, *this, _verbose }; + Ipv4_address const _dst_ip { _config.attribute_value("dst_ip", Ipv4_address()) }; + Mac_address _dst_mac { }; + Constructible _dhcp_client { }; + Reconstructible _ip_config { _config.attribute_value("interface", Ipv4_address_prefix()), + _config.attribute_value("gateway", Ipv4_address()), + Ipv4_address() }; +// Rtc::Connection _rtc { _env }; + Reporter reporter { _env, "set_rtc" }; + + Sntp_timestamp _rtc_ts_to_sntp_ts(Rtc::Timestamp const rtc_ts) + { + struct tm tm { (int)rtc_ts.second, (int)rtc_ts.minute, + (int)rtc_ts.hour, (int)rtc_ts.day, + (int)rtc_ts.month - 1, (int)rtc_ts.year - 1900, + 0, 0, 0, 0, nullptr }; + + return Sntp_timestamp::from_unix_timestamp(tm_to_secs(&tm)); + } + + Rtc::Timestamp _sntp_ts_to_rtc_ts(Sntp_timestamp const sntp_ts) + { + struct tm tm { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr }; + if (secs_to_tm(sntp_ts.to_unix_timestamp(), &tm)) { + warning("failed to convert timestamp"); + return Rtc::Timestamp(); + } + return { + 0, (unsigned)tm.tm_sec, (unsigned)tm.tm_min, + (unsigned)tm.tm_hour, (unsigned)tm.tm_mday, + (unsigned)tm.tm_mon + 1, (unsigned)tm.tm_year + 1900 }; + } + + void _handle_ip(Ethernet_frame ð, + Size_guard &size_guard); + + void _handle_udp(Ipv4_packet &ip, + Size_guard &size_guard); + + void _handle_arp(Ethernet_frame ð, + Size_guard &size_guard); + + void _broadcast_arp_request(Ipv4_address const &ip); + + void _send_arp_reply(Ethernet_frame &req_eth, + Arp_packet &req_arp); + + void _send_sntp_request(Duration not_used = Duration(Microseconds(0))); + + public: + + struct Invalid_arguments : Exception { }; + + Main(Env &env); + + + /***************** + ** Nic_handler ** + *****************/ + + void handle_eth(Ethernet_frame ð, + Size_guard &size_guard) override; + + + /************************* + ** Dhcp_client_handler ** + *************************/ + + void ip_config(Ipv4_config const &ip_config) override; + + Ipv4_config const &ip_config() const override { return *_ip_config; } +}; + + +void Main::ip_config(Ipv4_config const &ip_config) +{ + if (_verbose) { + log("IP config: ", ip_config); } + + _ip_config.construct(ip_config); + _period.construct(_timer, *this, &Main::_send_sntp_request, _period_us); +} + + +Main::Main(Env &env) : _env(env) +{ + /* exit unsuccessful if parameters are invalid */ + if (_dst_ip == Ipv4_address()) { + throw Invalid_arguments(); } + + /* if there is a static IP config, start sending pings periodically */ + if (ip_config().valid) { + _period.construct(_timer, *this, &Main::_send_sntp_request, _period_us); } + + /* else, start the DHCP client for requesting an IP config */ + else { + _dhcp_client.construct(_heap, _timer, _nic, *this); } + + reporter.enabled(true); +} + + +void Main::handle_eth(Ethernet_frame ð, + Size_guard &size_guard) +{ + try { + /* print receipt message */ + if (_verbose) { + log("rcv ", eth); } + + if (!ip_config().valid) { + _dhcp_client->handle_eth(eth, size_guard); } + + /* drop packet if ETH does not target us */ + if (eth.dst() != _nic.mac() && + eth.dst() != Ethernet_frame::broadcast()) + { + if (_verbose) { + log("bad ETH destination"); } + return; + } + /* select ETH sub-protocol */ + switch (eth.type()) { + case Ethernet_frame::Type::ARP: _handle_arp(eth, size_guard); break; + case Ethernet_frame::Type::IPV4: _handle_ip(eth, size_guard); break; + default: ; } + } + catch (Drop_packet_inform exception) { + if (_verbose) { + log("drop packet: ", exception.msg); } + } +} + + +void Main::_handle_ip(Ethernet_frame ð, + Size_guard &size_guard) +{ + /* drop packet if IP does not target us */ + Ipv4_packet &ip = eth.data(size_guard); + if (ip.dst() != ip_config().interface.address && + ip.dst() != Ipv4_packet::broadcast()) + { + if (_verbose) { + log("bad IP destination"); } + return; + } + /* drop packet if IP checksum is invalid */ + if (ip.checksum_error()) { + if (_verbose) { + log("bad IP checksum"); } + return; + } + /* select IP sub-protocol */ + switch (ip.protocol()) { + case Ipv4_packet::Protocol::UDP: _handle_udp(ip, size_guard); break; + default: ; } +} + + +void Main::_handle_udp(Ipv4_packet &ip, + Size_guard &size_guard) +{ + /* drop packet if UDP checksum is invalid */ + Udp_packet &udp = ip.data(size_guard); + if (udp.checksum_error(ip.src(), ip.dst())) { + if (_verbose) { + log("bad UDP checksum"); } + return; + } + /* drop packet if UDP source port is invalid */ + if (udp.src_port().value != Sntp_packet::UDP_PORT) { + if (_verbose) { + log("bad UDP source port"); } + return; + } + /* drop packet if UDP destination port is invalid */ + if (udp.dst_port().value != SRC_PORT) { + if (_verbose) { + log("bad UDP destination port"); } + return; + } + + Sntp_packet &sntp = udp.data(size_guard); + if (sntp.version_number() != Sntp_packet::VERSION_NUMBER) { + if (_verbose) { + log("bad SNTP version number"); } + return; + } + if (sntp.mode() != Sntp_packet::MODE_SERVER) { + if (_verbose) { + log("bad SNTP mode"); } + return; + } + Rtc::Timestamp rtc_ts { _sntp_ts_to_rtc_ts(sntp.transmit_timestamp()) }; + Reporter::Xml_generator xml(reporter, [&] () { + xml.attribute("year", rtc_ts.year); + xml.attribute("month", rtc_ts.month); + xml.attribute("day", rtc_ts.day); + xml.attribute("hour", rtc_ts.hour); + xml.attribute("minute", rtc_ts.minute); + xml.attribute("second", rtc_ts.second); + }); +} + + +void Main::_handle_arp(Ethernet_frame ð, + Size_guard &size_guard) +{ + /* check ARP protocol- and hardware address type */ + Arp_packet &arp = eth.data(size_guard); + if (!arp.ethernet_ipv4()) { + error("ARP for unknown protocol"); } + + /* check ARP operation */ + switch (arp.opcode()) { + case Arp_packet::REPLY: + + /* check whether we waited for this ARP reply */ + if (_dst_mac != Mac_address()) { + return; } + + if (ip_config().interface.prefix_matches(_dst_ip)) { + if (arp.src_ip() != _dst_ip) { + return; } + } else { + if (arp.src_ip() != ip_config().gateway) { + return; } + } + /* set destination MAC address and retry to ping */ + _dst_mac = arp.src_mac(); + _send_sntp_request(); + return; + + case Arp_packet::REQUEST: + + /* check whether the ARP request targets us */ + if (arp.dst_ip() != ip_config().interface.address) { + return; } + + _send_arp_reply(eth, arp); + + default: ; } +} + + +void Main::_send_arp_reply(Ethernet_frame &req_eth, + Arp_packet &req_arp) +{ + _nic.send(sizeof(Ethernet_frame) + sizeof(Arp_packet), + [&] (void *pkt_base, Size_guard &size_guard) + { + /* write Ethernet header */ + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(req_eth.src()); + eth.src(_nic.mac()); + eth.type(Ethernet_frame::Type::ARP); + + /* write ARP header */ + Arp_packet &arp = eth.construct_at_data(size_guard); + arp.hardware_address_type(Arp_packet::ETHERNET); + arp.protocol_address_type(Arp_packet::IPV4); + arp.hardware_address_size(sizeof(Mac_address)); + arp.protocol_address_size(sizeof(Ipv4_address)); + arp.opcode(Arp_packet::REPLY); + arp.src_mac(_nic.mac()); + arp.src_ip(ip_config().interface.address); + arp.dst_mac(req_eth.src()); + arp.dst_ip(req_arp.src_ip()); + }); +} + + +void Main::_broadcast_arp_request(Ipv4_address const &dst_ip) +{ + _nic.send(sizeof(Ethernet_frame) + sizeof(Arp_packet), + [&] (void *pkt_base, Size_guard &size_guard) + { + /* write Ethernet header */ + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(Mac_address(0xff)); + eth.src(_nic.mac()); + eth.type(Ethernet_frame::Type::ARP); + + /* write ARP header */ + Arp_packet &arp = eth.construct_at_data(size_guard); + arp.hardware_address_type(Arp_packet::ETHERNET); + arp.protocol_address_type(Arp_packet::IPV4); + arp.hardware_address_size(sizeof(Mac_address)); + arp.protocol_address_size(sizeof(Ipv4_address)); + arp.opcode(Arp_packet::REQUEST); + arp.src_mac(_nic.mac()); + arp.src_ip(ip_config().interface.address); + arp.dst_mac(Mac_address(0xff)); + arp.dst_ip(dst_ip); + }); +} + + +void Main::_send_sntp_request(Duration) +{ + /* if we do not yet know the Ethernet destination, request it via ARP */ + if (_dst_mac == Mac_address()) { + if (ip_config().interface.prefix_matches(_dst_ip)) { + _broadcast_arp_request(_dst_ip); } + else { + _broadcast_arp_request(ip_config().gateway); } + return; + } + _nic.send(sizeof(Ethernet_frame) + sizeof(Ipv4_packet) + + sizeof(Udp_packet) + sizeof(Sntp_packet), + [&] (void *pkt_base, Size_guard &size_guard) + { + /* create ETH header */ + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(_dst_mac); + eth.src(_nic.mac()); + eth.type(Ethernet_frame::Type::IPV4); + + /* create IP header */ + size_t const ip_off = size_guard.head_size(); + Ipv4_packet &ip = eth.construct_at_data(size_guard); + ip.header_length(sizeof(Ipv4_packet) / 4); + ip.version(4); + ip.time_to_live(IPV4_TIME_TO_LIVE); + ip.src(ip_config().interface.address); + ip.dst(_dst_ip); + + /* adapt IP header to UDP */ + ip.protocol(Ipv4_packet::Protocol::UDP); + + /* create UDP header */ + size_t const udp_off = size_guard.head_size(); + Udp_packet &udp = ip.construct_at_data(size_guard); + udp.src_port(Port(SRC_PORT)); + udp.dst_port(Port(Sntp_packet::UDP_PORT)); + + /* create SNTP header */ + Sntp_packet &sntp = udp.construct_at_data(size_guard); + sntp.version_number(Sntp_packet::VERSION_NUMBER); + sntp.mode(Sntp_packet::MODE_CLIENT); + + /* finish UDP header */ + udp.length(size_guard.head_size() - udp_off); + udp.update_checksum(ip.src(), ip.dst()); + + /* finish IP header */ + ip.total_length(size_guard.head_size() - ip_off); + ip.update_checksum(); + }); +} + +void Component::construct(Env &env) { static Main main(env); } diff --git a/repos/libports/src/app/sntp_client/nic.cc b/repos/libports/src/app/sntp_client/nic.cc new file mode 100644 index 000000000..dd835d63e --- /dev/null +++ b/repos/libports/src/app/sntp_client/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/libports/src/app/sntp_client/nic.h b/repos/libports/src/app/sntp_client/nic.h new file mode 100644 index 000000000..f29d337c2 --- /dev/null +++ b/repos/libports/src/app/sntp_client/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/libports/src/app/sntp_client/protocol.h b/repos/libports/src/app/sntp_client/protocol.h new file mode 100644 index 000000000..03e774d8d --- /dev/null +++ b/repos/libports/src/app/sntp_client/protocol.h @@ -0,0 +1,32 @@ +/* + * \brief Supported protocols + * \author Martin Stein + * \date 2018-03-27 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef __PROTOCOL_H_ +#define __PROTOCOL_H_ + +/* Genode includes */ +#include + +namespace Genode { enum class Protocol : uint16_t { ICMP, UDP }; } + +namespace Genode +{ + inline size_t ascii_to(char const *s, Protocol &result) + { + if (!strcmp(s, "icmp", 4)) { result = Protocol::ICMP; return 4; } + if (!strcmp(s, "udp", 3)) { result = Protocol::UDP; return 3; } + return 0; + } +} + +#endif /* __PROTOCOL_H_ */ diff --git a/repos/libports/src/app/sntp_client/target.mk b/repos/libports/src/app/sntp_client/target.mk new file mode 100644 index 000000000..fe0e2f35b --- /dev/null +++ b/repos/libports/src/app/sntp_client/target.mk @@ -0,0 +1,17 @@ +TARGET = sntp_client + +LIBS += base net + +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) + +CONFIG_XSD = config.xsd + +# musl-libc contrib sources +MUSL_TM = $(REP_DIR)/src/lib/musl_tm +SRC_C = secs_to_tm.c tm_to_secs.c +INC_DIR += $(MUSL_TM) + +vpath %.c $(MUSL_TM) diff --git a/repos/libports/src/app/sntp_client/xml_node.cc b/repos/libports/src/app/sntp_client/xml_node.cc new file mode 100644 index 000000000..b2dbee155 --- /dev/null +++ b/repos/libports/src/app/sntp_client/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, + uint64_t const default_sec) +{ + uint64_t sec = node.attribute_value(name, (uint64_t)0); + if (!sec) { + sec = default_sec; + } + return Microseconds(sec * 1000 * 1000); +} diff --git a/repos/libports/src/app/sntp_client/xml_node.h b/repos/libports/src/app/sntp_client/xml_node.h new file mode 100644 index 000000000..33cf34647 --- /dev/null +++ b/repos/libports/src/app/sntp_client/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, + uint64_t const default_sec); +} + +#endif /* _XML_NODE_H_ */ diff --git a/repos/os/include/net/sntp.h b/repos/os/include/net/sntp.h new file mode 100644 index 000000000..250df5ad6 --- /dev/null +++ b/repos/os/include/net/sntp.h @@ -0,0 +1,111 @@ +/* + * \brief Simple Network Time Protocol (SNTP) Version 4 (RFC 4330) + * \author Martin Stein + * \date 2019-07-11 + */ + +/* + * Copyright (C) 2019 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 _NET__SNTP_H_ +#define _NET__SNTP_H_ + +/* Genode includes */ +#include + +namespace Net +{ + class Sntp_timestamp; + class Sntp_packet; +} + + +class Net::Sntp_timestamp +{ + private: + + using uint32_t = Genode::uint32_t; + using uint64_t = Genode::uint64_t; + + enum { UNIX_TS_OFFSET_SEC = 2208988800 }; + + uint32_t const _seconds; + uint32_t const _seconds_fraction; + + public: + + Sntp_timestamp(uint64_t const plain_value) + : + _seconds { (uint32_t)(plain_value >> 32) }, + _seconds_fraction { (uint32_t)(plain_value) } + { } + + uint64_t to_unix_timestamp() const + { + return _seconds - UNIX_TS_OFFSET_SEC; + } + + static Sntp_timestamp from_unix_timestamp(uint64_t const unix_ts) + { + return unix_ts + UNIX_TS_OFFSET_SEC; + } + + + /*************** + ** Accessors ** + ***************/ + + uint32_t seconds() const { return _seconds; } + uint32_t seconds_fraction() const { return _seconds_fraction; } +}; + + +class Net::Sntp_packet +{ + private: + + Genode::uint8_t _byte_0; + Genode::uint8_t _stratum; + Genode::uint8_t _poll; + Genode::uint8_t _precision; + Genode::uint32_t _root_delay; + Genode::uint32_t _root_dispersion; + Genode::uint32_t _reference_identifier; + Genode::uint64_t _reference_timestamp; + Genode::uint64_t _originate_timestamp; + Genode::uint64_t _receive_timestamp; + Genode::uint64_t _transmit_timestamp; + + struct Byte_0 : Genode::Register<8> { + struct Mode : Bitfield<0, 3> { }; + struct Version_number : Bitfield<3, 3> { }; + }; + + public: + + enum { UDP_PORT = 123 }; + enum { VERSION_NUMBER = 4 }; + enum { MODE_CLIENT = 3 }; + enum { MODE_SERVER = 4 }; + + + /*************** + ** Accessors ** + ***************/ + + void version_number(Genode::uint8_t v) { Byte_0::Version_number::set(_byte_0, v); } + void mode (Genode::uint8_t v) { Byte_0::Mode ::set(_byte_0, v); } + + Byte_0::access_t version_number() const { return Byte_0::Version_number::get(_byte_0); } + Byte_0::access_t mode() const { return Byte_0::Mode::get(_byte_0); } + Genode::uint64_t transmit_timestamp() const { return host_to_big_endian(_transmit_timestamp); } + Genode::uint64_t receive_timestamp() const { return host_to_big_endian(_receive_timestamp); } + Genode::uint64_t originate_timestamp() const { return host_to_big_endian(_originate_timestamp); } + +} __attribute__((packed)); + +#endif /* _NET__SNTP_H_ */ diff --git a/tool/autopilot.list b/tool/autopilot.list index fcd994e98..905b81fa5 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -52,6 +52,7 @@ seoul-auto smartcard smbios_decoder smp +sntp_client solo5 sub_rm tar_rom