nic_router: support multiple DHCP option 6 entries

* The NIC router now considers, memorizes, and, if configured, reports
  multiple DHCP option 6 entries from DHCP replies that it received as DHCP
  client
* A DHCP server at the NIC router can now be configured statically with
  multiple DNS server addresses to propagate
* The 'dns_server_from' attribute of the DHCP server of the NIC router now
  supports the forwarding of multiple DNS server addresses
* The automated run/nic_router_dhcp test tests all the above mentioned new
  functionality and reconfiguring it at runtime. The test was added to the
  autopilot.
* All run scripts were adapted to fit the new NIC router configuration
  interface

Fixes #3952
This commit is contained in:
Martin Stein 2020-11-16 15:43:40 +01:00 committed by Norman Feske
parent 306466fc60
commit bad8caee3f
39 changed files with 2485 additions and 143 deletions

View File

@ -228,8 +228,11 @@ proc test_7_router_config { } {
<domain name="t7_d1" interface="100.200.0.1/24">
<dhcp-server ip_first="100.200.0.32"
ip_last="100.200.0.64"
ip_lease_time_sec="3600"
dns_server="8.8.8.8"/>
ip_lease_time_sec="3600">
<dns-server ip="8.8.8.8"/>
</dhcp-server>
<tcp dst="10.0.0.0/16">
<permit port="2345" domain="t7_d2" />

View File

@ -159,8 +159,9 @@ proc test_7_router_config { } {
<domain name="lan_2" interface="100.200.0.1/24">
<dhcp-server ip_first="100.200.0.32"
ip_last="100.200.0.64"
ip_lease_time_sec="3600"
dns_server="8.8.8.8"/>
ip_lease_time_sec="3600">
<dns-server ip="8.8.8.8"/>
</dhcp-server>
<tcp dst="10.0.0.0/16">
<permit port="2345" domain="lan_3" />

View File

@ -0,0 +1,282 @@
#
# See os/src/test/nic_router_dhcp/README for a documentation.
#
create_boot_directory
import_from_depot [depot_user]/src/[base_src]
set build_components {
init
server/dynamic_rom
test/nic_router_dhcp/client
server/nic_router
}
lappend_if [nic_router_2_managed] build_components test/nic_router_dhcp/manager
lappend_if [nic_router_2_managed] build_components server/report_rom
build $build_components
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="200"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="dynamic_rom">
<resource name="RAM" quantum="4M"/>
<provides><service name="ROM"/> </provides>
<config verbose="yes">
<rom name="nic_router_1.config">
<inline>
<config>
<policy label="nic_router_2 -> " domain="downlink"/>
<domain name="downlink" interface="10.2.3.1/24">
<dhcp-server ip_first="10.2.3.2"
ip_last="10.2.3.2">
<dns-server ip="1.2.3.4"/>
<dns-server ip="2.3.4.5"/>
<dns-server ip="3.4.5.6"/>
</dhcp-server>
</domain>
</config>
</inline>
<sleep milliseconds="3000"/>
<inline>
<config>
<policy label="nic_router_2 -> " domain="downlink"/>
<domain name="downlink" interface="10.2.3.1/24">
<dhcp-server ip_first="10.2.3.2"
ip_last="10.2.3.2">
<dns-server ip="4.5.6.7"/>
<dns-server ip="5.6.7.8"/>
</dhcp-server>
</domain>
</config>
</inline>
<sleep milliseconds="3000"/>
<inline>
<config>
<policy label="nic_router_2 -> " domain="downlink"/>
<domain name="downlink" interface="10.2.3.1/24">
<dhcp-server ip_first="10.2.3.2"
ip_last="10.2.3.2">
<dns-server ip="6.7.8.9"/>
</dhcp-server>
</domain>
</config>
</inline>
<sleep milliseconds="3000"/>
<inline>
<config>
<policy label="nic_router_2 -> " domain="downlink"/>
<domain name="downlink" interface="10.2.3.1/24">
<dhcp-server ip_first="10.2.3.2"
ip_last="10.2.3.2">
</dhcp-server>
</domain>
</config>
</inline>
<sleep milliseconds="3000"/>
</rom>
</config>
</start>
<start name="nic_router_1">
<binary name="nic_router"/>
<resource name="RAM" quantum="10M"/>
<provides><service name="Nic"/></provides>
<route>
<service name="ROM" label="config">
<child name="dynamic_rom" label="nic_router_1.config"/>
</service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="nic_router_2">
<binary name="nic_router"/>
<resource name="RAM" quantum="10M"/>
<provides><service name="Nic"/></provides>}
append_if [expr ![nic_router_2_managed]] config {
<config verbose_packets="no">
<policy label="test_client -> " domain="downlink"/>
<uplink domain="uplink"/>
<domain name="uplink"/>
<domain name="downlink" interface="10.0.3.1/24">
<dhcp-server ip_first="10.0.3.2"
ip_last="10.0.3.2"
dns_server_from="uplink"/>
</domain>
</config>}
append config {
<route>
<service name="Nic"> <child name="nic_router_1"/> </service>}
append_if [nic_router_2_managed] config {
<service name="ROM" label="config">
<child name="report_rom"/>
</service>}
append config {
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="test_client">
<binary name="test-nic_router_dhcp-client"/>
<resource name="RAM" quantum="10M"/>
<config verbose="no"/>
<route>
<service name="Nic"> <child name="nic_router_2"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>}
append_if [nic_router_2_managed] config {
<start name="report_rom">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="no">
<policy label="test_manager -> router_state"
report="nic_router_2 -> state"/>
<policy label="nic_router_2 -> config"
report="test_manager -> router_config"/>
</config>
</start>
<start name="test_manager">
<binary name="test-nic_router_dhcp-manager"/>
<resource name="RAM" quantum="1M"/>
<route>
<service name="ROM" label="router_state">
<child name="report_rom"/>
</service>
<service name="Report"> <child name="report_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>}
append config {
</config>}
install_config $config
set boot_modules {
init
dynamic_rom
nic_router
test-nic_router_dhcp-client
}
lappend_if [nic_router_2_managed] boot_modules test-nic_router_dhcp-manager
lappend_if [nic_router_2_managed] boot_modules report_rom
build_boot_image $boot_modules
append qemu_args " -nographic "
append_qemu_nic_args
append done_string ".*DHCP request completed:.*\n"
append done_string ".* IP lease time: 3600 seconds.*\n"
append done_string ".* Interface: 10.0.3.2/24.*\n"
append done_string ".* Router: 10.0.3.1.*\n"
append done_string ".* DNS server #1: 1.2.3.4.*\n"
append done_string ".* DNS server #2: 2.3.4.5.*\n"
append done_string ".* DNS server #3: 3.4.5.6.*\n"
append done_string ".*DHCP request completed:.*\n"
append done_string ".* IP lease time: 3600 seconds.*\n"
append done_string ".* Interface: 10.0.3.2/24.*\n"
append done_string ".* Router: 10.0.3.1.*\n"
append done_string ".* DNS server #1: 4.5.6.7.*\n"
append done_string ".* DNS server #2: 5.6.7.8.*\n"
append done_string ".*DHCP request completed:.*\n"
append done_string ".* IP lease time: 3600 seconds.*\n"
append done_string ".* Interface: 10.0.3.2/24.*\n"
append done_string ".* Router: 10.0.3.1.*\n"
append done_string ".* DNS server #1: 6.7.8.9.*\n"
append done_string ".*DHCP request completed:.*\n"
append done_string ".* IP lease time: 3600 seconds.*\n"
append done_string ".* Interface: 10.0.3.2/24.*\n"
append done_string ".* Router: 10.0.3.1.*\n"
append done_string ".*DHCP request completed:.*\n"
append done_string ".* IP lease time: 3600 seconds.*\n"
append done_string ".* Interface: 10.0.3.2/24.*\n"
append done_string ".* Router: 10.0.3.1.*\n"
append done_string ".* DNS server #1: 1.2.3.4.*\n"
append done_string ".* DNS server #2: 2.3.4.5.*\n"
append done_string ".* DNS server #3: 3.4.5.6.*\n"
run_genode_until $done_string 30

View File

@ -0,0 +1,6 @@
#
# See os/src/test/nic_router_dhcp/README for a documentation.
#
proc nic_router_2_managed { } { return 1 }
source ${genode_dir}/repos/os/run/nic_router_dhcp.inc

View File

@ -0,0 +1,6 @@
#
# See os/src/test/nic_router_dhcp/README for a documentation.
#
proc nic_router_2_managed { } { return 0 }
source ${genode_dir}/repos/os/run/nic_router_dhcp.inc

View File

@ -385,34 +385,61 @@ One can configure the NIC router to act as DHCP server at interfaces of a
domain by adding the <dhcp> tag to the configuration of the domain like
this:
<domain name="vbox" interface="10.0.1.1/24">
<dhcp-server ip_first="10.0.1.80"
ip_last="10.0.1.100"
ip_lease_time_sec="3600"
dns_server="10.0.0.2"
dns_server_from="uplink" />
...
</domain>
! <domain name="vbox" interface="10.0.1.1/24">
!
! <dhcp-server ip_first="10.0.1.80"
! ip_last="10.0.1.100"
! ip_lease_time_sec="3600">
!
! <dns-server ip="8.8.8.8" />
! <dns-server ip="1.1.1.1" />
! ...
!
! </dhcp-server>
! ...
!
! </domain>
The attributes ip_first and ip_last define the available IPv4 address range
while ip_lease_time_sec defines the lifetime of an IPv4 address assignment in
seconds. The IPv4 address range must be in the subnet defined by the interface
attribute of the domain tag and must not cover the IPv4 address in this
attribute. The dns_server attribute gives the IPv4 address of the DNS server
that might also be in another subnet. The dns_server_from attribute has effect
only if the dns_server attribute is not set. If this is the case, the
dns_server_from attribute states the domain from whose IP config to take the
DNS server address. This is useful, for instance, if the stated domain
receives the address of a local DNS server via DHCP. Whenever the IP config
of the stated domain becomes invalid, the DHCP server switches to a mode where
it drops all requests unanswered until the IP config becomes valid again.
or like this:
! <domain name="vbox" interface="10.0.1.1/24">
!
! <dhcp-server ip_first="10.0.1.80"
! ip_last="10.0.1.100"
! ip_lease_time_sec="3600"
! dns_server_from="uplink" />
! ...
!
! </domain>
The mandatory attributes 'ip_first' and 'ip_last' define the available IPv4
address range while the optional attribute 'ip_lease_time_sec' defines the
lifetime of an IPv4 address assignment in seconds. The IPv4 address range must
be in the subnet defined by the 'interface' attribute of the <domain> tag and
must not cover the IPv4 address given by this attribute.
The <dns-server> sub-tags from the first example statically provide a list of
DNS server addresses that shall be propagated by the DHCP server through DHCP
option 6 entries to its clients. These addresses might be of any IP subnet. The
DHCP option 6 entries in the DHCP replies will have the same order as the
<dns-server> tags in the configuration.
The 'dns_server_from' attribute from the second example takes effect only when
the <dhcp-server> tag does not contain any <dns-server> sub-tags. The attribute
states the domain from whose IP config to take the list of propagated DNS
server addresses. Note that the order of DNS server adresses is not altered
thereby. This is useful in scenarios where these addresses must be obtained
dynamically through the DHCP client of another domain. An implication of the
'dns_server_from' attribute is that the link state of all interfaces at the
domain with the DHCP server becomes bound to the validity of the IP config of
the domain that is stated in the attribute.
The lifetime of an assignment that was yet only offered to the client can be
configured for all domains in the <config> tag of the router:
! <config dhcp_offer_timeout_sec="6">
The timeout ip_lease_time_sec is applied only when the offer is acknowledged
The timeout 'ip_lease_time_sec' is applied only when the offer is acknowledged
by the client in time.
@ -454,7 +481,7 @@ Configuration example (shows default values of attributes):
! quota="yes"
! config="yes"
! config_triggers="no"
! interval_sec="5">
! interval_sec="5"/>
! </config>
If the 'report' tag is not available, no reports are send.
@ -560,19 +587,24 @@ two contrary edges at a max. The following diagrams demonstrate this:
Examples
~~~~~~~~
This section will list and explain some interesting configuration snippets. A
comprehensive example of how to use the router (except DHCP server
functionality) can be found in the test script 'libports/run/nic_router.run'.
For an example of how to use the DHCP server and the DHCP client functionality
see the 'os/run/ping_nic_router.run' script.
In-action examples of how to use the router are provided through the following
automated run scripts:
The environment for the examples shall be as
follows. There are two virtual subnets 192.168.1.0/24 and 192.168.2.0/24 that
connect as Virtnet A and B to the router. The standard gateway of the virtual
networks is the NIC router with IP 192.168.*.1 . The router's uplink leads to
the NIC driver that connects the machine with your home network 10.0.2.0/24.
Your home network is connected to the internet through its standard gateway
10.0.2.1 .
* libports/run/nic_router.run (basic functionality)
* dde_linux/run/nic_router_uplinks.run (dynamically switching uplinks)
* os/run/ping_nic_router.run (ICMP routing)
* os/run/nic_router_dhcp_unmanaged.run (DHCP + link states without a manager)
* os/run/nic_router_dhcp_managed.run (DHCP + link states with a manager)
* os/run/nic_router_flood.run (client misbehaving on protocol level)
* os/run/nic_router_stress.run (client misbehaving on session level)
The rest of this section will list and explain some smaller configuration
snippets. The environment for these examples shall be as follows. There are
two virtual subnets 192.168.1.0/24 and 192.168.2.0/24 that connect as Virtnet A
and B to the router. The standard gateway of the virtual networks is the NIC
router with IP 192.168.*.1 . The router's uplink leads to the NIC driver that
connects the machine with your home network 10.0.2.0/24. Your home network is
connected to the internet through its standard gateway 10.0.2.1 .
Connecting local networks

View File

@ -109,10 +109,18 @@
<xs:element name="dhcp-server">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="dns-server">
<xs:complexType>
<xs:attribute name="ip" type="Ipv4_address" />
</xs:complexType>
</xs:element><!-- dns-server -->
</xs:choice>
<xs:attribute name="ip_first" type="Ipv4_address" />
<xs:attribute name="ip_last" type="Ipv4_address" />
<xs:attribute name="ip_lease_time_sec" type="Seconds" />
<xs:attribute name="dns_server" type="Ipv4_address" />
<xs:attribute name="dns_server_from" type="Domain_name" />
</xs:complexType>
</xs:element><!-- dhcp-server -->

View File

@ -0,0 +1,30 @@
/*
* \brief Genode DHCP protocol plus local utilities
* \author Martin Stein
* \date 2020-11-18
*/
/*
* Copyright (C) 2020 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_H_
#define _DHCP_H_
/* Genode includes */
#include <net/dhcp.h>
namespace Net {
template <typename T>
static Ipv4_address dhcp_ipv4_option(Dhcp_packet &dhcp)
{
try { return dhcp.option<T>().value(); }
catch (Dhcp_packet::Option_not_found) { return Ipv4_address { }; }
}
}
#endif /* _DHCP_H_ */

View File

@ -142,20 +142,7 @@ void Dhcp_client::handle_dhcp_reply(Dhcp_packet &dhcp)
}
_lease_time_sec = dhcp.option<Dhcp_packet::Ip_lease_time>().value();
_set_state(State::BOUND, _rerequest_timeout(1));
Ipv4_address dns_server;
Ipv4_address subnet_mask;
Ipv4_address router_ip;
try { dns_server = dhcp.option<Dhcp_packet::Dns_server_ipv4>().value(); }
catch (Dhcp_packet::Option_not_found) { }
try { subnet_mask = dhcp.option<Dhcp_packet::Subnet_mask>().value(); }
catch (Dhcp_packet::Option_not_found) { }
try { router_ip = dhcp.option<Dhcp_packet::Router_ipv4>().value(); }
catch (Net::Dhcp_packet::Option_not_found) { }
_domain().ip_config(dhcp.yiaddr(), subnet_mask, router_ip,
dns_server);
_domain().ip_config_from_dhcp_ack(dhcp);
break;
}
case State::RENEW:

