From bad8caee3f7ed25218d4e4205ee278dae1d01e0f Mon Sep 17 00:00:00 2001 From: Martin Stein Date: Mon, 16 Nov 2020 15:43:40 +0100 Subject: [PATCH] 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 --- repos/libports/run/nic_router.run | 7 +- repos/libports/run/nic_router_dyn_config.run | 5 +- repos/os/run/nic_router_dhcp.inc | 282 ++++++++++++++++++ repos/os/run/nic_router_dhcp_managed.run | 6 + repos/os/run/nic_router_dhcp_unmanaged.run | 6 + repos/os/src/server/nic_router/README | 100 ++++--- repos/os/src/server/nic_router/config.xsd | 10 +- repos/os/src/server/nic_router/dhcp.h | 30 ++ repos/os/src/server/nic_router/dhcp_client.cc | 15 +- repos/os/src/server/nic_router/dhcp_server.cc | 72 +++-- repos/os/src/server/nic_router/dhcp_server.h | 55 +++- repos/os/src/server/nic_router/dns_server.cc | 34 +++ repos/os/src/server/nic_router/dns_server.h | 50 ++++ repos/os/src/server/nic_router/domain.cc | 63 ++-- repos/os/src/server/nic_router/domain.h | 19 +- repos/os/src/server/nic_router/interface.cc | 8 +- repos/os/src/server/nic_router/ipv4_config.cc | 74 ++++- repos/os/src/server/nic_router/ipv4_config.h | 56 ++-- repos/os/src/server/nic_router/list.h | 53 ++++ repos/os/src/server/nic_router/target.mk | 2 +- repos/os/src/test/nic_router_dhcp/README | 153 ++++++++++ .../nic_router_dhcp/client/dhcp_client.cc | 281 +++++++++++++++++ .../test/nic_router_dhcp/client/dhcp_client.h | 105 +++++++ .../client/ipv4_address_prefix.cc | 103 +++++++ .../client/ipv4_address_prefix.h | 82 +++++ .../nic_router_dhcp/client/ipv4_config.cc | 43 +++ .../test/nic_router_dhcp/client/ipv4_config.h | 49 +++ .../src/test/nic_router_dhcp/client/main.cc | 119 ++++++++ .../os/src/test/nic_router_dhcp/client/nic.cc | 46 +++ .../os/src/test/nic_router_dhcp/client/nic.h | 137 +++++++++ .../src/test/nic_router_dhcp/client/target.mk | 8 + .../nic_router_dhcp/manager/dns_server.cc | 34 +++ .../test/nic_router_dhcp/manager/dns_server.h | 50 ++++ .../manager/ipv4_address_prefix.cc | 103 +++++++ .../manager/ipv4_address_prefix.h | 82 +++++ .../src/test/nic_router_dhcp/manager/list.h | 102 +++++++ .../src/test/nic_router_dhcp/manager/main.cc | 175 +++++++++++ .../test/nic_router_dhcp/manager/target.mk | 7 + tool/autopilot.list | 2 + 39 files changed, 2485 insertions(+), 143 deletions(-) create mode 100644 repos/os/run/nic_router_dhcp.inc create mode 100644 repos/os/run/nic_router_dhcp_managed.run create mode 100644 repos/os/run/nic_router_dhcp_unmanaged.run create mode 100644 repos/os/src/server/nic_router/dhcp.h create mode 100644 repos/os/src/server/nic_router/dns_server.cc create mode 100644 repos/os/src/server/nic_router/dns_server.h create mode 100644 repos/os/src/test/nic_router_dhcp/README create mode 100644 repos/os/src/test/nic_router_dhcp/client/dhcp_client.cc create mode 100644 repos/os/src/test/nic_router_dhcp/client/dhcp_client.h create mode 100644 repos/os/src/test/nic_router_dhcp/client/ipv4_address_prefix.cc create mode 100644 repos/os/src/test/nic_router_dhcp/client/ipv4_address_prefix.h create mode 100644 repos/os/src/test/nic_router_dhcp/client/ipv4_config.cc create mode 100644 repos/os/src/test/nic_router_dhcp/client/ipv4_config.h create mode 100644 repos/os/src/test/nic_router_dhcp/client/main.cc create mode 100644 repos/os/src/test/nic_router_dhcp/client/nic.cc create mode 100644 repos/os/src/test/nic_router_dhcp/client/nic.h create mode 100644 repos/os/src/test/nic_router_dhcp/client/target.mk create mode 100644 repos/os/src/test/nic_router_dhcp/manager/dns_server.cc create mode 100644 repos/os/src/test/nic_router_dhcp/manager/dns_server.h create mode 100644 repos/os/src/test/nic_router_dhcp/manager/ipv4_address_prefix.cc create mode 100644 repos/os/src/test/nic_router_dhcp/manager/ipv4_address_prefix.h create mode 100644 repos/os/src/test/nic_router_dhcp/manager/list.h create mode 100644 repos/os/src/test/nic_router_dhcp/manager/main.cc create mode 100644 repos/os/src/test/nic_router_dhcp/manager/target.mk diff --git a/repos/libports/run/nic_router.run b/repos/libports/run/nic_router.run index 4932f5a082..1c64259c4c 100644 --- a/repos/libports/run/nic_router.run +++ b/repos/libports/run/nic_router.run @@ -228,8 +228,11 @@ proc test_7_router_config { } { + ip_lease_time_sec="3600"> + + + + diff --git a/repos/libports/run/nic_router_dyn_config.run b/repos/libports/run/nic_router_dyn_config.run index 3de270d64a..44d027279e 100644 --- a/repos/libports/run/nic_router_dyn_config.run +++ b/repos/libports/run/nic_router_dyn_config.run @@ -159,8 +159,9 @@ proc test_7_router_config { } { + ip_lease_time_sec="3600"> + + diff --git a/repos/os/run/nic_router_dhcp.inc b/repos/os/run/nic_router_dhcp.inc new file mode 100644 index 0000000000..e24786dbac --- /dev/null +++ b/repos/os/run/nic_router_dhcp.inc @@ -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 { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + +append_if [expr ![nic_router_2_managed]] config { + + + + + + + + + + + + + + } + +append config { + + + } + +append_if [nic_router_2_managed] config { + + + + } + +append config { + + + + + + + + + + + + + + } + +append_if [nic_router_2_managed] config { + + + + + + + + + + + + + + + + + + + + + + } + +append 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 diff --git a/repos/os/run/nic_router_dhcp_managed.run b/repos/os/run/nic_router_dhcp_managed.run new file mode 100644 index 0000000000..80f6a2f998 --- /dev/null +++ b/repos/os/run/nic_router_dhcp_managed.run @@ -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 diff --git a/repos/os/run/nic_router_dhcp_unmanaged.run b/repos/os/run/nic_router_dhcp_unmanaged.run new file mode 100644 index 0000000000..ec5476446c --- /dev/null +++ b/repos/os/run/nic_router_dhcp_unmanaged.run @@ -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 diff --git a/repos/os/src/server/nic_router/README b/repos/os/src/server/nic_router/README index 1e83c8179c..9a78b6e88c 100644 --- a/repos/os/src/server/nic_router/README +++ b/repos/os/src/server/nic_router/README @@ -385,34 +385,61 @@ One can configure the NIC router to act as DHCP server at interfaces of a domain by adding the tag to the configuration of the domain like this: - - - ... - +! +! +! +! +! +! +! ... +! +! +! ... +! +! -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: + +! +! +! +! ... +! +! + +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 tag and +must not cover the IPv4 address given by this attribute. + +The 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 + tags in the configuration. + +The 'dns_server_from' attribute from the second example takes effect only when +the tag does not contain any 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 tag of the router: ! -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"/> ! 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 diff --git a/repos/os/src/server/nic_router/config.xsd b/repos/os/src/server/nic_router/config.xsd index 70c72df643..c9773e028a 100644 --- a/repos/os/src/server/nic_router/config.xsd +++ b/repos/os/src/server/nic_router/config.xsd @@ -109,10 +109,18 @@ + + + + + + + + + - diff --git a/repos/os/src/server/nic_router/dhcp.h b/repos/os/src/server/nic_router/dhcp.h new file mode 100644 index 0000000000..87e52162ab --- /dev/null +++ b/repos/os/src/server/nic_router/dhcp.h @@ -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 + +namespace Net { + + template + static Ipv4_address dhcp_ipv4_option(Dhcp_packet &dhcp) + { + try { return dhcp.option().value(); } + catch (Dhcp_packet::Option_not_found) { return Ipv4_address { }; } + } +} + +#endif /* _DHCP_H_ */ diff --git a/repos/os/src/server/nic_router/dhcp_client.cc b/repos/os/src/server/nic_router/dhcp_client.cc index b8e662bcbb..6c2b9a9885 100644 --- a/repos/os/src/server/nic_router/dhcp_client.cc +++ b/repos/os/src/server/nic_router/dhcp_client.cc @@ -142,20 +142,7 @@ void Dhcp_client::handle_dhcp_reply(Dhcp_packet &dhcp) } _lease_time_sec = dhcp.option().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().value(); } - catch (Dhcp_packet::Option_not_found) { } - try { subnet_mask = dhcp.option().value(); } - catch (Dhcp_packet::Option_not_found) { } - try { router_ip = dhcp.option().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: diff --git a/repos/os/src/server/nic_router/dhcp_server.cc b/repos/os/src/server/nic_router/dhcp_server.cc index 1e3399fa4c..65cf000c5b 100644 --- a/repos/os/src/server/nic_router/dhcp_server.cc +++ b/repos/os/src/server/nic_router/dhcp_server.cc @@ -21,12 +21,38 @@ using namespace Net; using namespace Genode; -/***************** - ** Dhcp_server ** - *****************/ +/********************** + ** Dhcp_server_base ** + **********************/ -void Dhcp_server::_invalid(Domain &domain, - char const *reason) +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()) { log("[", domain, "] invalid DHCP server (", reason, ")"); } @@ -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::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 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_name dns_server_from = @@ -131,17 +173,9 @@ Pointer 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::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; } diff --git a/repos/os/src/server/nic_router/dhcp_server.h b/repos/os/src/server/nic_router/dhcp_server.h index 6aedab3013..6f9888d068 100644 --- a/repos/os/src/server/nic_router/dhcp_server.h +++ b/repos/os/src/server/nic_router/dhcp_server.h @@ -15,10 +15,11 @@ #define _DHCP_SERVER_H_ /* local includes */ -#include #include #include #include +#include +#include /* Genode includes */ #include @@ -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_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 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 _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 + 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; } }; diff --git a/repos/os/src/server/nic_router/dns_server.cc b/repos/os/src/server/nic_router/dns_server.cc new file mode 100644 index 0000000000..0876c1b82b --- /dev/null +++ b/repos/os/src/server/nic_router/dns_server.cc @@ -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 + +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; +} diff --git a/repos/os/src/server/nic_router/dns_server.h b/repos/os/src/server/nic_router/dns_server.h new file mode 100644 index 0000000000..ed7984cbd9 --- /dev/null +++ b/repos/os/src/server/nic_router/dns_server.h @@ -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 + +/* Genode includes */ +#include + +namespace Net { class Dns_server; } + +class Net::Dns_server : private Genode::Noncopyable, + public Net::List::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_ */ diff --git a/repos/os/src/server/nic_router/domain.cc b/repos/os/src/server/nic_router/domain.cc index a372843663..31073ad387 100644 --- a/repos/os/src/server/nic_router/domain.cc +++ b/repos/os/src/server/nic_router/domain.cc @@ -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 &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 &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 &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()) { diff --git a/repos/os/src/server/nic_router/domain.h b/repos/os/src/server/nic_router/domain.h index fccd40e79b..c7ac7f7667 100644 --- a/repos/os/src/server/nic_router/domain.h +++ b/repos/os/src/server/nic_router/domain.h @@ -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 + 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(); diff --git a/repos/os/src/server/nic_router/interface.cc b/repos/os/src/server/nic_router/interface.cc index 6466eff82c..851f1b7c40 100644 --- a/repos/os/src/server/nic_router/interface.cc +++ b/repos/os/src/server/nic_router/interface.cc @@ -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_srv.ip_lease_time().value / 1000 / 1000); dhcp_opts.append_option(local_intf.subnet_mask()); dhcp_opts.append_option(local_intf.address); - if (dhcp_srv.dns_server().valid()) { - dhcp_opts.append_option(dhcp_srv.dns_server()); } + dhcp_srv.for_each_dns_server_ip([&] (Ipv4_address const &dns_server_ip) { + dhcp_opts.append_option(dns_server_ip); + }); dhcp_opts.append_option(local_intf.broadcast_address()); dhcp_opts.append_option(); @@ -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::Invalid(); } if (old_dhcp_srv.ip_lease_time().value != diff --git a/repos/os/src/server/nic_router/ipv4_config.cc b/repos/os/src/server/nic_router/ipv4_config.cc index b923ab796c..bd844de3ad 100644 --- a/repos/os/src/server/nic_router/ipv4_config.cc +++ b/repos/os/src/server/nic_router/ipv4_config.cc @@ -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_ack) }, + gateway { dhcp_ipv4_option(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(&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"); + } } diff --git a/repos/os/src/server/nic_router/ipv4_config.h b/repos/os/src/server/nic_router/ipv4_config.h index 3ccfbf8c40..30547c729f 100644 --- a/repos/os/src/server/nic_router/ipv4_config.h +++ b/repos/os/src/server/nic_router/ipv4_config.h @@ -16,36 +16,56 @@ /* local includes */ #include +#include +#include + +/* Genode includes */ +#include namespace Net { class Ipv4_config; } struct Net::Ipv4_config { - Ipv4_address_prefix const interface { }; - bool const interface_valid { interface.valid() }; - Ipv4_address const gateway { }; - bool const gateway_valid { gateway.valid() }; - 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() }; - bool const valid { point_to_point || - (interface_valid && - (!gateway_valid || - interface.prefix_matches(gateway))) }; + Genode::Allocator &alloc; + Ipv4_address_prefix const interface; + bool const interface_valid { interface.valid() }; + Ipv4_address const gateway; + bool const gateway_valid { gateway.valid() }; + bool const point_to_point { gateway_valid && + interface_valid && + interface.prefix == 32 }; + Net::List 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 + void for_each_dns_server(FUNC && functor) const + { + dns_servers.for_each([&] (Dns_server const &dns_server) { + functor(dns_server); + }); } diff --git a/repos/os/src/server/nic_router/list.h b/repos/os/src/server/nic_router/list.h index 1c8d1ae14b..2fda07d77a 100644 --- a/repos/os/src/server/nic_router/list.h +++ b/repos/os/src/server/nic_router/list.h @@ -37,6 +37,17 @@ struct Net::List : Genode::List } } + template + 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 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 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::Element::next() }; + + LT const *const next_elem_2 { + curr_elem_2->List::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_ */ diff --git a/repos/os/src/server/nic_router/target.mk b/repos/os/src/server/nic_router/target.mk index 533a54daa2..011e8cba8b 100644 --- a/repos/os/src/server/nic_router/target.mk +++ b/repos/os/src/server/nic_router/target.mk @@ -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) diff --git a/repos/os/src/test/nic_router_dhcp/README b/repos/os/src/test/nic_router_dhcp/README new file mode 100644 index 0000000000..bff259836e --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/README @@ -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. diff --git a/repos/os/src/test/nic_router_dhcp/client/dhcp_client.cc b/repos/os/src/test/nic_router_dhcp/client/dhcp_client.cc new file mode 100644 index 0000000000..f1a3271f04 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/dhcp_client.cc @@ -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 +#include +#include + +/* Genode includes */ +#include + +enum { PKT_SIZE = 1024 }; + +struct Send_buffer_too_small : Genode::Exception { }; +struct Bad_send_dhcp_args : Genode::Exception { }; + +using namespace Genode; +using namespace Net; +using Message_type = Dhcp_packet::Message_type; +using Dhcp_options = Dhcp_packet::Options_aggregator; + + +/*************** + ** Utilities ** + ***************/ + +void append_param_req_list(Dhcp_options &dhcp_opts) +{ + dhcp_opts.append_param_req_list([&] (Dhcp_options::Parameter_request_list_data &data) { + data.append_param_req(); + data.append_param_req(); + data.append_param_req(); + data.append_param_req(); + data.append_param_req(); + data.append_param_req(); + }); +} + + +/***************** + ** Dhcp_client ** + *****************/ + +Dhcp_client::Dhcp_client(Genode::Allocator &alloc, + Timer::Connection &timer, + Nic &nic, + Dhcp_client_handler &handler) +: + _alloc (alloc), + _timeout (timer, *this, &Dhcp_client::_handle_timeout), + _nic (nic), + _handler (handler) +{ + _discover(); +} + + +void Dhcp_client::_discover() +{ + _set_state(State::SELECT, _discover_timeout); + _send(Message_type::DISCOVER, Ipv4_address(), Ipv4_address(), + Ipv4_address()); +} + + +void Dhcp_client::_rerequest(State next_state) +{ + _set_state(next_state, _rerequest_timeout(2)); + Ipv4_address const client_ip = _handler.ip_config().interface.address; + _send(Message_type::REQUEST, client_ip, Ipv4_address(), client_ip); +} + + +void Dhcp_client::_set_state(State state, Microseconds timeout) +{ + _state = state; + _timeout.schedule(timeout); +} + + +Microseconds Dhcp_client::_rerequest_timeout(unsigned lease_time_div_log2) +{ + /* FIXME limit the time because of shortcomings in timeout framework */ + enum { MAX_TIMEOUT_SEC = 3600 }; + uint64_t timeout_sec = _lease_time_sec >> lease_time_div_log2; + + if (timeout_sec > MAX_TIMEOUT_SEC) { + timeout_sec = MAX_TIMEOUT_SEC; + warning("Had to prune the state timeout of DHCP client"); + } + return Microseconds(timeout_sec * 1000 * 1000); +} + + +void Dhcp_client::_handle_timeout(Duration) +{ + switch (_state) { + case State::BOUND: _rerequest(State::RENEW); break; + case State::RENEW: _rerequest(State::REBIND); break; + default: _discover(); + } +} + + +void Dhcp_client::handle_eth(Ethernet_frame ð, Size_guard &size_guard) +{ + if (eth.dst() != _nic.mac() && + eth.dst() != Mac_address(0xff)) + { + throw Drop_packet_inform("DHCP client expects Ethernet targeting the router"); + } + Ipv4_packet &ip = eth.data(size_guard); + if (ip.protocol() != Ipv4_packet::Protocol::UDP) { + throw Drop_packet_inform("DHCP client expects UDP packet"); } + + Udp_packet &udp = ip.data(size_guard); + if (!Dhcp_packet::is_dhcp(&udp)) { + throw Drop_packet_inform("DHCP client expects DHCP packet"); } + + Dhcp_packet &dhcp = udp.data(size_guard); + if (dhcp.op() != Dhcp_packet::REPLY) { + throw Drop_packet_inform("DHCP client expects DHCP reply"); } + + if (dhcp.client_mac() != _nic.mac()) { + throw Drop_packet_inform("DHCP client expects DHCP targeting the router"); } + + try { _handle_dhcp_reply(dhcp); } + catch (Dhcp_packet::Option_not_found) { + throw Drop_packet_inform("DHCP client misses DHCP option"); } +} + + +void Dhcp_client::_handle_dhcp_reply(Dhcp_packet &dhcp) +{ + Message_type const msg_type = + dhcp.option().value(); + + switch (_state) { + case State::SELECT: + + if (msg_type != Message_type::OFFER) { + throw Drop_packet_inform("DHCP client expects an offer"); + } + _set_state(State::REQUEST, _request_timeout); + _send(Message_type::REQUEST, Ipv4_address(), + dhcp.option().value(), + dhcp.yiaddr()); + break; + + case State::REQUEST: + { + if (msg_type != Message_type::ACK) { + throw Drop_packet_inform("DHCP client expects an acknowledgement"); + } + _lease_time_sec = dhcp.option().value(); + _set_state(State::BOUND, _rerequest_timeout(1)); + + log("DHCP request completed:"); + log(" IP lease time: ", _lease_time_sec, " seconds"); + log(" Interface: ", Ipv4_address_prefix(dhcp.yiaddr(), dhcp.option().value())); + log(" Router: ", dhcp.option().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(&opt)->value(); + } + if (opt.code() != Dhcp_packet::Option::Code::DNS_SERVER) { + return; + } + log(" DNS server #", idx++, ": ", reinterpret_cast(&opt)->value()); + }); + Ipv4_config ip_config( + Ipv4_address_prefix( + dhcp.yiaddr(), + dhcp.option().value()), + dhcp.option().value(), + dns_server); + + _handler.ip_config(ip_config); + break; + } + case State::RENEW: + case State::REBIND: + + if (msg_type != Message_type::ACK) { + throw Drop_packet_inform("DHCP client expects an acknowledgement"); + } + _set_state(State::BOUND, _rerequest_timeout(1)); + _lease_time_sec = dhcp.option().value(); + break; + + default: throw Drop_packet_inform("DHCP client doesn't expect a packet"); + } +} + + +void Dhcp_client::_send(Message_type msg_type, + Ipv4_address client_ip, + Ipv4_address server_ip, + Ipv4_address requested_ip) +{ + _nic.send(PKT_SIZE, [&] (void *pkt_base, Size_guard &size_guard) { + + /* create ETH header of the request */ + Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); + eth.dst(Mac_address(0xff)); + eth.src(_nic.mac()); + eth.type(Ethernet_frame::Type::IPV4); + + /* create IP header of the request */ + enum { IPV4_TIME_TO_LIVE = 64 }; + size_t const ip_off = size_guard.head_size(); + Ipv4_packet &ip = eth.construct_at_data(size_guard); + ip.header_length(sizeof(Ipv4_packet) / 4); + ip.version(4); + ip.time_to_live(IPV4_TIME_TO_LIVE); + ip.protocol(Ipv4_packet::Protocol::UDP); + ip.src(client_ip); + ip.dst(Ipv4_address(0xff)); + + /* create UDP header of the request */ + size_t const udp_off = size_guard.head_size(); + Udp_packet &udp = ip.construct_at_data(size_guard); + udp.src_port(Port(Dhcp_packet::BOOTPC)); + udp.dst_port(Port(Dhcp_packet::BOOTPS)); + + /* create mandatory DHCP fields of the request */ + size_t const dhcp_off = size_guard.head_size(); + Dhcp_packet &dhcp = udp.construct_at_data(size_guard); + dhcp.op(Dhcp_packet::REQUEST); + dhcp.htype(Dhcp_packet::Htype::ETH); + dhcp.hlen(sizeof(Mac_address)); + dhcp.ciaddr(client_ip); + dhcp.client_mac(_nic.mac()); + dhcp.default_magic_cookie(); + + /* append DHCP option fields to the request */ + Dhcp_options dhcp_opts(dhcp, size_guard); + dhcp_opts.append_option(msg_type); + switch (msg_type) { + case Message_type::DISCOVER: + append_param_req_list(dhcp_opts); + dhcp_opts.append_option(_nic.mac()); + dhcp_opts.append_option(PKT_SIZE - dhcp_off); + break; + + case Message_type::REQUEST: + append_param_req_list(dhcp_opts); + dhcp_opts.append_option(_nic.mac()); + dhcp_opts.append_option(PKT_SIZE - dhcp_off); + if (_state == State::REQUEST) { + dhcp_opts.append_option(requested_ip); + dhcp_opts.append_option(server_ip); + } + break; + + default: + throw Bad_send_dhcp_args(); + } + dhcp_opts.append_option(); + + /* fill in header values that need the packet to be complete already */ + udp.length(size_guard.head_size() - udp_off); + udp.update_checksum(ip.src(), ip.dst()); + ip.total_length(size_guard.head_size() - ip_off); + ip.update_checksum(); + }); +} diff --git a/repos/os/src/test/nic_router_dhcp/client/dhcp_client.h b/repos/os/src/test/nic_router_dhcp/client/dhcp_client.h new file mode 100644 index 0000000000..e1bbd2ee9c --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/dhcp_client.h @@ -0,0 +1,105 @@ +/* + * \brief DHCP client state model + * \author Martin Stein + * \date 2016-08-24 + */ + +/* + * Copyright (C) 2016-2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _DHCP_CLIENT_H_ +#define _DHCP_CLIENT_H_ + +/* Genode includes */ +#include +#include + +namespace Net { + + /* external definition */ + class Nic_peer; + class Ipv4_config; + class Ethernet_frame; + + /* local definition */ + class Dhcp_client; + class Dhcp_client_handler; + class Drop_packet_inform; +} + + +struct Net::Drop_packet_inform : Genode::Exception +{ + char const *msg; + + Drop_packet_inform(char const *msg) : msg(msg) { } +}; + + +class Net::Dhcp_client_handler +{ + public: + + virtual void ip_config(Ipv4_config const &ip_config) = 0; + + virtual Ipv4_config const &ip_config() const = 0; + + virtual ~Dhcp_client_handler() { } +}; + + +class Net::Dhcp_client +{ + private: + + enum class State + { + INIT = 0, SELECT = 1, REQUEST = 2, BOUND = 3, RENEW = 4, REBIND = 5 + }; + + enum { DISCOVER_TIMEOUT_SEC = 2 }; + enum { REQUEST_TIMEOUT_SEC = 2 }; + + Genode::Allocator &_alloc; + State _state { State::INIT }; + Timer::One_shot_timeout _timeout; + unsigned long _lease_time_sec = 0; + Genode::Microseconds const _discover_timeout { (Genode::uint64_t)DISCOVER_TIMEOUT_SEC * 1000 * 1000 }; + Genode::Microseconds const _request_timeout { (Genode::uint64_t)REQUEST_TIMEOUT_SEC * 1000 * 1000 }; + Nic &_nic; + Dhcp_client_handler &_handler; + + void _handle_dhcp_reply(Dhcp_packet &dhcp); + + void _handle_timeout(Genode::Duration); + + void _rerequest(State next_state); + + Genode::Microseconds _rerequest_timeout(unsigned lease_time_div_log2); + + void _set_state(State state, Genode::Microseconds timeout); + + void _send(Dhcp_packet::Message_type msg_type, + Ipv4_address client_ip, + Ipv4_address server_ip, + Ipv4_address requested_ip); + + void _discover(); + + public: + + Dhcp_client(Genode::Allocator &alloc, + Timer::Connection &timer, + Nic &nic, + Dhcp_client_handler &handler); + + void handle_eth(Ethernet_frame ð, + Size_guard &size_guard); + +}; + +#endif /* _DHCP_CLIENT_H_ */ diff --git a/repos/os/src/test/nic_router_dhcp/client/ipv4_address_prefix.cc b/repos/os/src/test/nic_router_dhcp/client/ipv4_address_prefix.cc new file mode 100644 index 0000000000..bf46d115e3 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/ipv4_address_prefix.cc @@ -0,0 +1,103 @@ +/* + * \brief Ipv4 address combined with a subnet prefix length + * \author Martin Stein + * \date 2017-10-12 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* local includes */ +#include + +using namespace Genode; +using namespace Net; + + +Ipv4_address Ipv4_address_prefix::subnet_mask() const +{ + Ipv4_address result; + if (prefix >= 8) { + + result.addr[0] = 0xff; + + if (prefix >= 16) { + + result.addr[1] = 0xff; + + if (prefix >= 24) { + + result.addr[2] = 0xff; + result.addr[3] = 0xff << (32 - prefix); + } else { + result.addr[2] = 0xff << (24 - prefix); + } + } else { + result.addr[1] = 0xff << (16 - prefix); + } + } else { + result.addr[0] = 0xff << (8 - prefix); + } + return result; +} + + +void Ipv4_address_prefix::print(Genode::Output &output) const +{ + Genode::print(output, address, "/", prefix); +} + + +bool Ipv4_address_prefix::prefix_matches(Ipv4_address const &ip) const +{ + uint8_t prefix_left = prefix; + uint8_t byte = 0; + for (; prefix_left >= 8; prefix_left -= 8, byte++) { + if (ip.addr[byte] != address.addr[byte]) { + return false; } + } + if (prefix_left == 0) { + return true; } + + uint8_t const mask = ~(0xff >> prefix_left); + return !((ip.addr[byte] ^ address.addr[byte]) & mask); +} + + +Ipv4_address Ipv4_address_prefix::broadcast_address() const +{ + Ipv4_address result = address; + Ipv4_address const mask = subnet_mask(); + for (unsigned i = 0; i < 4; i++) { + result.addr[i] |= ~mask.addr[i]; + } + return result; +} + + +Ipv4_address_prefix::Ipv4_address_prefix(Ipv4_address address, + Ipv4_address subnet_mask) +: + address(address), prefix(0) +{ + Genode::uint8_t rest; + if (subnet_mask.addr[0] != 0xff) { + rest = subnet_mask.addr[0]; + prefix = 0; + } else if (subnet_mask.addr[1] != 0xff) { + rest = subnet_mask.addr[1]; + prefix = 8; + } else if (subnet_mask.addr[2] != 0xff) { + rest = subnet_mask.addr[2]; + prefix = 16; + } else { + rest = subnet_mask.addr[3]; + prefix = 24; + } + for (Genode::uint8_t mask = 1 << 7; rest & mask; mask >>= 1) + prefix++; +} diff --git a/repos/os/src/test/nic_router_dhcp/client/ipv4_address_prefix.h b/repos/os/src/test/nic_router_dhcp/client/ipv4_address_prefix.h new file mode 100644 index 0000000000..5d031c9666 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/ipv4_address_prefix.h @@ -0,0 +1,82 @@ +/* + * \brief Ipv4 address combined with a subnet prefix length + * \author Martin Stein + * \date 2017-10-12 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _IPV4_ADDRESS_PREFIX_H_ +#define _IPV4_ADDRESS_PREFIX_H_ + +/* Genode includes */ +#include + +namespace Net { + class Ipv4_address_prefix; + + 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_ */ diff --git a/repos/os/src/test/nic_router_dhcp/client/ipv4_config.cc b/repos/os/src/test/nic_router_dhcp/client/ipv4_config.cc new file mode 100644 index 0000000000..5aea166f58 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/ipv4_config.cc @@ -0,0 +1,43 @@ +/* + * \brief IPv4 peer configuration + * \author Martin Stein + * \date 2016-08-19 + */ + +/* + * Copyright (C) 2016-2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* Genode includes */ +#include + +/* local includes */ +#include + +using namespace Genode; +using namespace Net; + +Ipv4_config::Ipv4_config(Ipv4_address_prefix interface, + Ipv4_address gateway, + Ipv4_address dns_server) +: + interface(interface), gateway(gateway), dns_server(dns_server) +{ + if (!valid && (interface_valid || gateway_valid)) { + error("Bad IP configuration"); + } +} + + +void Ipv4_config::print(Genode::Output &output) const +{ + Genode::print(output, "interface ", interface); + if (gateway.valid()) { + Genode::print(output, ", gateway ", gateway); } + + if (dns_server.valid()) { + Genode::print(output, ", DNS server ", dns_server); } +} diff --git a/repos/os/src/test/nic_router_dhcp/client/ipv4_config.h b/repos/os/src/test/nic_router_dhcp/client/ipv4_config.h new file mode 100644 index 0000000000..6b3f011074 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/ipv4_config.h @@ -0,0 +1,49 @@ +/* + * \brief IPv4 peer configuration + * \author Martin Stein + * \date 2016-08-19 + */ + +/* + * Copyright (C) 2016-2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _IPV4_CONFIG_H_ +#define _IPV4_CONFIG_H_ + +/* local includes */ +#include + +namespace Net { class Ipv4_config; } + +struct Net::Ipv4_config +{ + Ipv4_address_prefix const interface { }; + bool const interface_valid { interface.valid() }; + Ipv4_address const gateway { }; + bool const gateway_valid { gateway.valid() }; + Ipv4_address const dns_server { }; + bool const valid { interface_valid && + (!gateway_valid || + interface.prefix_matches(gateway)) }; + + Ipv4_config(Ipv4_address_prefix interface, + Ipv4_address gateway, + Ipv4_address dns_server); + + Ipv4_config() { } + + bool operator != (Ipv4_config const &other) const + { + return interface != other.interface || + gateway != other.gateway || + dns_server != other.dns_server; + } + + void print(Genode::Output &output) const; +}; + +#endif /* _IPV4_CONFIG_H_ */ diff --git a/repos/os/src/test/nic_router_dhcp/client/main.cc b/repos/os/src/test/nic_router_dhcp/client/main.cc new file mode 100644 index 0000000000..6b67a50bc9 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/main.cc @@ -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 +#include +#include + +/* Genode includes */ +#include +#include +#include +#include +#include + +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 { }; + bool _link_state { false }; + Reconstructible _ip_config { }; + + public: + + struct Invalid_arguments : Exception { }; + + Main(Env &env); + + + /***************** + ** Nic_handler ** + *****************/ + + void handle_eth(Ethernet_frame ð, + 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 ð, + 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); } diff --git a/repos/os/src/test/nic_router_dhcp/client/nic.cc b/repos/os/src/test/nic_router_dhcp/client/nic.cc new file mode 100644 index 0000000000..dd835d63e3 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/nic.cc @@ -0,0 +1,46 @@ +/* + * \brief NIC connection wrapper for a more convenient interface + * \author Martin Stein + * \date 2018-04-16 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* local includes */ +#include + +using namespace Net; +using namespace Genode; + + +void Net::Nic::_ready_to_ack() +{ + while (_source().ack_avail()) { + _source().release_packet(_source().get_acked_packet()); } +} + + +void Net::Nic::_ready_to_submit() +{ + while (_sink().packet_avail()) { + + Packet_descriptor const pkt = _sink().get_packet(); + if (!pkt.size()) { + continue; } + + Size_guard size_guard(pkt.size()); + _handler.handle_eth(Ethernet_frame::cast_from(_sink().packet_content(pkt), size_guard), + size_guard); + + if (!_sink().ready_to_ack()) { + error("ack state FULL"); + return; + } + _sink().acknowledge_packet(pkt); + } +} diff --git a/repos/os/src/test/nic_router_dhcp/client/nic.h b/repos/os/src/test/nic_router_dhcp/client/nic.h new file mode 100644 index 0000000000..b874c7f808 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/nic.h @@ -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 +#include +#include + +namespace Genode { + + class Env; +} + +namespace Net { + + struct Nic_handler; + class Nic; + + using Packet_descriptor = ::Nic::Packet_descriptor; + using Packet_stream_sink = ::Nic::Packet_stream_sink< ::Nic::Session::Policy>; + using Packet_stream_source = ::Nic::Packet_stream_source< ::Nic::Session::Policy>; +} + + +struct Net::Nic_handler +{ + virtual void handle_eth(Ethernet_frame ð, + Size_guard &size_guard) = 0; + + virtual void handle_link_state(bool link_state) = 0; + + virtual ~Nic_handler() { } +}; + + +class Net::Nic +{ + private: + + using Signal_handler = Genode::Signal_handler; + + enum { PKT_SIZE = ::Nic::Packet_allocator::DEFAULT_PACKET_SIZE }; + enum { BUF_SIZE = ::Nic::Session::QUEUE_SIZE * PKT_SIZE }; + + Genode::Env &_env; + Genode::Allocator &_alloc; + Nic_handler &_handler; + bool const &_verbose; + ::Nic::Packet_allocator _pkt_alloc { &_alloc }; + ::Nic::Connection _nic { _env, &_pkt_alloc, BUF_SIZE, BUF_SIZE }; + Signal_handler _sink_ack { _env.ep(), *this, &Nic::_ack_avail }; + Signal_handler _sink_submit { _env.ep(), *this, &Nic::_ready_to_submit }; + Signal_handler _source_ack { _env.ep(), *this, &Nic::_ready_to_ack }; + Signal_handler _source_submit { _env.ep(), *this, &Nic::_packet_avail }; + 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 + void send(Genode::size_t pkt_size, + FUNC && write_to_pkt) + { + try { + Packet_descriptor pkt = _source().alloc_packet(pkt_size); + void *pkt_base = _source().packet_content(pkt); + Size_guard size_guard(pkt_size); + write_to_pkt(pkt_base, size_guard); + _source().submit_packet(pkt); + if (_verbose) { + Size_guard size_guard(pkt_size); + try { Genode::log("snd ", Ethernet_frame::cast_from(pkt_base, size_guard)); } + catch (Size_guard::Exceeded) { Genode::log("snd ?"); } + } + } + catch (Net::Packet_stream_source::Packet_alloc_failed) { + Genode::warning("failed to allocate packet"); } + } + + + /*************** + ** Accessors ** + ***************/ + + Mac_address const &mac() const { return _mac; } +}; + + +#endif /* _NIC_H_ */ diff --git a/repos/os/src/test/nic_router_dhcp/client/target.mk b/repos/os/src/test/nic_router_dhcp/client/target.mk new file mode 100644 index 0000000000..5cdf6856d6 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/client/target.mk @@ -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) diff --git a/repos/os/src/test/nic_router_dhcp/manager/dns_server.cc b/repos/os/src/test/nic_router_dhcp/manager/dns_server.cc new file mode 100644 index 0000000000..17337ead6f --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/manager/dns_server.cc @@ -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 + +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; +} diff --git a/repos/os/src/test/nic_router_dhcp/manager/dns_server.h b/repos/os/src/test/nic_router_dhcp/manager/dns_server.h new file mode 100644 index 0000000000..56d5b80fd4 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/manager/dns_server.h @@ -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 + +/* Genode includes */ +#include + +namespace Local { class Dns_server; } + +class Local::Dns_server : private Genode::Noncopyable, + public Local::List::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_ */ diff --git a/repos/os/src/test/nic_router_dhcp/manager/ipv4_address_prefix.cc b/repos/os/src/test/nic_router_dhcp/manager/ipv4_address_prefix.cc new file mode 100644 index 0000000000..bf46d115e3 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/manager/ipv4_address_prefix.cc @@ -0,0 +1,103 @@ +/* + * \brief Ipv4 address combined with a subnet prefix length + * \author Martin Stein + * \date 2017-10-12 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* local includes */ +#include + +using namespace Genode; +using namespace Net; + + +Ipv4_address Ipv4_address_prefix::subnet_mask() const +{ + Ipv4_address result; + if (prefix >= 8) { + + result.addr[0] = 0xff; + + if (prefix >= 16) { + + result.addr[1] = 0xff; + + if (prefix >= 24) { + + result.addr[2] = 0xff; + result.addr[3] = 0xff << (32 - prefix); + } else { + result.addr[2] = 0xff << (24 - prefix); + } + } else { + result.addr[1] = 0xff << (16 - prefix); + } + } else { + result.addr[0] = 0xff << (8 - prefix); + } + return result; +} + + +void Ipv4_address_prefix::print(Genode::Output &output) const +{ + Genode::print(output, address, "/", prefix); +} + + +bool Ipv4_address_prefix::prefix_matches(Ipv4_address const &ip) const +{ + uint8_t prefix_left = prefix; + uint8_t byte = 0; + for (; prefix_left >= 8; prefix_left -= 8, byte++) { + if (ip.addr[byte] != address.addr[byte]) { + return false; } + } + if (prefix_left == 0) { + return true; } + + uint8_t const mask = ~(0xff >> prefix_left); + return !((ip.addr[byte] ^ address.addr[byte]) & mask); +} + + +Ipv4_address Ipv4_address_prefix::broadcast_address() const +{ + Ipv4_address result = address; + Ipv4_address const mask = subnet_mask(); + for (unsigned i = 0; i < 4; i++) { + result.addr[i] |= ~mask.addr[i]; + } + return result; +} + + +Ipv4_address_prefix::Ipv4_address_prefix(Ipv4_address address, + Ipv4_address subnet_mask) +: + address(address), prefix(0) +{ + Genode::uint8_t rest; + if (subnet_mask.addr[0] != 0xff) { + rest = subnet_mask.addr[0]; + prefix = 0; + } else if (subnet_mask.addr[1] != 0xff) { + rest = subnet_mask.addr[1]; + prefix = 8; + } else if (subnet_mask.addr[2] != 0xff) { + rest = subnet_mask.addr[2]; + prefix = 16; + } else { + rest = subnet_mask.addr[3]; + prefix = 24; + } + for (Genode::uint8_t mask = 1 << 7; rest & mask; mask >>= 1) + prefix++; +} diff --git a/repos/os/src/test/nic_router_dhcp/manager/ipv4_address_prefix.h b/repos/os/src/test/nic_router_dhcp/manager/ipv4_address_prefix.h new file mode 100644 index 0000000000..5d031c9666 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/manager/ipv4_address_prefix.h @@ -0,0 +1,82 @@ +/* + * \brief Ipv4 address combined with a subnet prefix length + * \author Martin Stein + * \date 2017-10-12 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _IPV4_ADDRESS_PREFIX_H_ +#define _IPV4_ADDRESS_PREFIX_H_ + +/* Genode includes */ +#include + +namespace Net { + class Ipv4_address_prefix; + + 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_ */ diff --git a/repos/os/src/test/nic_router_dhcp/manager/list.h b/repos/os/src/test/nic_router_dhcp/manager/list.h new file mode 100644 index 0000000000..e8d935b056 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/manager/list.h @@ -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 +#include + +namespace Local { template class List; } + + +template +struct Local::List : Genode::List +{ + using Base = Genode::List; + + template + void for_each(FUNC && functor) + { + for (LT *elem = Base::first(); elem; ) + { + LT *const next = elem->Base::Element::next(); + functor(*elem); + elem = next; + } + } + + template + 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 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::Element::next() }; + + LT const *const next_elem_2 { + curr_elem_2->List::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_ */ diff --git a/repos/os/src/test/nic_router_dhcp/manager/main.cc b/repos/os/src/test/nic_router_dhcp/manager/main.cc new file mode 100644 index 0000000000..c7b509aade --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/manager/main.cc @@ -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 +#include +#include +#include +#include + +/* local includes */ +#include +#include + +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
_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_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_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); } diff --git a/repos/os/src/test/nic_router_dhcp/manager/target.mk b/repos/os/src/test/nic_router_dhcp/manager/target.mk new file mode 100644 index 0000000000..76fd289216 --- /dev/null +++ b/repos/os/src/test/nic_router_dhcp/manager/target.mk @@ -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) diff --git a/tool/autopilot.list b/tool/autopilot.list index 968a74fe3b..fff5382b88 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -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