ping: dynamic IP configuration

Use DHCP to obtain and maintain an IP configuration if no static
configuration is given.

Issue #2775
This commit is contained in:
Martin Stein 2018-04-26 15:57:53 +02:00 committed by Christian Helmuth
parent ce57319e4b
commit d93fda594a
16 changed files with 1036 additions and 162 deletions

View File

@ -57,8 +57,7 @@ append config {
</start> </start>
<start name="ping"> <start name="ping">
<resource name="RAM" quantum="8M"/> <resource name="RAM" quantum="8M"/>
<config src_ip="10.0.2.55" <config dst_ip="10.0.2.2"
dst_ip="10.0.2.2"
period_sec="1" period_sec="1"
verbose="no" verbose="no"
count="3"/> count="3"/>
@ -96,4 +95,4 @@ append_if [have_spec lan9118] qemu_args " -net nic,model=lan9118 "
append qemu_args " -net user -nographic " 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

View File

@ -119,7 +119,8 @@ append config {
<start name="ping_11"> <start name="ping_11">
<binary name="ping"/> <binary name="ping"/>
<resource name="RAM" quantum="8M"/> <resource name="RAM" quantum="8M"/>
<config src_ip="10.0.3.2" <config interface="10.0.3.2/24"
gateway="10.0.3.1"
dst_ip="10.0.2.2" dst_ip="10.0.2.2"
period_sec="1" period_sec="1"
verbose="no"/> verbose="no"/>
@ -132,7 +133,8 @@ append config {
<start name="ping_12"> <start name="ping_12">
<binary name="ping"/> <binary name="ping"/>
<resource name="RAM" quantum="8M"/> <resource name="RAM" quantum="8M"/>
<config src_ip="10.0.3.3" <config interface="10.0.3.3/24"
gateway="10.0.3.1"
dst_ip="10.0.2.2" dst_ip="10.0.2.2"
period_sec="1" period_sec="1"
verbose="no"/> verbose="no"/>
@ -145,7 +147,8 @@ append config {
<start name="ping_21"> <start name="ping_21">
<binary name="ping"/> <binary name="ping"/>
<resource name="RAM" quantum="8M"/> <resource name="RAM" quantum="8M"/>
<config src_ip="10.0.5.2" <config interface="10.0.5.2/24"
gateway="10.0.5.1"
dst_ip="10.0.2.2" dst_ip="10.0.2.2"
period_sec="1" period_sec="1"
verbose="no"/> verbose="no"/>
@ -158,7 +161,8 @@ append config {
<start name="ping_22"> <start name="ping_22">
<binary name="ping"/> <binary name="ping"/>
<resource name="RAM" quantum="8M"/> <resource name="RAM" quantum="8M"/>
<config src_ip="10.0.5.3" <config interface="10.0.5.3/24"
gateway="10.0.5.1"
dst_ip="10.0.1.2" dst_ip="10.0.1.2"
period_sec="1" period_sec="1"
verbose="no"/> verbose="no"/>

View File

@ -1,17 +1,16 @@
The 'ping' component continuously sends ICMP Echo requests to a given IP host 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 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 Echo handshake it prints a short statistic. The ICMP data field gets filled
static IP configuration. The ICMP data field gets filled with the letters of with the letters of the alphabet ('a' to 'z') repeatedly.
the alphabet ('a' to 'z') repeatedly.
Configuration Configuration
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
This is an example configuration of the component which shows the default 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':
! <config src_ip="10.0.0.72" ! <config interface="10.0.0.72/24"
! dst_ip="10.0.0.24" ! dst_ip="10.0.0.24"
! period_sec="5" ! period_sec="5"
! verbose="no" ! verbose="no"
@ -19,8 +18,12 @@ value for each attribute except 'config.dst_ip' and 'config.src_ip':
This is a short description of the tags and attributes: This is a short description of the tags and attributes:
:config.src_ip: :config.interface:
Mandatory. IP address of the component. 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: :config.dst_ip:
Mandatory. IP address of the target host. Mandatory. IP address of the target host.

View File

@ -25,11 +25,18 @@
</xs:restriction> </xs:restriction>
</xs:simpleType><!-- Ipv4_address --> </xs:simpleType><!-- Ipv4_address -->
<xs:simpleType name="Ipv4_address_prefix">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/[0-9]{1,2}"/>
</xs:restriction>
</xs:simpleType><!-- Ipv4_address_prefix -->
<xs:element name="config"> <xs:element name="config">
<xs:complexType> <xs:complexType>
<xs:attribute name="verbose" type="Boolean" /> <xs:attribute name="verbose" type="Boolean" />
<xs:attribute name="dst_ip" type="Ipv4_address" /> <xs:attribute name="dst_ip" type="Ipv4_address" />
<xs:attribute name="src_ip" type="Ipv4_address" /> <xs:attribute name="interface" type="Ipv4_address_prefix" />
<xs:attribute name="gateway" type="Ipv4_address" />
<xs:attribute name="period_sec" type="Seconds" /> <xs:attribute name="period_sec" type="Seconds" />
<xs:attribute name="count" type="xs:positiveInteger" /> <xs:attribute name="count" type="xs:positiveInteger" />
</xs:complexType> </xs:complexType>

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 };
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 &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,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 <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 = 10 };
enum { REQUEST_TIMEOUT_SEC = 10 };
enum { OFFER_TIMEOUT_SEC = 10 };
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 { 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 &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

@ -11,6 +11,11 @@
* under the terms of the GNU Affero General Public License version 3. * 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>
/* Genode includes */ /* Genode includes */
#include <net/ipv4.h> #include <net/ipv4.h>
#include <net/ethernet.h> #include <net/ethernet.h>
@ -20,18 +25,10 @@
#include <base/heap.h> #include <base/heap.h>
#include <base/attached_rom_dataspace.h> #include <base/attached_rom_dataspace.h>
#include <timer_session/connection.h> #include <timer_session/connection.h>
#include <nic_session/connection.h>
#include <nic/packet_allocator.h>
using namespace Net; using namespace Net;
using namespace Genode; 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, Microseconds read_sec_attr(Xml_node const node,
char const *name, 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: private:
@ -57,34 +55,26 @@ class Main
enum { ICMP_DATA_SIZE = 56 }; enum { ICMP_DATA_SIZE = 56 };
enum { DEFAULT_COUNT = 5 }; enum { DEFAULT_COUNT = 5 };
enum { DEFAULT_PERIOD_SEC = 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; Env &_env;
Attached_rom_dataspace _config_rom { _env, "config" }; Attached_rom_dataspace _config_rom { _env, "config" };
Xml_node _config { _config_rom.xml() }; Xml_node _config { _config_rom.xml() };
Timer::Connection _timer { _env }; Timer::Connection _timer { _env };
Microseconds _send_time { 0 }; Microseconds _send_time { 0 };
Periodic_timeout _period { _timer, *this, &Main::_send_ping, Microseconds _period_us { read_sec_attr(_config, "period_sec", DEFAULT_PERIOD_SEC) };
read_sec_attr(_config, "period_sec", DEFAULT_PERIOD_SEC) }; Constructible<Periodic_timeout> _period { };
Heap _heap { &_env.ram(), &_env.rm() }; Heap _heap { &_env.ram(), &_env.rm() };
Nic::Packet_allocator _pkt_alloc { &_heap }; bool const _verbose { _config.attribute_value("verbose", false) };
Nic::Connection _nic { _env, &_pkt_alloc, BUF_SIZE, BUF_SIZE }; Net::Nic _nic { _env, _heap, *this, _verbose };
Signal_handler _sink_ack { _env.ep(), *this, &Main::_ack_avail }; Ipv4_address const _dst_ip { _config.attribute_value("dst_ip", Ipv4_address()) };
Signal_handler _sink_submit { _env.ep(), *this, &Main::_ready_to_submit }; Mac_address _dst_mac { };
Signal_handler _source_ack { _env.ep(), *this, &Main::_ready_to_ack }; uint16_t _ip_id { 1 };
Signal_handler _source_submit { _env.ep(), *this, &Main::_packet_avail }; uint16_t _icmp_seq { 1 };
bool const _verbose { _config.attribute_value("verbose", false) }; unsigned long _count { _config.attribute_value("count", (unsigned long)DEFAULT_COUNT) };
Ipv4_address const _src_ip { _config.attribute_value("src_ip", Ipv4_address()) }; Constructible<Dhcp_client> _dhcp_client { };
Ipv4_address const _dst_ip { _config.attribute_value("dst_ip", Ipv4_address()) }; Reconstructible<Ipv4_config> _ip_config { _config.attribute_value("interface", Ipv4_address_prefix()),
Mac_address const _src_mac { _nic.mac_address() }; _config.attribute_value("gateway", Ipv4_address()),
Mac_address _dst_mac { }; Ipv4_address() };
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);
void _handle_ip(Ethernet_frame &eth, void _handle_ip(Ethernet_frame &eth,
Size_guard &size_guard); Size_guard &size_guard);
@ -103,94 +93,93 @@ class Main
void _handle_arp(Ethernet_frame &eth, void _handle_arp(Ethernet_frame &eth,
Size_guard &size_guard); Size_guard &size_guard);
void _broadcast_arp_request(); void _broadcast_arp_request(Ipv4_address const &ip);
void _send_arp_reply(Ethernet_frame &req_eth, void _send_arp_reply(Ethernet_frame &req_eth,
Arp_packet &req_arp); Arp_packet &req_arp);
template <typename FUNC>
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))); 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: public:
struct Invalid_arguments : Exception { }; struct Invalid_arguments : Exception { };
Main(Env &env); 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_ping, _period_us);
}
Main::Main(Env &env) : _env(env) Main::Main(Env &env) : _env(env)
{ {
/* exit unsuccessful if parameters are invalid */ /* exit unsuccessful if parameters are invalid */
if (_src_ip == Ipv4_address() || if (_dst_ip == Ipv4_address() || _count == 0) {
_dst_ip == Ipv4_address() || throw Invalid_arguments(); }
_count == 0)
{ /* if there is a static IP config, start sending pings periodically */
throw Invalid_arguments(); if (ip_config().valid) {
} _period.construct(_timer, *this, &Main::_send_ping, _period_us); }
/* install packet stream signals */
_nic.rx_channel()->sigh_ready_to_ack(_sink_ack); /* else, start the DHCP client for requesting an IP config */
_nic.rx_channel()->sigh_packet_avail(_sink_submit); else {
_nic.tx_channel()->sigh_ack_avail(_source_ack); _dhcp_client.construct(_heap, _timer, _nic, *this); }
_nic.tx_channel()->sigh_ready_to_submit(_source_submit);
} }
void Main::_handle_eth(void *const eth_base, void Main::handle_eth(Ethernet_frame &eth,
Size_guard &size_guard) Size_guard &size_guard)
{ {
/* print receipt message */ try {
Ethernet_frame &eth = Ethernet_frame::cast_from(eth_base, size_guard); /* print receipt message */
if (_verbose) {
log("rcv ", eth); }
/* drop packet if ETH does not target us */
if (eth.dst() != _src_mac &&
eth.dst() != Ethernet_frame::broadcast())
{
if (_verbose) { if (_verbose) {
log("bad ETH destination"); } log("rcv ", eth); }
return;
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 &eth,
{ {
/* drop packet if IP does not target us */ /* drop packet if IP does not target us */
Ipv4_packet &ip = eth.data<Ipv4_packet>(size_guard); Ipv4_packet &ip = eth.data<Ipv4_packet>(size_guard);
if (ip.dst() != _src_ip && if (ip.dst() != ip_config().interface.address &&
ip.dst() != Ipv4_packet::broadcast()) ip.dst() != Ipv4_packet::broadcast())
{ {
if (_verbose) { if (_verbose) {
@ -355,9 +344,16 @@ void Main::_handle_arp(Ethernet_frame &eth,
case Arp_packet::REPLY: case Arp_packet::REPLY:
/* check whether we waited for this ARP 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; } 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 */ /* set destination MAC address and retry to ping */
_dst_mac = arp.src_mac(); _dst_mac = arp.src_mac();
_send_ping(); _send_ping();
@ -366,7 +362,7 @@ void Main::_handle_arp(Ethernet_frame &eth,
case Arp_packet::REQUEST: case Arp_packet::REQUEST:
/* check whether the ARP request targets us */ /* check whether the ARP request targets us */
if (arp.dst_ip() != _src_ip) { if (arp.dst_ip() != ip_config().interface.address) {
return; } return; }
_send_arp_reply(eth, arp); _send_arp_reply(eth, arp);
@ -375,40 +371,16 @@ void Main::_handle_arp(Ethernet_frame &eth,
} }
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, void Main::_send_arp_reply(Ethernet_frame &req_eth,
Arp_packet &req_arp) Arp_packet &req_arp)
{ {
_send(sizeof(Ethernet_frame) + sizeof(Arp_packet), _nic.send(sizeof(Ethernet_frame) + sizeof(Arp_packet),
[&] (void *pkt_base, Size_guard &size_guard) [&] (void *pkt_base, Size_guard &size_guard)
{ {
/* write Ethernet header */ /* write Ethernet header */
Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard); Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard);
eth.dst(req_eth.src()); eth.dst(req_eth.src());
eth.src(_src_mac); eth.src(_nic.mac());
eth.type(Ethernet_frame::Type::ARP); eth.type(Ethernet_frame::Type::ARP);
/* write ARP header */ /* write ARP header */
@ -418,23 +390,23 @@ void Main::_send_arp_reply(Ethernet_frame &req_eth,
arp.hardware_address_size(sizeof(Mac_address)); arp.hardware_address_size(sizeof(Mac_address));
arp.protocol_address_size(sizeof(Ipv4_address)); arp.protocol_address_size(sizeof(Ipv4_address));
arp.opcode(Arp_packet::REPLY); arp.opcode(Arp_packet::REPLY);
arp.src_mac(_src_mac); arp.src_mac(_nic.mac());
arp.src_ip(_src_ip); arp.src_ip(ip_config().interface.address);
arp.dst_mac(req_eth.src()); arp.dst_mac(req_eth.src());
arp.dst_ip(req_arp.src_ip()); 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), _nic.send(sizeof(Ethernet_frame) + sizeof(Arp_packet),
[&] (void *pkt_base, Size_guard &size_guard) [&] (void *pkt_base, Size_guard &size_guard)
{ {
/* write Ethernet header */ /* write Ethernet header */
Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard); Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard);
eth.dst(Mac_address(0xff)); eth.dst(Mac_address(0xff));
eth.src(_src_mac); eth.src(_nic.mac());
eth.type(Ethernet_frame::Type::ARP); eth.type(Ethernet_frame::Type::ARP);
/* write ARP header */ /* write ARP header */
@ -444,28 +416,32 @@ void Main::_broadcast_arp_request()
arp.hardware_address_size(sizeof(Mac_address)); arp.hardware_address_size(sizeof(Mac_address));
arp.protocol_address_size(sizeof(Ipv4_address)); arp.protocol_address_size(sizeof(Ipv4_address));
arp.opcode(Arp_packet::REQUEST); arp.opcode(Arp_packet::REQUEST);
arp.src_mac(_src_mac); arp.src_mac(_nic.mac());
arp.src_ip(_src_ip); arp.src_ip(ip_config().interface.address);
arp.dst_mac(Mac_address(0xff)); arp.dst_mac(Mac_address(0xff));
arp.dst_ip(_dst_ip); arp.dst_ip(dst_ip);
}); });
} }
void Main::_send_ping(Duration) void Main::_send_ping(Duration)
{ {
/* if we do not yet know the Ethernet destination, request it via ARP */
if (_dst_mac == Mac_address()) { 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; return;
} }
_send(sizeof(Ethernet_frame) + sizeof(Ipv4_packet) + _nic.send(sizeof(Ethernet_frame) + sizeof(Ipv4_packet) +
sizeof(Icmp_packet) + ICMP_DATA_SIZE, sizeof(Icmp_packet) + ICMP_DATA_SIZE,
[&] (void *pkt_base, Size_guard &size_guard) [&] (void *pkt_base, Size_guard &size_guard)
{ {
/* create ETH header */ /* create ETH header */
Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard); Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard);
eth.dst(_dst_mac); eth.dst(_dst_mac);
eth.src(_src_mac); eth.src(_nic.mac());
eth.type(Ethernet_frame::Type::IPV4); eth.type(Ethernet_frame::Type::IPV4);
/* create IP header */ /* create IP header */
@ -475,7 +451,7 @@ void Main::_send_ping(Duration)
ip.version(4); ip.version(4);
ip.time_to_live(IPV4_TIME_TO_LIVE); ip.time_to_live(IPV4_TIME_TO_LIVE);
ip.protocol(Ipv4_packet::Protocol::ICMP); ip.protocol(Ipv4_packet::Protocol::ICMP);
ip.src(_src_ip); ip.src(ip_config().interface.address);
ip.dst(_dst_ip); ip.dst(_dst_ip);
/* create ICMP header */ /* create ICMP header */

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);
}
}

129
repos/os/src/app/ping/nic.h Normal file
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

@ -2,7 +2,8 @@ TARGET = ping
LIBS += base net 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) INC_DIR += $(PRG_DIR)

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,
unsigned long const default_sec)
{
unsigned long sec = node.attribute_value(name, 0UL);
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 <os/duration.h>
namespace Genode {
Microseconds read_sec_attr(Xml_node const node,
char const *name,
unsigned long const default_sec);
}
#endif /* _XML_NODE_H_ */