sntp_client: report received transmit timestamp

For now in libports because it relies on contrib sources from musl doing
the time data conversion.

Ref #3448
Ref #3450
This commit is contained in:
Martin Stein 2019-07-12 15:13:40 +02:00 committed by Christian Helmuth
parent 322bacd380
commit cb6377355e
18 changed files with 1629 additions and 0 deletions

View File

@ -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 {
<config>
<parent-provides>
<service name="ROM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="report_rom">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="yes">
<policy label_suffix="set_rtc" report="sntp_client -> set_rtc"/>
</config>
</start>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="drivers" caps="1000">
<resource name="RAM" quantum="32M" constrain_phys="yes"/>
<binary name="init"/>
<route>
<service name="ROM" label="config"> <parent label="drivers.config"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<any-service> <parent/> </any-service>
</route>
<provides> <service name="Nic"/> </provides>
</start>
<start name="sntp_client">
<resource name="RAM" quantum="8M"/>
<config verbose="no" dst_ip="193.175.73.151" period_min="1"/>
</start>
</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] <set_rtc year.*?report_rom] <set_rtc year.*?\n"
run_genode_until $done_string 90

View File

@ -0,0 +1,62 @@
The 'ping' component continuously sends ICMP Echo or UDP requests to a given
IP host and waits for the corresponding ICMP Echo or UDP replies. For each
successfull ICMP Echo or UDP 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.interface':
! <config interface="10.0.0.72/24"
! dst_ip="10.0.0.24"
! dst_port="50000"
! protocol="icmp"
! period_sec="5"
! verbose="no"
! count="5" />
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'.

View File

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="base_types.xsd"/>
<xs:include schemaLocation="net_types.xsd"/>
<xs:include schemaLocation="timeout_types.xsd"/>
<xs:simpleType name="Protocol">
<xs:restriction base="xs:string">
<xs:enumeration value="icmp" />
<xs:enumeration value="udp" />
</xs:restriction>
</xs:simpleType><!-- Protocol -->
<xs:element name="config">
<xs:complexType>
<xs:attribute name="verbose" type="Boolean" />
<xs:attribute name="dst_ip" type="Ipv4_address" />
<xs:attribute name="interface" type="Ipv4_address_prefix" />
<xs:attribute name="gateway" type="Ipv4_address" />
<xs:attribute name="period_min" type="Minutes" />
</xs:complexType>
</xs:element><!-- config -->
</xs:schema>

View File

@ -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 <nic.h>
#include <dhcp_client.h>
#include <ipv4_config.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(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 &eth, 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<Ipv4_packet>(size_guard);
if (ip.protocol() != Ipv4_packet::Protocol::UDP) {
throw Drop_packet_inform("DHCP client expects UDP packet"); }
Udp_packet &udp = ip.data<Udp_packet>(size_guard);
if (!Dhcp_packet::is_dhcp(&udp)) {
throw Drop_packet_inform("DHCP client expects DHCP packet"); }
Dhcp_packet &dhcp = udp.data<Dhcp_packet>(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<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) { }
Ipv4_config ip_config(
Ipv4_address_prefix(
dhcp.yiaddr(),
dhcp.option<Dhcp_packet::Subnet_mask>().value()),
dhcp.option<Dhcp_packet::Router_ipv4>().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<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)
{
_nic.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(_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<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(_nic.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>(_nic.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>(_nic.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,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 <net/dhcp.h>
#include <timer_session/connection.h>
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<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 &_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 &eth,
Size_guard &size_guard);
};
#endif /* _DHCP_CLIENT_H_ */

View File

@ -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 <ipv4_address_prefix.h>
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++;
}

View File

@ -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 <net/ipv4.h>
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_ */

View File

@ -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 <base/log.h>
/* local includes */
#include <ipv4_config.h>
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); }
}

View File

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

View File

@ -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 <nic.h>
#include <ipv4_config.h>
#include <dhcp_client.h>
#include <tm.h>
/* Genode includes */
#include <net/ipv4.h>
#include <net/ethernet.h>
#include <net/arp.h>
#include <net/sntp.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <timer_session/connection.h>
#include <rtc_session/connection.h>
#include <os/reporter.h>
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<Main>;
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<Periodic_timeout> _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> _dhcp_client { };
Reconstructible<Ipv4_config> _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 &eth,
Size_guard &size_guard);
void _handle_udp(Ipv4_packet &ip,
Size_guard &size_guard);
void _handle_arp(Ethernet_frame &eth,
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 &eth,
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 &eth,
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 &eth,
Size_guard &size_guard)
{
/* drop packet if IP does not target us */
Ipv4_packet &ip = eth.data<Ipv4_packet>(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<Udp_packet>(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<Sntp_packet>(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 &eth,
Size_guard &size_guard)
{
/* check ARP protocol- and hardware address type */
Arp_packet &arp = eth.data<Arp_packet>(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 &eth = 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<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::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 &eth = 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<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(_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 &eth = 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<Ipv4_packet>(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<Udp_packet>(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<Sntp_packet>(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); }

View File

@ -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 <nic.h>
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);
}
}

View File

@ -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 <nic_session/connection.h>
#include <nic/packet_allocator.h>
#include <net/ethernet.h>
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 &eth,
Size_guard &size_guard) = 0;
virtual ~Nic_handler() { }
};
class Net::Nic
{
private:
using Signal_handler = Genode::Signal_handler<Nic>;
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 <typename FUNC>
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_ */

View File

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

View File

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

View File

@ -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 <xml_node.h>
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);
}

View File

@ -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 <util/xml_node.h>
#include <base/duration.h>
namespace Genode {
Microseconds read_sec_attr(Xml_node const node,
char const *name,
uint64_t const default_sec);
}
#endif /* _XML_NODE_H_ */

111
repos/os/include/net/sntp.h Normal file
View File

@ -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 <util/register.h>
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_ */

View File

@ -52,6 +52,7 @@ seoul-auto
smartcard
smbios_decoder
smp
sntp_client
solo5
sub_rm
tar_rom