View File

@ -21,11 +21,37 @@ using namespace Net;
using namespace Genode;
/*****************
** Dhcp_server **
*****************/
/**********************
** Dhcp_server_base **
**********************/
void Dhcp_server::_invalid(Domain &domain,
Dhcp_server_base::Dhcp_server_base(Xml_node const &node,
Domain const &domain,
Allocator &alloc)
:
_alloc { alloc }
{
node.for_each_sub_node("dns-server", [&] (Xml_node const &sub_node) {
try {
_dns_servers.insert_as_tail(*new (alloc)
Dns_server(sub_node.attribute_value("ip", Ipv4_address())));
} catch (Dns_server::Invalid) {
_invalid(domain, "invalid DNS server entry");
}
});
}
Dhcp_server_base::~Dhcp_server_base()
{
_dns_servers.destroy_each(_alloc);
}
void Dhcp_server_base::_invalid(Domain const &domain,
char const *reason)
{
if (domain.config().verbose()) {
@ -35,13 +61,17 @@ void Dhcp_server::_invalid(Domain &domain,
}
/*****************
** Dhcp_server **
*****************/
Dhcp_server::Dhcp_server(Xml_node const node,
Domain &domain,
Allocator &alloc,
Ipv4_address_prefix const &interface,
Domain_tree &domains)
:
_dns_server(node.attribute_value("dns_server", Ipv4_address())),
Dhcp_server_base(node, domain, alloc),
_dns_server_from(_init_dns_server_from(node, domains)),
_ip_lease_time (_init_ip_lease_time(node)),
_ip_first(node.attribute_value("ip_first", Ipv4_address())),
@ -75,9 +105,9 @@ Microseconds Dhcp_server::_init_ip_lease_time(Xml_node const node)
void Dhcp_server::print(Output &output) const
{
if (_dns_server.valid()) {
Genode::print(output, "DNS server ", _dns_server, ", ");
}
_dns_servers.for_each([&] (Dns_server const &dns_server) {
Genode::print(output, "DNS server ", dns_server.ip(), ", ");
});
try { Genode::print(output, "DNS server from ", _dns_server_from(), ", "); }
catch (Pointer<Domain>::Invalid) { }
@ -88,6 +118,18 @@ void Dhcp_server::print(Output &output) const
}
bool Dhcp_server::dns_servers_equal_to_those_of(Dhcp_server const &dhcp_server) const
{
return _dns_servers.equal_to(dhcp_server._dns_servers);
}
Ipv4_config const &Dhcp_server::_resolve_dns_server_from() const
{
return _dns_server_from().ip_config();
}
Ipv4_address Dhcp_server::alloc_ip()
{
try {
@ -117,7 +159,7 @@ void Dhcp_server::free_ip(Ipv4_address const &ip)
Pointer<Domain> Dhcp_server::_init_dns_server_from(Genode::Xml_node const node,
Domain_tree &domains)
{
if (_dns_server.valid()) {
if (!_dns_servers.empty()) {
return Pointer<Domain>();
}
Domain_name dns_server_from =
@ -131,17 +173,9 @@ Pointer<Domain> Dhcp_server::_init_dns_server_from(Genode::Xml_node const node,
}
Ipv4_address const &Dhcp_server::dns_server() const
{
try { return _dns_server_from().ip_config().dns_server; }
catch (Pointer<Domain>::Invalid) { }
return _dns_server;
}
bool Dhcp_server::ready() const
{
if (_dns_server.valid()) {
if (!_dns_servers.empty()) {
return true;
}
try { return _dns_server_from().ip_config().valid; }

View File

@ -15,10 +15,11 @@
#define _DHCP_SERVER_H_
/* local includes */
#include <ipv4_address_prefix.h>
#include <bit_allocator_dynamic.h>
#include <list.h>
#include <pointer.h>
#include <dns_server.h>
#include <ipv4_config.h>
/* Genode includes */
#include <net/mac_address.h>
@ -29,6 +30,7 @@
namespace Net {
class Configuration;
class Dhcp_server_base;
class Dhcp_server;
class Dhcp_allocation;
class Dhcp_allocation_tree;
@ -41,11 +43,31 @@ namespace Net {
}
class Net::Dhcp_server : private Genode::Noncopyable
class Net::Dhcp_server_base
{
protected:
Genode::Allocator &_alloc;
Net::List<Dns_server> _dns_servers { };
void _invalid(Domain const &domain,
char const *reason);
public:
Dhcp_server_base(Genode::Xml_node const &node,
Domain const &domain,
Genode::Allocator &alloc);
~Dhcp_server_base();
};
class Net::Dhcp_server : private Genode::Noncopyable,
private Net::Dhcp_server_base
{
private:
Ipv4_address const _dns_server;
Pointer<Domain> const _dns_server_from;
Genode::Microseconds const _ip_lease_time;
Ipv4_address const _ip_first;
@ -54,14 +76,13 @@ class Net::Dhcp_server : private Genode::Noncopyable
Genode::uint32_t const _ip_count;
Genode::Bit_allocator_dynamic _ip_alloc;
void _invalid(Domain &domain,
char const *reason);
Genode::Microseconds _init_ip_lease_time(Genode::Xml_node const node);
Pointer<Domain> _init_dns_server_from(Genode::Xml_node const node,
Domain_tree &domains);
Ipv4_config const &_resolve_dns_server_from() const;
public:
enum { DEFAULT_IP_LEASE_TIME_SEC = 3600 };
@ -83,6 +104,27 @@ class Net::Dhcp_server : private Genode::Noncopyable
bool ready() const;
template <typename FUNC>
void for_each_dns_server_ip(FUNC && functor) const
{
if (_dns_server_from.valid()) {
_resolve_dns_server_from().for_each_dns_server(
[&] (Dns_server const &dns_server) {
functor(dns_server.ip());
});
} else {
_dns_servers.for_each([&] (Dns_server const &dns_server) {
functor(dns_server.ip());
});
}
}
bool
dns_servers_equal_to_those_of(Dhcp_server const &dhcp_server) const;
/*********
** log **
@ -95,7 +137,6 @@ class Net::Dhcp_server : private Genode::Noncopyable
** Accessors **
***************/
Ipv4_address const &dns_server() const;
Domain &dns_server_from() { return _dns_server_from(); }
Genode::Microseconds ip_lease_time() const { return _ip_lease_time; }
};

View File

@ -0,0 +1,34 @@
/*
* \brief DNS server entry of a DHCP server or IPv4 config
* \author Martin Stein
* \date 2020-11-17
*/
/*
* Copyright (C) 2020 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 <dns_server.h>
using namespace Net;
using namespace Genode;
Net::Dns_server::Dns_server(Ipv4_address const &ip)
:
_ip { ip }
{
if (!_ip.valid()) {
throw Invalid { };
}
}
bool Net::Dns_server::equal_to(Dns_server const &server) const
{
return _ip == server._ip;
}

View File

@ -0,0 +1,50 @@
/*
* \brief DNS server entry of a DHCP server or IPv4 config
* \author Martin Stein
* \date 2020-11-17
*/
/*
* Copyright (C) 2020 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 _DNS_SERVER_H_
#define _DNS_SERVER_H_
/* local includes */
#include <list.h>
/* Genode includes */
#include <net/ipv4.h>
namespace Net { class Dns_server; }
class Net::Dns_server : private Genode::Noncopyable,
public Net::List<Dns_server>::Element
{
private:
Net::Ipv4_address const _ip;
public:
struct Invalid : Genode::Exception { };
Dns_server(Net::Ipv4_address const &ip);
bool equal_to(Dns_server const &server) const;
/***************
** Accessors **
***************/
Net::Ipv4_address const &ip() const { return _ip; }
};
#endif /* _DHCP_SERVER_H_ */

View File

@ -47,7 +47,7 @@ void Domain::_log_ip_config() const
if (!ip_config.valid &&
(ip_config.interface_valid ||
ip_config.gateway_valid ||
ip_config.dns_server_valid))
!ip_config.dns_servers.empty()))
{
log("[", *this, "] malformed ", _ip_config_dynamic ? "dynamic" :
"static", "IP config: ", ip_config);
@ -60,7 +60,7 @@ void Domain::_log_ip_config() const
}
void Domain::ip_config(Ipv4_config const &new_ip_config)
void Domain::_prepare_reconstructing_ip_config()
{
if (!_ip_config_dynamic) {
throw Ip_config_static(); }
@ -69,7 +69,7 @@ void Domain::ip_config(Ipv4_config const &new_ip_config)
if (ip_config().valid) {
/* mark IP config invalid */
_ip_config.construct();
_ip_config.construct(_alloc);
/* detach all dependent interfaces from old IP config */
_interfaces.for_each([&] (Interface &interface) {
@ -86,8 +86,11 @@ void Domain::ip_config(Ipv4_config const &new_ip_config)
waiter.src().cancel_arp_waiting(waiter);
}
}
/* overwrite old with new IP config */
_ip_config.construct(new_ip_config);
}
void Domain::_finish_reconstructing_ip_config()
{
_log_ip_config();
/* attach all dependent interfaces to new IP config if it is valid */
@ -113,20 +116,15 @@ void Domain::ip_config(Ipv4_config const &new_ip_config)
void Domain::discard_ip_config()
{
/* install invalid IP config */
Ipv4_config const new_ip_config;
ip_config(new_ip_config);
_reconstruct_ip_config([&] (Reconstructible<Ipv4_config> &ip_config) {
ip_config.construct(_alloc); });
}
void Domain::ip_config(Ipv4_address ip,
Ipv4_address subnet_mask,
Ipv4_address gateway,
Ipv4_address dns_server)
void Domain::ip_config_from_dhcp_ack(Dhcp_packet &dhcp_ack)
{
Ipv4_config const new_ip_config(Ipv4_address_prefix(ip, subnet_mask),
gateway, dns_server);
ip_config(new_ip_config);
_reconstruct_ip_config([&] (Reconstructible<Ipv4_config> &ip_config) {
ip_config.construct(dhcp_ack, _alloc); });
}
@ -139,7 +137,8 @@ void Domain::try_reuse_ip_config(Domain const &domain)
{
return;
}
ip_config(domain.ip_config());
_reconstruct_ip_config([&] (Reconstructible<Ipv4_config> &ip_config) {
ip_config.construct(domain.ip_config(), _alloc); });
}
@ -198,18 +197,20 @@ void Domain::print(Output &output) const
Domain::Domain(Configuration &config, Xml_node const node, Allocator &alloc)
:
Domain_base(node), Avl_string_base(_name.string()), _config(config),
_node(node), _alloc(alloc),
_ip_config(_node.attribute_value("interface", Ipv4_address_prefix()),
_node.attribute_value("gateway", Ipv4_address()),
Ipv4_address()),
_verbose_packets(_node.attribute_value("verbose_packets",
_config.verbose_packets())),
_verbose_packet_drop(_node.attribute_value("verbose_packet_drop",
_config.verbose_packet_drop())),
_icmp_echo_server(_node.attribute_value("icmp_echo_server",
_config.icmp_echo_server())),
_label(_node.attribute_value("label", String<160>()).string())
Domain_base { node },
Avl_string_base { Domain_base::_name.string() },
_config { config },
_node { node },
_alloc { alloc },
_ip_config { node, alloc },
_verbose_packets { node.attribute_value("verbose_packets",
config.verbose_packets()) },
_verbose_packet_drop { node.attribute_value("verbose_packet_drop",
config.verbose_packet_drop()) },
_icmp_echo_server { node.attribute_value("icmp_echo_server",
config.icmp_echo_server()) },
_label { node.attribute_value("label",
String<160>()).string() }
{
_log_ip_config();
@ -375,7 +376,11 @@ void Domain::report(Xml_generator &xml)
if (_config.report().config()) {
xml.attribute("ipv4", String<19>(ip_config().interface));
xml.attribute("gw", String<16>(ip_config().gateway));
xml.attribute("dns", String<16>(ip_config().dns_server));
ip_config().for_each_dns_server([&] (Dns_server const &dns_server) {
xml.node("dns", [&] () {
xml.attribute("ip", String<16>(dns_server.ip()));
});
});
empty = false;
}
if (_config.report().stats()) {

View File

@ -142,6 +142,18 @@ class Net::Domain : public Domain_base,
void _log_ip_config() const;
void _prepare_reconstructing_ip_config();
void _finish_reconstructing_ip_config();
template <typename FUNC>
void _reconstruct_ip_config(FUNC && functor)
{
_prepare_reconstructing_ip_config();
functor(_ip_config);
_finish_reconstructing_ip_config();
}
void __FIXME__dissolve_foreign_arp_waiters();
public:
@ -162,12 +174,7 @@ class Net::Domain : public Domain_base,
Ipv4_address const &next_hop(Ipv4_address const &ip) const;
void ip_config(Ipv4_config const &ip_config);
void ip_config(Ipv4_address ip,
Ipv4_address subnet_mask,
Ipv4_address gateway,
Ipv4_address dns_server);
void ip_config_from_dhcp_ack(Dhcp_packet &dhcp_ack);
void discard_ip_config();

View File

@ -34,6 +34,7 @@ using Genode::Exception;
using Genode::Out_of_ram;
using Genode::Out_of_caps;
using Genode::Constructible;
using Genode::Reconstructible;
using Genode::Signal_context_capability;
using Genode::Signal_transmitter;
@ -644,8 +645,9 @@ void Interface::_send_dhcp_reply(Dhcp_server const &dhcp_srv,
dhcp_opts.append_option<Dhcp_packet::Ip_lease_time>(dhcp_srv.ip_lease_time().value / 1000 / 1000);
dhcp_opts.append_option<Dhcp_packet::Subnet_mask>(local_intf.subnet_mask());
dhcp_opts.append_option<Dhcp_packet::Router_ipv4>(local_intf.address);
if (dhcp_srv.dns_server().valid()) {
dhcp_opts.append_option<Dhcp_packet::Dns_server_ipv4>(dhcp_srv.dns_server()); }
dhcp_srv.for_each_dns_server_ip([&] (Ipv4_address const &dns_server_ip) {
dhcp_opts.append_option<Dhcp_packet::Dns_server_ipv4>(dns_server_ip);
});
dhcp_opts.append_option<Dhcp_packet::Broadcast_addr>(local_intf.broadcast_address());
dhcp_opts.append_option<Dhcp_packet::Options_end>();
@ -1865,7 +1867,7 @@ void Interface::_update_dhcp_allocations(Domain &old_domain,
try {
Dhcp_server &old_dhcp_srv = old_domain.dhcp_server();
Dhcp_server &new_dhcp_srv = new_domain.dhcp_server();
if (old_dhcp_srv.dns_server() != new_dhcp_srv.dns_server()) {
if (!old_dhcp_srv.dns_servers_equal_to_those_of(new_dhcp_srv)) {
throw Pointer<Dhcp_server>::Invalid();
}
if (old_dhcp_srv.ip_lease_time().value !=

View File

@ -20,19 +20,79 @@
using namespace Genode;
using namespace Net;
Ipv4_config::Ipv4_config(Ipv4_address_prefix interface,
Ipv4_address gateway,
Ipv4_address dns_server)
Ipv4_config::Ipv4_config(Allocator &alloc)
:
interface(interface), gateway(gateway), dns_server(dns_server)
alloc { alloc },
interface { },
gateway { }
{ }
Ipv4_config::Ipv4_config(Xml_node const &domain_node,
Allocator &alloc)
:
alloc { alloc },
interface { domain_node.attribute_value("interface", Ipv4_address_prefix()) },
gateway { domain_node.attribute_value("gateway", Ipv4_address()) }
{ }
Ipv4_config::Ipv4_config(Ipv4_config const &ip_config,
Allocator &alloc)
:
alloc { alloc },
interface { ip_config.interface },
gateway { ip_config.gateway }
{
ip_config.dns_servers.for_each([&] (Dns_server const &dns_server) {
dns_servers.insert_as_tail(
*new (alloc) Dns_server(dns_server.ip()));
});
}
Ipv4_config::Ipv4_config(Dhcp_packet &dhcp_ack,
Allocator &alloc)
:
alloc { alloc },
interface { dhcp_ack.yiaddr(),
dhcp_ipv4_option<Dhcp_packet::Subnet_mask>(dhcp_ack) },
gateway { dhcp_ipv4_option<Dhcp_packet::Router_ipv4>(dhcp_ack) }
{
dhcp_ack.for_each_option([&] (Dhcp_packet::Option const &opt)
{
if (opt.code() != Dhcp_packet::Option::Code::DNS_SERVER) {
return;
}
try {
dns_servers.insert_as_tail(*new (alloc)
Dns_server(
reinterpret_cast<Dhcp_packet::Dns_server_ipv4 const *>(&opt)->value()));
}
catch (Dns_server::Invalid) { }
});
}
Ipv4_config::~Ipv4_config()
{
dns_servers.destroy_each(alloc);
}
void Ipv4_config::print(Output &output) const
{
if (valid) {
Genode::print(output, "interface ", interface, ", gateway ", gateway,
", DNS server ", dns_server, " P2P ", point_to_point); }
else {
Genode::print(output, "none"); }
" P2P ", point_to_point);
for_each_dns_server([&] (Dns_server const &dns_server) {
Genode::print(output, ", DNS server ", dns_server.ip()); });
} else {
Genode::print(output, "none");
}
}

View File

@ -16,36 +16,56 @@
/* local includes */
#include <ipv4_address_prefix.h>
#include <dhcp.h>
#include <dns_server.h>
/* Genode includes */
#include <util/xml_node.h>
namespace Net { class Ipv4_config; }
struct Net::Ipv4_config
{
Ipv4_address_prefix const interface { };
Genode::Allocator &alloc;
Ipv4_address_prefix const interface;
bool const interface_valid { interface.valid() };
Ipv4_address const gateway { };
Ipv4_address const gateway;
bool const gateway_valid { gateway.valid() };
bool const point_to_point { gateway_valid &&
interface_valid &&
interface.prefix == 32 };
Ipv4_address const dns_server { };
bool const dns_server_valid { dns_server.valid() };
Net::List<Dns_server> dns_servers { };
bool const valid { point_to_point ||
(interface_valid &&
(!gateway_valid ||
interface.prefix_matches(gateway))) };
Ipv4_config(Ipv4_address_prefix interface,
Ipv4_address gateway,
Ipv4_address dns_server);
Ipv4_config(Net::Dhcp_packet &dhcp_ack,
Genode::Allocator &alloc);
Ipv4_config() { }
Ipv4_config(Genode::Xml_node const &domain_node,
Genode::Allocator &alloc);
Ipv4_config(Ipv4_config const &ip_config,
Genode::Allocator &alloc);
Ipv4_config(Genode::Allocator &alloc);
~Ipv4_config();
bool operator != (Ipv4_config const &other) const
{
return interface != other.interface ||
gateway != other.gateway ||
dns_server != other.dns_server;
!dns_servers.equal_to(other.dns_servers);
}
template <typename FUNC>
void for_each_dns_server(FUNC && functor) const
{
dns_servers.for_each([&] (Dns_server const &dns_server) {
functor(dns_server);
});
}

View File

@ -37,6 +37,17 @@ struct Net::List : Genode::List<LT>
}
}
template <typename FUNC>
void for_each(FUNC && functor) const
{
for (LT const *elem = Base::first(); elem; )
{
LT const *const next = elem->Base::Element::next();
functor(*elem);
elem = next;
}
}
void destroy_each(Genode::Deallocator &dealloc)
{
while (LT *elem = Base::first()) {
@ -44,6 +55,48 @@ struct Net::List : Genode::List<LT>
destroy(dealloc, elem);
}
}
bool empty() const
{
return Base::first() == nullptr;
}
void insert_as_tail(LT const &le)
{
LT *elem { Base::first() };
if (elem) {
while (elem->Base::Element::next()) {
elem = elem->Base::Element::next();
}
}
Base::insert(&le, elem);
}
bool equal_to(List<LT> const &list) const
{
LT const *curr_elem_1 { Base::first() };
LT const *curr_elem_2 { list.Base::first() };
while (true) {
if (curr_elem_1 == nullptr) {
return curr_elem_2 == nullptr;
}
if (curr_elem_2 == nullptr) {
return false;
}
LT const *const next_elem_1 {
curr_elem_1->List<LT>::Element::next() };
LT const *const next_elem_2 {
curr_elem_2->List<LT>::Element::next() };
if (!curr_elem_1->equal_to(*curr_elem_2)) {
return false;
}
curr_elem_1 = next_elem_1;
curr_elem_2 = next_elem_2;
}
}
};
#endif /* _LIST_H_ */

View File

@ -7,7 +7,7 @@ SRC_CC += component.cc port_allocator.cc forward_rule.cc
SRC_CC += nat_rule.cc main.cc ipv4_config.cc
SRC_CC += uplink.cc interface.cc arp_cache.cc configuration.cc
SRC_CC += domain.cc l3_protocol.cc direct_rule.cc link.cc
SRC_CC += transport_rule.cc permit_rule.cc
SRC_CC += transport_rule.cc permit_rule.cc dns_server.cc
SRC_CC += dhcp_client.cc dhcp_server.cc report.cc xml_node.cc
INC_DIR += $(PRG_DIR)

View File

@ -0,0 +1,153 @@
====================
NIC-router-DHCP test
====================
Martin Stein
The NIC-router-DHCP test is meant to test and demonstrate the functionalities
of the NIC router that correspond to the management of networks through DHCP.
This comprises the abilities of the router to basically act as DHCP client and
DHCP server, to recognize and propagate additional information through DHCP,
and to react to changes in DHCP environments.
Unmanaged test scenario
#######################
The test is integrated by the run scripts
'os/run/nic_router_dhcp_unmanaged.run' and 'os/run/nic_router_managed.run'.
The "unmanaged" variant creates the following setup:
+--------------------------------------+ Received
| Test Client | DHCP info
| (DHCP Client 2) |----------------> Serial Output
+--------------------------------------+
|
| NIC session
|
v
+---------------+-------------------+--+
| NIC Router 2 | Domain Downlink 2 | |
| | (DHCP Server 2) | |
| +-------------------+ |
| ^ |
| | Built-in |
| | DHCP info |
| | forwarding |
| | |
| | |
| +-------------------+ |
| | Domain Uplink | |
| | (DHCP Client 1) | |
+---------------+-------------------+--+
|
| NIC session
|
v
+---------------+-------------------+--+ Reconfigure
| NIC Router 1 | Domain Downlink 1 | | DHCP server +--------------+
| | (DHCP Server 1) | |<----------------| Dynamic ROM |
| +-------------------+ | +--------------+
| |
+--------------------------------------+
Throughout the test, the dynamic ROM changes the setup of DHCP Server 1
multiple times. This results also in changes of the additional information
propagated via DHCP. As a reaction to the changes, the link state of domain
Downlink 1 does a "down-up" sequence in order to advise all connected DHCP
clients to reset. DHCP Client 1 does so and receives the updated information
for its domain Uplink.
But the domain Uplink is also watched by Downlink 2 for additional DHCP
information through the router-internal forwarding mechanism (currently only
DNS server addresses, see attribute 'dns_server_from'). Therefore, the reset of
DHCP Client 1 also causes the link state of Downlink 2 to go "down" until DHCP
Client 1 finishes re-requesting DHCP. This causes DHCP Client 2 in the test
client (os/src/test/nic_router_dhcp/manager) to reset and re-request both the
basic DHCP info (originating from DHCP Server 2) and the updated additional
DHCP info (originating from DHCP Server 1) as soon as Downlink 2 is "up" again.
The test terminates successfully when the test client has printed a certain
sequence of successively received DHCP setups to the serial output. It fails,
at the other hand, when the expected output wasn't observed for a certain time.
Managed test scenario
#####################
The "managed" variant of test differs only in one detail from the "unmanaged"
variant: It doesn't use the router-internal mechanism for forwarding additional
DHCP information from the domain Uplink to domain Downlink 2. Instead, it
achieves the forwarding through an additional manager component
(os/src/test/nic_router_dhcp/manager):
+--------------------------------------+ Received
| Test Client | DHCP info
| (DHCP Client 2) |----------------> Serial Output
+--------------------------------------+
|
| NIC session
|
v Reconfigure
+---------------+-------------------+--+ DHCP server +--------------+
| NIC Router 2 | Domain Downlink 2 | | +---+ | Test Manager |
| | (DHCP Server 2) | |<-----| R |------| |
| +-------------------+ | | e | | |
| | | p | | |
| | | o | | |
| | | r | | |
| | | t | | |
| | | | | |
| | | R | | |
| +-------------------+ | | O | | |
| | Domain Uplink | |------| M |----->| |
| | (DHCP Client 1) | | +---+ | |
+---------------+-------------------+--+ Observe DHCP +--------------+
| client state
| NIC session
|
v
+---------------+-------------------+--+ Reconfigure
| NIC Router 1 | Domain Downlink 1 | | DHCP server +--------------+
| | (DHCP Server 1) | |<----------------| Dynamic ROM |
| +-------------------+ | +--------------+
| |
+--------------------------------------+
The manager initially writes out a router configuration using a Report session.
The configuration is received by NIC Router 2 through a ROM session. The
mediator between the managers Report and the routers ROM session is a Report
ROM server. The initial router configuration written by the manager is
basically the same as the static NIC-Router-2 configuration in the "unmanaged"
scenario with the small addition that it causes the router to report its state.
Now, each time that the DCHP info of domain Uplink changes, NIC Router 2
generates a new state report reflecting the updated DHCP info. The manager
receives the state update through its ROM session with the Report ROM server.
Now, the manager checks whether the additional DHCP info of domain Uplink
was affected by the update. If so, it generates a new router configuration
injecting the new additional DHCP info into DHCP Server 2.
Note that the test manager takes care not to create endless feedback-response
loops by re-configuring the router only when the interesting part of the router
state changes (the additional DHCP info of domain Uplink). The test manager
reduces re-configuration even further by doing it only when the DHCP info at
domain Uplink became valid. This means that it skips the short periods where
DHCP Client 1 is waiting for the re-request to finish and no DHCP info is
available. It's fine for DHCP Server 2 to stay with the outdated information
during this time as no basic DHCP information is affected and routing via
Uplink remains blocked until DHCP finished anyway.
The rest of the process remains the same as in the "unmanaged" variant. The
"managed" approach has the benefit that the forwarding of additional DHCP info
can be adapted to scenarios with special requirements. For instance, one might
want to filter information in order to restrict or protect the client behind
the router.

View File

@ -0,0 +1,281 @@
/*
* \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));
log("DHCP request completed:");
log(" IP lease time: ", _lease_time_sec, " seconds");
log(" Interface: ", Ipv4_address_prefix(dhcp.yiaddr(), dhcp.option<Dhcp_packet::Subnet_mask>().value()));
log(" Router: ", dhcp.option<Dhcp_packet::Router_ipv4>().value());
Ipv4_address dns_server { };
unsigned idx { 1 };
dhcp.for_each_option([&] (Dhcp_packet::Option const &opt)
{
if (!dns_server.valid()) {
dns_server = reinterpret_cast<Dhcp_packet::Dns_server_ipv4 const *>(&opt)->value();
}
if (opt.code() != Dhcp_packet::Option::Code::DNS_SERVER) {
return;
}
log(" DNS server #", idx++, ": ", reinterpret_cast<Dhcp_packet::Dns_server_ipv4 const *>(&opt)->value());
});
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;
static inline Genode::size_t ascii_to(char const *, Net::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;
}
};
Genode::size_t Net::ascii_to(char const *s, Ipv4_address_prefix &result)
{
using namespace Genode;
/* 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,119 @@
/*
* \brief Test the DHCP functionality of the NIC router
* \author Martin Stein
* \date 2020-11-23
*/
/*
* Copyright (C) 2020 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>
/* Genode includes */
#include <net/ethernet.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <timer_session/connection.h>
using namespace Net;
using namespace Genode;
class Main : public Nic_handler,
public Dhcp_client_handler
{
private:
Env &_env;
Attached_rom_dataspace _config_rom { _env, "config" };
Xml_node _config { _config_rom.xml() };
Timer::Connection _timer { _env };
Heap _heap { &_env.ram(), &_env.rm() };
bool const _verbose { _config.attribute_value("verbose", false) };
Net::Nic _nic { _env, _heap, *this, _verbose };
Constructible<Dhcp_client> _dhcp_client { };
bool _link_state { false };
Reconstructible<Ipv4_config> _ip_config { };
public:
struct Invalid_arguments : Exception { };
Main(Env &env);
/*****************
** Nic_handler **
*****************/
void handle_eth(Ethernet_frame &eth,
Size_guard &size_guard) override;
void handle_link_state(bool link_state) override
{
if (!_link_state && link_state) {
_dhcp_client.construct(_heap, _timer, _nic, *this);
}
if (_link_state && !link_state && ip_config().valid) {
ip_config(Ipv4_config { });
}
_link_state = link_state;
};
/*************************
** 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);
}
Main::Main(Env &env) : _env(env)
{
log("Initialized");
_nic.handle_link_state();
}
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); }
else {
throw Drop_packet_inform("IP config still valid");
}
}
catch (Drop_packet_inform exception) {
if (_verbose) {
log("drop packet: ", exception.msg); }
}
}
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,137 @@
/*
* \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 void handle_link_state(bool link_state) = 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 };
Signal_handler _link_state_handler { _env.ep(), *this, &Nic::handle_link_state };
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)
{
_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);
_nic.link_state_sigh(_link_state_handler);
}
void handle_link_state()
{
_handler.handle_link_state(_nic.link_state());
}
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,8 @@
TARGET = test-nic_router_dhcp-client
LIBS += base net
SRC_CC += main.cc dhcp_client.cc ipv4_address_prefix.cc
SRC_CC += nic.cc ipv4_config.cc
INC_DIR += $(PRG_DIR)

View File

@ -0,0 +1,34 @@
/*
* \brief DNS server entry of a DHCP server or IPv4 config
* \author Martin Stein
* \date 2020-11-17
*/
/*
* Copyright (C) 2020 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 <dns_server.h>
using namespace Net;
using namespace Genode;
Local::Dns_server::Dns_server(Ipv4_address const &ip)
:
_ip { ip }
{
if (!_ip.valid()) {
throw Invalid { };
}
}
bool Local::Dns_server::equal_to(Dns_server const &server) const
{
return _ip == server._ip;
}

View File

@ -0,0 +1,50 @@
/*
* \brief DNS server entry of a DHCP server or IPv4 config
* \author Martin Stein
* \date 2020-11-17
*/
/*
* Copyright (C) 2020 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 _DNS_SERVER_H_
#define _DNS_SERVER_H_
/* local includes */
#include <list.h>
/* Genode includes */
#include <net/ipv4.h>
namespace Local { class Dns_server; }
class Local::Dns_server : private Genode::Noncopyable,
public Local::List<Dns_server>::Element
{
private:
Net::Ipv4_address const _ip;
public:
struct Invalid : Genode::Exception { };
Dns_server(Net::Ipv4_address const &ip);
bool equal_to(Dns_server const &server) const;
/***************
** Accessors **
***************/
Net::Ipv4_address const &ip() const { return _ip; }
};
#endif /* _DHCP_SERVER_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;
static inline Genode::size_t ascii_to(char const *, Net::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;
}
};
Genode::size_t Net::ascii_to(char const *s, Ipv4_address_prefix &result)
{
using namespace Genode;
/* 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,102 @@
/*
* \brief Genode list with additional functions needed by NIC router
* \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 _LIST_H_
#define _LIST_H_
/* Genode includes */
#include <util/list.h>
#include <base/allocator.h>
namespace Local { template <typename> class List; }
template <typename LT>
struct Local::List : Genode::List<LT>
{
using Base = Genode::List<LT>;
template <typename FUNC>
void for_each(FUNC && functor)
{
for (LT *elem = Base::first(); elem; )
{
LT *const next = elem->Base::Element::next();
functor(*elem);
elem = next;
}
}
template <typename FUNC>
void for_each(FUNC && functor) const
{
for (LT const *elem = Base::first(); elem; )
{
LT const *const next = elem->Base::Element::next();
functor(*elem);
elem = next;
}
}
void destroy_each(Genode::Deallocator &dealloc)
{
while (LT *elem = Base::first()) {
Base::remove(elem);
destroy(dealloc, elem);
}
}
bool empty() const
{
return Base::first() == nullptr;
}
void insert_as_tail(LT const &le)
{
LT *elem { Base::first() };
if (elem) {
while (elem->Base::Element::next()) {
elem = elem->Base::Element::next();
}
}
Base::insert(&le, elem);
}
bool equal_to(List<LT> const &list) const
{
LT const *curr_elem_1 { Base::first() };
LT const *curr_elem_2 { list.Base::first() };
while (true) {
if (curr_elem_1 == nullptr) {
return curr_elem_2 == nullptr;
}
if (curr_elem_2 == nullptr) {
return false;
}
LT const *const next_elem_1 {
curr_elem_1->List<LT>::Element::next() };
LT const *const next_elem_2 {
curr_elem_2->List<LT>::Element::next() };
if (!curr_elem_1->equal_to(*curr_elem_2)) {
return false;
}
curr_elem_1 = next_elem_1;
curr_elem_2 = next_elem_2;
}
}
};
#endif /* _LIST_H_ */

View File

@ -0,0 +1,175 @@
/*
* \brief Server component for Network Address Translation on NIC sessions
* \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.
*/
/* Genode */
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <util/list.h>
#include <os/reporter.h>
/* local includes */
#include <ipv4_address_prefix.h>
#include <dns_server.h>
using namespace Net;
using namespace Genode;
using Domain_name = String<160>;
namespace Local { class Main; }
class Local::Main
{
private:
Env &_env;
Heap _heap { &_env.ram(), &_env.rm() };
Attached_rom_dataspace _router_state_rom { _env, "router_state" };
Signal_handler<Main> _router_state_handler { _env.ep(), *this, &Main::_handle_router_state };
Expanding_reporter _router_config_reporter { _env, "config", "router_config" };
bool _router_config_outdated { true };
Local::List<Dns_server> _dns_servers { };
void _handle_router_state();
public:
Main(Env &env);
};
Local::Main::Main(Env &env) : _env(env)
{
log("Initialized");
_router_state_rom.sigh(_router_state_handler);
_handle_router_state();
}
void Local::Main::_handle_router_state()
{
/* Request the moste recent content of the router state dataspace. */
log("Read state of nic_router_2");
_router_state_rom.update();
/*
* Search for the uplink domain tag in the updated router state,
* read the new list of DNS servers from and compare it to the old list to
* see whether we have to re-configure the router.
*/
bool domain_found { false };
_router_state_rom.xml().for_each_sub_node(
"domain",
[&] (Xml_node const &domain_node)
{
/*
* If we already found the uplink domain, refrain from inspecting
* further domain tags.
*/
if (domain_found) {
return;
}
if (domain_node.attribute_value("name", Domain_name()) == "uplink") {
domain_found = true;
/*
* Consider re-configuring the router only when the uplink has
* a valid IPv4 config. This prevents us from propagating each,
* normally short-living "No-DNS-Server"-state that merely comes
* from the fact that the uplink has to redo DHCP and invalidates
* its Ipv4 config for this time.
*/
if (!domain_node.attribute_value("ipv4", Ipv4_address_prefix { }).valid()) {
return;
}
/*
* Read out all DNS servers from the new uplink state
* and memorize them in a function-local list.
*/
Local::List<Dns_server> dns_servers { };
domain_node.for_each_sub_node(
"dns",
[&] (Xml_node const &dns_node)
{
dns_servers.insert_as_tail(
*new (_heap) Dns_server(dns_node.attribute_value("ip", Ipv4_address { })));
});
/*
* If the new list of DNS servers differs our member list,
* update the member list, and remember to write out a new router
* configuration.
*/
if (!_dns_servers.equal_to(dns_servers)) {
_dns_servers.destroy_each(_heap);
dns_servers.for_each([&] (Dns_server const &dns_server) {
_dns_servers.insert_as_tail(
*new (_heap) Dns_server(dns_server.ip()));
});
_router_config_outdated = true;
}
dns_servers.destroy_each(_heap);
}
});
/*
* Write out a new router configuration with the updated list of
* DNS servers if necessary.
*/
if (_router_config_outdated) {
log("Write config of nic_router_2");
_router_config_reporter.generate([&] (Xml_generator &xml) {
xml.node("report", [&] () {
xml.attribute("bytes", "no");
xml.attribute("stats", "no");
xml.attribute("quota", "no");
xml.attribute("config", "yes");
xml.attribute("config_triggers", "yes");
xml.attribute("interval_sec", "100");
});
xml.node("policy", [&] () {
xml.attribute("label", "test_client -> ");
xml.attribute("domain", "downlink");
});
xml.node("uplink", [&] () {
xml.attribute("domain", "uplink");
});
xml.node("domain", [&] () {
xml.attribute("name", "uplink");
});
xml.node("domain", [&] () {
xml.attribute("name", "downlink");
xml.attribute("interface", "10.0.3.1/24");
xml.node("dhcp-server", [&] () {
xml.attribute("ip_first", "10.0.3.2");
xml.attribute("ip_last", "10.0.3.2");
_dns_servers.for_each([&] (Dns_server const &dns_server) {
xml.node("dns-server", [&] () {
xml.attribute("ip", String<16>(dns_server.ip()));
});
});
});
});
});
_router_config_outdated = false;
}
}
void Component::construct(Env &env) { static Local::Main main(env); }

View File

@ -0,0 +1,7 @@
TARGET = test-nic_router_dhcp-manager
LIBS += base
SRC_CC += main.cc ipv4_address_prefix.cc dns_server.cc
INC_DIR += $(PRG_DIR)

View File

@ -41,6 +41,8 @@ nic_bridge
nic_bridge_stress
nic_dump
nic_router
nic_router_dhcp_managed
nic_router_dhcp_unmanaged
nic_router_flood
nic_router_stress
nic_router_uplinks