From 30a96706cb87dfebd6b4a3ecff210be64a4f5505 Mon Sep 17 00:00:00 2001 From: Martin Stein Date: Fri, 6 Oct 2017 13:00:05 +0200 Subject: [PATCH] nic_router: dhcp server functionality 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 lifetime of an offered assignment is the configured round trip time of the router while the ip_lease_time_sec is applied only if the offer is requested by the client in time. The ports/run/virtualbox_nic_router.run script is an example of how to use the new DHCP server functionality. Ref #2490 --- repos/os/include/net/dhcp.h | 8 + repos/os/include/net/ethernet.h | 5 + repos/os/include/net/ipv4.h | 40 ++ repos/os/src/lib/net/ipv4.cc | 9 + repos/os/src/server/nic_router/README | 37 +- .../server/nic_router/bit_allocator_dynamic.h | 227 ++++++++++ repos/os/src/server/nic_router/domain.cc | 87 ++++ repos/os/src/server/nic_router/domain.h | 88 +++- repos/os/src/server/nic_router/interface.cc | 388 +++++++++++++++++- repos/os/src/server/nic_router/interface.h | 97 ++++- .../os/src/server/nic_router/port_allocator.h | 4 +- repos/ports/run/virtualbox_nic_router.run | 194 +++++++++ repos/ports/run/virtualbox_nic_router.vbox | 101 +++++ 13 files changed, 1257 insertions(+), 28 deletions(-) create mode 100644 repos/os/src/server/nic_router/bit_allocator_dynamic.h create mode 100644 repos/ports/run/virtualbox_nic_router.run create mode 100644 repos/ports/run/virtualbox_nic_router.vbox diff --git a/repos/os/include/net/dhcp.h b/repos/os/include/net/dhcp.h index ba2cdbef7..3797d5546 100644 --- a/repos/os/include/net/dhcp.h +++ b/repos/os/include/net/dhcp.h @@ -112,6 +112,14 @@ class Net::Dhcp_packet throw No_dhcp_packet(); } + void default_magic_cookie() { + _magic_cookie = host_to_big_endian(0x63825363); + } + + void zero_fill_sname() { Genode::memset(_sname, 0, sizeof(_sname)); } + + void zero_fill_file() { Genode::memset(_file, 0, sizeof(_file)); } + /******************************* ** Utilities for the options ** diff --git a/repos/os/include/net/ethernet.h b/repos/os/include/net/ethernet.h index fa52272d2..ccb26285a 100644 --- a/repos/os/include/net/ethernet.h +++ b/repos/os/include/net/ethernet.h @@ -84,6 +84,11 @@ class Net::Ethernet_frame throw No_ethernet_frame(); } + /** + * Constructor for composing a new Ethernet frame + */ + Ethernet_frame() { } + /*************** ** Accessors ** diff --git a/repos/os/include/net/ipv4.h b/repos/os/include/net/ipv4.h index 140c91a51..48f04b42e 100644 --- a/repos/os/include/net/ipv4.h +++ b/repos/os/include/net/ipv4.h @@ -51,6 +51,9 @@ struct Net::Ipv4_address : Network_address Genode::uint32_t to_uint32_little_endian() const; static Ipv4_address from_uint32_little_endian(Genode::uint32_t ip_raw); + + bool is_in_range(Ipv4_address const &first, + Ipv4_address const &last) const; } __attribute__((packed)); @@ -229,6 +232,43 @@ struct Net::Ipv4_address_prefix void print(Genode::Output &output) const; bool prefix_matches(Ipv4_address const &ip) const; + + Ipv4_address 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; + } + + Ipv4_address 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; + } }; diff --git a/repos/os/src/lib/net/ipv4.cc b/repos/os/src/lib/net/ipv4.cc index daf6f8c46..7a2cb3076 100644 --- a/repos/os/src/lib/net/ipv4.cc +++ b/repos/os/src/lib/net/ipv4.cc @@ -38,6 +38,15 @@ void Net::Ipv4_packet::print(Genode::Output &output) const } +bool Ipv4_address::is_in_range(Ipv4_address const &first, + Ipv4_address const &last) const +{ + uint32_t const ip_raw = to_uint32_little_endian(); + return ip_raw >= first.to_uint32_little_endian() && + ip_raw <= last.to_uint32_little_endian(); +} + + uint32_t Ipv4_address::to_uint32_big_endian() const { return addr[0] | diff --git a/repos/os/src/server/nic_router/README b/repos/os/src/server/nic_router/README index a9f2ab942..dd2af1619 100644 --- a/repos/os/src/server/nic_router/README +++ b/repos/os/src/server/nic_router/README @@ -10,7 +10,8 @@ Brief The 'nic_router' component can be used to individually route IPv4 packets between multiple NIC sessions. Thereby, it can translate between different subnets. The component supports IP routing, TCP and UDP routing, the -partitioning of the TCP and UDP port spaces, port forwarding, and NAT. +partitioning of the TCP and UDP port spaces, port forwarding, NAT, and can +also act as DHCP server. Basics @@ -272,12 +273,42 @@ usage is necessary to avoid that a client can run Denial-of-Service attacks against the destination domain by occupying all of its ports. +Configuring DHCP server functionality +##################################### + +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 lifetime of an offered assignment is the configured round-trip time of +the router while ip_lease_time_sec is applied only when the offer is +acknowledged by the client in time. + + Examples ######## This section will list and explain some interesting configuration snippets. A -comprehensive example of how to use the router can be found in the test script -'libports/run/nic_router.run'. The environment for the examples shall be as +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 functionality see the +'ports/run/virtualbox_nic_router.run' script. + +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 diff --git a/repos/os/src/server/nic_router/bit_allocator_dynamic.h b/repos/os/src/server/nic_router/bit_allocator_dynamic.h new file mode 100644 index 000000000..a4055074e --- /dev/null +++ b/repos/os/src/server/nic_router/bit_allocator_dynamic.h @@ -0,0 +1,227 @@ +/* + * \brief Bit Allocator that uses a dynamically allocated RAM dataspace + * \author Martin Stein + * \date 2017-09-26 + */ + +/* + * 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 _BIT_ALLOCATOR_DYNAMIC_H_ +#define _BIT_ALLOCATOR_DYNAMIC_H_ + +/* Genode includes */ +#include + +namespace Genode { + + class Bit_array_dynamic; + class Bit_allocator_dynamic; +} + + +class Genode::Bit_array_dynamic +{ + public: + + struct Invalid_bit_count : Exception { }; + struct Invalid_index_access : Exception { }; + struct Invalid_clear : Exception { }; + struct Invalid_set : Exception { }; + struct Count_of_bits_not_word_aligned : Exception { }; + + protected: + + enum { + BITS_PER_BYTE = 8UL, + BITS_PER_WORD = sizeof(addr_t) * BITS_PER_BYTE + }; + + private: + + unsigned _bit_cnt; + unsigned _word_cnt; + addr_t *_words; + + addr_t _word(addr_t index) const { + return index / BITS_PER_WORD; } + + void _check_range(addr_t const index, + addr_t const width) const + { + if ((index >= _word_cnt * BITS_PER_WORD) || + width > _word_cnt * BITS_PER_WORD || + _word_cnt * BITS_PER_WORD - width < index) + throw Invalid_index_access(); + } + + addr_t _mask(addr_t const index, addr_t const width, + addr_t &rest) const + { + addr_t const shift = index - _word(index) * BITS_PER_WORD; + + rest = width + shift > BITS_PER_WORD ? + width + shift - BITS_PER_WORD : 0; + + return (width >= BITS_PER_WORD) ? ~0UL << shift + : ((1UL << width) - 1) << shift; + } + + void _set(addr_t index, addr_t width, bool free) + { + _check_range(index, width); + + addr_t rest, word, mask; + do { + word = _word(index); + mask = _mask(index, width, rest); + + if (free) { + if ((_words[word] & mask) != mask) + throw Invalid_clear(); + _words[word] &= ~mask; + } else { + if (_words[word] & mask) + throw Invalid_set(); + _words[word] |= mask; + } + + index = (_word(index) + 1) * BITS_PER_WORD; + width = rest; + } while (rest); + } + + public: + + /** + * Return true if at least one bit is set between + * index until index + width - 1 + */ + bool get(addr_t index, addr_t width) const + { + _check_range(index, width); + + bool used = false; + addr_t rest, mask; + do { + mask = _mask(index, width, rest); + used = _words[_word(index)] & mask; + index = (_word(index) + 1) * BITS_PER_WORD; + width = rest; + } while (!used && rest); + + return used; + } + + void set(addr_t const index, addr_t const width) { + _set(index, width, false); } + + void clear(addr_t const index, addr_t const width) { + _set(index, width, true); } + + Bit_array_dynamic(addr_t *addr, unsigned bits) + : _bit_cnt(bits), _word_cnt(_bit_cnt / BITS_PER_WORD), + _words(addr) + { + if (!bits || bits % BITS_PER_WORD) + throw Invalid_bit_count(); + + memset(_words, 0, sizeof(addr_t)*_word_cnt); + + if (bits % BITS_PER_WORD) + throw Count_of_bits_not_word_aligned(); + } +}; + + +class Genode::Bit_allocator_dynamic +{ + private: + + addr_t _next { 0 }; + Allocator &_alloc; + unsigned const _bits_aligned; + addr_t *const _ram; + Bit_array_dynamic _array; + + enum { + BITS_PER_BYTE = 8UL, + BITS_PER_WORD = sizeof(addr_t) * BITS_PER_BYTE, + }; + + /** + * Reserve consecutive number of bits + * + * \noapi + */ + void _reserve(addr_t bit_start, size_t const num) + { + if (!num) return; + + _array.set(bit_start, num); + } + + size_t _ram_size() const + { + return (_bits_aligned / BITS_PER_WORD) * sizeof(addr_t); + } + + public: + + struct Out_of_indices : Exception { }; + + addr_t alloc(size_t const num_log2 = 0) + { + addr_t const step = 1UL << num_log2; + addr_t max = ~0UL; + + do { + try { + /* throws exception if array is accessed outside bounds */ + for (addr_t i = _next & ~(step - 1); i < max; i += step) { + if (_array.get(i, step)) + continue; + + _array.set(i, step); + _next = i + step; + return i; + } + } catch (Bit_array_dynamic::Invalid_index_access) { } + + max = _next; + _next = 0; + + } while (max != 0); + + throw Out_of_indices(); + } + + void free(addr_t const bit_start, size_t const num_log2 = 0) + { + _array.clear(bit_start, 1UL << num_log2); + _next = bit_start; + } + + Bit_allocator_dynamic(Allocator &alloc, unsigned bits) + : + _alloc(alloc), + _bits_aligned(bits % BITS_PER_WORD ? + bits + BITS_PER_WORD - (bits % BITS_PER_WORD) : + bits), + _ram((addr_t *)_alloc.alloc(_ram_size())), + _array(_ram, _bits_aligned) + { + _reserve(bits, _bits_aligned - bits); + } + + ~Bit_allocator_dynamic() + { + _alloc.free((void *)_ram, _ram_size()); + } +}; + +#endif /* _BIT_ALLOCATOR_DYNAMIC_H_ */ diff --git a/repos/os/src/server/nic_router/domain.cc b/repos/os/src/server/nic_router/domain.cc index 45311b65d..347f64c77 100644 --- a/repos/os/src/server/nic_router/domain.cc +++ b/repos/os/src/server/nic_router/domain.cc @@ -24,6 +24,75 @@ using namespace Net; using namespace Genode; +/***************** + ** Dhcp_server ** + *****************/ + +Dhcp_server::Dhcp_server(Xml_node const node, + Allocator &alloc, + Ipv4_address_prefix const &interface) +: + _dns_server(node.attribute_value("dns_server", Ipv4_address())), + _ip_lease_time(_init_ip_lease_time(node)), + _ip_first(node.attribute_value("ip_first", Ipv4_address())), + _ip_last(node.attribute_value("ip_last", Ipv4_address())), + _ip_first_raw(_ip_first.to_uint32_little_endian()), + _ip_count(_ip_last.to_uint32_little_endian() - _ip_first_raw), + _ip_alloc(alloc, _ip_count) +{ + if (!interface.prefix_matches(_ip_first) || + !interface.prefix_matches(_ip_last) || + interface.address.is_in_range(_ip_first, _ip_last)) + { + throw Invalid(); + } +} + + +Microseconds Dhcp_server::_init_ip_lease_time(Xml_node const node) +{ + unsigned long ip_lease_time_sec = + node.attribute_value("ip_lease_time_sec", 0UL); + + if (!ip_lease_time_sec) { + warning("fall back to default ip_lease_time_sec=\"", + (unsigned long)DEFAULT_IP_LEASE_TIME_SEC, "\""); + ip_lease_time_sec = DEFAULT_IP_LEASE_TIME_SEC; + } + return Microseconds((unsigned long)ip_lease_time_sec * 1000 * 1000); +} + + +void Dhcp_server::print(Output &output) const +{ + if (_dns_server.valid()) { + Genode::print(output, "DNS server ", _dns_server, " "); + } + Genode::print(output, "IP first ", _ip_first, + ", last ", _ip_last, + ", count ", _ip_count, + ", lease time ", _ip_lease_time.value / 1000 / 1000, " sec"); +} + + +Ipv4_address Dhcp_server::alloc_ip() +{ + try { + return Ipv4_address::from_uint32_little_endian(_ip_alloc.alloc() + + _ip_first_raw); + } + catch (Bit_allocator_dynamic::Out_of_indices) { + throw Alloc_ip_failed(); + } +} + + +void Dhcp_server::free_ip(Ipv4_address const &ip) +{ + _ip_alloc.free(ip.to_uint32_little_endian() - _ip_first_raw); +} + + /*********************** ** Domain_avl_member ** ***********************/ @@ -100,6 +169,24 @@ Domain::Domain(Configuration &config, Xml_node const node, Allocator &alloc) { throw Invalid(); } + /* try to find configuration for DHCP server role */ + try { + _dhcp_server.set(*new (alloc) + Dhcp_server(node.sub_node("dhcp-server"), alloc, _interface_attr)); + + if (_config.verbose()) { + log(" DHCP server: ", _dhcp_server.deref()); } + } + catch (Xml_node::Nonexistent_sub_node) { } + catch (Dhcp_server::Invalid) { + error("Invalid DHCP server configuration at domain ", *this); } +} + + +Domain::~Domain() +{ + try { destroy(_alloc, &_dhcp_server.deref()); } + catch (Pointer::Invalid) { } } diff --git a/repos/os/src/server/nic_router/domain.h b/repos/os/src/server/nic_router/domain.h index c6875bad7..5f97f4bea 100644 --- a/repos/os/src/server/nic_router/domain.h +++ b/repos/os/src/server/nic_router/domain.h @@ -21,10 +21,13 @@ #include #include #include +#include /* Genode includes */ #include #include +#include +#include namespace Genode { class Allocator; } @@ -32,6 +35,7 @@ namespace Net { class Interface; class Configuration; + class Dhcp_server; class Domain_avl_member; class Domain_base; class Domain; @@ -40,6 +44,52 @@ namespace Net { } +class Net::Dhcp_server : Genode::Noncopyable +{ + private: + + Ipv4_address const _dns_server; + Genode::Microseconds const _ip_lease_time; + Ipv4_address const _ip_first; + Ipv4_address const _ip_last; + Genode::uint32_t const _ip_first_raw; + Genode::uint32_t const _ip_count; + Genode::Bit_allocator_dynamic _ip_alloc; + + Genode::Microseconds _init_ip_lease_time(Genode::Xml_node const node); + + public: + + enum { DEFAULT_IP_LEASE_TIME_SEC = 3600 }; + + struct Alloc_ip_failed : Genode::Exception { }; + struct Invalid : Genode::Exception { }; + + Dhcp_server(Genode::Xml_node const node, + Genode::Allocator &alloc, + Ipv4_address_prefix const &interface); + + Ipv4_address alloc_ip(); + + void free_ip(Ipv4_address const &ip); + + + /********* + ** log ** + *********/ + + void print(Genode::Output &output) const; + + + /*************** + ** Accessors ** + ***************/ + + Ipv4_address const &dns_server() const { return _dns_server; } + Genode::Microseconds ip_lease_time() const { return _ip_lease_time; } +}; + + class Net::Domain_avl_member : public Genode::Avl_string_base { private: @@ -74,22 +124,23 @@ class Net::Domain : public Domain_base { private: - Domain_avl_member _avl_member; - Configuration &_config; - Genode::Xml_node _node; - Genode::Allocator &_alloc; - Ipv4_address_prefix _interface_attr; - Ipv4_address const _gateway; - bool const _gateway_valid; - Ip_rule_list _ip_rules; - Forward_rule_tree _tcp_forward_rules; - Forward_rule_tree _udp_forward_rules; - Transport_rule_list _tcp_rules; - Transport_rule_list _udp_rules; - Port_allocator _tcp_port_alloc; - Port_allocator _udp_port_alloc; - Nat_rule_tree _nat_rules; - Pointer _interface; + Domain_avl_member _avl_member; + Configuration &_config; + Genode::Xml_node _node; + Genode::Allocator &_alloc; + Ipv4_address_prefix _interface_attr; + Ipv4_address const _gateway; + bool const _gateway_valid; + Ip_rule_list _ip_rules; + Forward_rule_tree _tcp_forward_rules; + Forward_rule_tree _udp_forward_rules; + Transport_rule_list _tcp_rules; + Transport_rule_list _udp_rules; + Port_allocator _tcp_port_alloc; + Port_allocator _udp_port_alloc; + Nat_rule_tree _nat_rules; + Pointer _interface; + Pointer _dhcp_server; void _read_forward_rules(Genode::Cstring const &protocol, Domain_tree &domains, @@ -112,6 +163,8 @@ class Net::Domain : public Domain_base Genode::Xml_node const node, Genode::Allocator &alloc); + ~Domain(); + void create_rules(Domain_tree &domains); Ipv4_address const &next_hop(Ipv4_address const &ip) const; @@ -138,8 +191,9 @@ class Net::Domain : public Domain_base Nat_rule_tree &nat_rules() { return _nat_rules; } Ipv4_address_prefix &interface_attr() { return _interface_attr; } Pointer &interface() { return _interface; } - Configuration &config() const { return _config; } + Configuration &config() const { return _config; } Domain_avl_member &avl_member() { return _avl_member; } + Dhcp_server &dhcp_server() { return _dhcp_server.deref(); } }; diff --git a/repos/os/src/server/nic_router/interface.cc b/repos/os/src/server/nic_router/interface.cc index 5c904ecb3..f4924d16e 100644 --- a/repos/os/src/server/nic_router/interface.cc +++ b/repos/os/src/server/nic_router/interface.cc @@ -24,6 +24,28 @@ using namespace Net; using namespace Genode; +/** + * Utility to ensure that a size value doesn't exceed a limit + */ +template +class Size_guard +{ + private: + + size_t _curr { 0 }; + + public: + + void add(size_t size) + { + size_t const new_size = _curr + size; + if (new_size > MAX) { throw EXCEPTION(); } + _curr = new_size; + } + + size_t curr() const { return _curr; } +}; + /*************** ** Utilities ** @@ -243,6 +265,13 @@ void Interface::link_closed(Link &link, L3_protocol const prot) } +void Interface::ip_allocation_expired(Ip_allocation &allocation) +{ + _release_ip_allocation(allocation); + _released_ip_allocations.insert(&allocation); +} + + void Interface::dissolve_link(Link_side &link_side, L3_protocol const prot) { _links(prot).remove(&link_side); @@ -302,6 +331,227 @@ void Interface::_nat_link_and_pass(Ethernet_frame ð, } +void Interface::_send_dhcp_reply(Dhcp_server const &dhcp_srv, + Mac_address const &client_mac, + Ipv4_address const &client_ip, + Dhcp_packet::Message_type msg_type, + uint32_t xid) +{ + /* allocate buffer for the reply */ + enum { BUF_SIZE = 512 }; + void *buf; + try { _alloc.alloc(BUF_SIZE, &buf); } + catch (...) { throw Alloc_dhcp_reply_buffer_failed(); } + + /* create ETH header of the reply */ + Size_guard reply_size; + reply_size.add(sizeof(Ethernet_frame)); + Ethernet_frame &reply_eth = *reinterpret_cast(buf); + reply_eth.dst(client_mac); + reply_eth.src(_router_mac); + reply_eth.type(Ethernet_frame::Type::IPV4); + + /* create IP header of the reply */ + enum { IPV4_TIME_TO_LIVE = 64 }; + size_t const reply_ip_off = reply_size.curr(); + reply_size.add(sizeof(Ipv4_packet)); + Ipv4_packet &reply_ip = *reply_eth.data(); + reply_ip.header_length(sizeof(Ipv4_packet) / 4); + reply_ip.version(4); + reply_ip.diff_service(0); + reply_ip.identification(0); + reply_ip.flags(0); + reply_ip.fragment_offset(0); + reply_ip.time_to_live(IPV4_TIME_TO_LIVE); + reply_ip.protocol(Ipv4_packet::Protocol::UDP); + reply_ip.src(_router_ip()); + reply_ip.dst(client_ip); + + /* create UDP header of the reply */ + size_t const reply_udp_off = reply_size.curr(); + reply_size.add(sizeof(Udp_packet)); + Udp_packet &reply_udp = *reply_ip.data(); + reply_udp.src_port(Port(Dhcp_packet::BOOTPS)); + reply_udp.dst_port(Port(Dhcp_packet::BOOTPC)); + + /* create mandatory DHCP fields of the reply */ + reply_size.add(sizeof(Dhcp_packet)); + Dhcp_packet &reply_dhcp = *reply_udp.data(); + reply_dhcp.op(Dhcp_packet::REPLY); + reply_dhcp.htype(Dhcp_packet::Htype::ETH); + reply_dhcp.hlen(sizeof(Mac_address)); + reply_dhcp.hops(0); + reply_dhcp.xid(xid); + reply_dhcp.secs(0); + reply_dhcp.flags(0); + reply_dhcp.ciaddr(msg_type == Dhcp_packet::Message_type::INFORM ? client_ip : Ipv4_address()); + reply_dhcp.yiaddr(msg_type == Dhcp_packet::Message_type::INFORM ? Ipv4_address() : client_ip); + reply_dhcp.siaddr(_router_ip()); + reply_dhcp.giaddr(Ipv4_address()); + reply_dhcp.client_mac(client_mac); + reply_dhcp.zero_fill_sname(); + reply_dhcp.zero_fill_file(); + reply_dhcp.default_magic_cookie(); + + /* append DHCP option fields to the reply */ + Dhcp_packet::Options_aggregator > + reply_dhcp_opts(reply_dhcp, reply_size); + reply_dhcp_opts.append_option(msg_type); + reply_dhcp_opts.append_option(_router_ip()); + reply_dhcp_opts.append_option(dhcp_srv.ip_lease_time().value / 1000 / 1000); + reply_dhcp_opts.append_option(_domain.interface_attr().subnet_mask()); + reply_dhcp_opts.append_option(_router_ip()); + if (dhcp_srv.dns_server().valid()) { + reply_dhcp_opts.append_option(dhcp_srv.dns_server()); } + reply_dhcp_opts.append_option(_domain.interface_attr().broadcast_address()); + reply_dhcp_opts.append_option(); + + /* fill in header values that need the packet to be complete already */ + reply_udp.length(reply_size.curr() - reply_udp_off); + reply_udp.update_checksum(reply_ip.src(), reply_ip.dst()); + reply_ip.total_length(reply_size.curr() - reply_ip_off); + reply_ip.checksum(Ipv4_packet::calculate_checksum(reply_ip)); + + /* send reply to sender of request and free reply buffer */ + _send(reply_eth, reply_size.curr()); + _alloc.free(buf, BUF_SIZE); +} + + +void Interface::_release_ip_allocation(Ip_allocation &allocation) +{ + if (_config().verbose()) { + log("Release IP allocation: ", allocation, " at ", *this); + } + _ip_allocations.remove(&allocation); +} + + +void Interface::_handle_dhcp_request(Ethernet_frame ð, + Genode::size_t eth_size, + Dhcp_packet &dhcp) +{ + try { + /* try to get the DHCP server config of this interface */ + Dhcp_server &dhcp_srv = _domain.dhcp_server(); + + /* determine type of DHCP request */ + Dhcp_packet::Message_type const msg_type = + dhcp.option().value(); + + try { + /* look up existing DHCP configuration for client */ + Ip_allocation &allocation = + _ip_allocations.find_by_mac(dhcp.client_mac()); + + switch (msg_type) { + case Dhcp_packet::Message_type::DISCOVER: + + if (allocation.bound()) { + throw Bad_dhcp_request(); + + } else { + allocation.lifetime(_config().rtt()); + _send_dhcp_reply(dhcp_srv, eth.src(), + allocation.ip(), + Dhcp_packet::Message_type::OFFER, + dhcp.xid()); + return; + } + case Dhcp_packet::Message_type::REQUEST: + + if (allocation.bound()) { + allocation.lifetime(dhcp_srv.ip_lease_time()); + _send_dhcp_reply(dhcp_srv, eth.src(), + allocation.ip(), + Dhcp_packet::Message_type::ACK, + dhcp.xid()); + return; + + } else { + Dhcp_packet::Server_ipv4 &dhcp_srv_ip = + dhcp.option(); + + if (dhcp_srv_ip.value() == _router_ip()) { + + allocation.set_bound(); + allocation.lifetime(dhcp_srv.ip_lease_time()); + if (_config().verbose()) { + log("Bind IP allocation: ", allocation, + " at ", *this); + } + _send_dhcp_reply(dhcp_srv, eth.src(), + allocation.ip(), + Dhcp_packet::Message_type::ACK, + dhcp.xid()); + return; + + } else { + + _release_ip_allocation(allocation); + _destroy_ip_allocation(allocation); + return; + } + } + case Dhcp_packet::Message_type::INFORM: + + _send_dhcp_reply(dhcp_srv, eth.src(), + allocation.ip(), + Dhcp_packet::Message_type::ACK, + dhcp.xid()); + return; + + case Dhcp_packet::Message_type::DECLINE: + case Dhcp_packet::Message_type::RELEASE: + + _release_ip_allocation(allocation); + _destroy_ip_allocation(allocation); + return; + + case Dhcp_packet::Message_type::NAK: + case Dhcp_packet::Message_type::OFFER: + case Dhcp_packet::Message_type::ACK: + default: throw Bad_dhcp_request(); + } + } + catch (Ip_allocation_tree::No_match) { + + switch (msg_type) { + case Dhcp_packet::Message_type::DISCOVER: + { + Ip_allocation &allocation = *new (_alloc) + Ip_allocation(*this, _config(), + dhcp_srv.alloc_ip(), + dhcp.client_mac(), _timer, + _config().rtt()); + + _ip_allocations.insert(&allocation); + if (_config().verbose()) { + log("Offer IP allocation: ", allocation, + " at ", *this); + } + _send_dhcp_reply(dhcp_srv, eth.src(), + allocation.ip(), + Dhcp_packet::Message_type::OFFER, + dhcp.xid()); + return; + } + case Dhcp_packet::Message_type::REQUEST: + case Dhcp_packet::Message_type::DECLINE: + case Dhcp_packet::Message_type::RELEASE: + case Dhcp_packet::Message_type::NAK: + case Dhcp_packet::Message_type::OFFER: + case Dhcp_packet::Message_type::ACK: + default: throw Bad_dhcp_request(); + } + } + } + catch (Dhcp_packet::Option_not_found) { + throw Bad_dhcp_request(); + } +} + + void Interface::_handle_ip(Ethernet_frame ð, Genode::size_t const eth_size, Packet_descriptor const &pkt) @@ -315,8 +565,30 @@ void Interface::_handle_ip(Ethernet_frame ð, L3_protocol const prot = ip.protocol(); size_t const prot_size = ip.total_length() - ip.header_length() * 4; void *const prot_base = _prot_base(prot, prot_size, ip); - Link_side_id const local = { ip.src(), _src_port(prot, prot_base), - ip.dst(), _dst_port(prot, prot_base) }; + + /* try handling DHCP requests before trying any routing */ + if (prot == L3_protocol::UDP) { + Udp_packet &udp = *new (ip.data()) + Udp_packet(eth_size - sizeof(Ipv4_packet)); + + if (Dhcp_packet::is_dhcp(&udp)) { + + /* get DHCP packet */ + Dhcp_packet &dhcp = *new (udp.data()) + Dhcp_packet(eth_size - sizeof(Ipv4_packet) + - sizeof(Udp_packet)); + + if (dhcp.op() == Dhcp_packet::REQUEST) { + try { + _handle_dhcp_request(eth, eth_size, dhcp); + return; + } + catch (Pointer::Invalid) { } + } + } + } + Link_side_id const local = { ip.src(), _src_port(prot, prot_base), + ip.dst(), _dst_port(prot, prot_base) }; /* try to route via existing UDP/TCP links */ try { @@ -325,7 +597,7 @@ void Interface::_handle_ip(Ethernet_frame ð, bool const client = local_side.is_client(); Link_side &remote_side = client ? link.server() : link.client(); Interface &interface = remote_side.interface(); - if(_config().verbose()) { + if (_config().verbose()) { log("Using ", l3_protocol_name(prot), " link: ", link); } _adapt_eth(eth, eth_size, remote_side.src_ip(), pkt, interface); @@ -535,13 +807,30 @@ void Interface::_ready_to_ack() } +void Interface::_destroy_ip_allocation(Ip_allocation &allocation) +{ + _domain.dhcp_server().free_ip(allocation.ip()); + destroy(_alloc, &allocation); +} + + +void Interface::_destroy_released_ip_allocations() +{ + while (Ip_allocation *allocation = _released_ip_allocations.first()) { + _released_ip_allocations.remove(allocation); + _destroy_ip_allocation(*allocation); + } +} + + void Interface::_handle_eth(void *const eth_base, size_t const eth_size, Packet_descriptor const &pkt) { - /* do garbage collection over transport-layer links */ + /* do garbage collection over transport-layer links and IP allocations */ _destroy_closed_links(_closed_udp_links, _alloc); _destroy_closed_links(_closed_tcp_links, _alloc); + _destroy_released_ip_allocations(); /* inspect and handle ethernet frame */ try { @@ -571,6 +860,18 @@ void Interface::_handle_eth(void *const eth_base, catch (Pointer::Invalid) { error("no interface connected to domain"); } + + catch (Bad_dhcp_request) { + error("bad DHCP request"); } + + catch (Alloc_dhcp_reply_buffer_failed) { + error("failed to allocate buffer for DHCP reply"); } + + catch (Dhcp_reply_buffer_too_small) { + error("DHCP reply buffer too small"); } + + catch (Dhcp_server::Alloc_ip_failed) { + error("failed to allocate IP for DHCP client"); } } @@ -651,6 +952,13 @@ Interface::~Interface() /* destroy links */ _destroy_links(_tcp_links, _closed_tcp_links, _alloc); _destroy_links(_udp_links, _closed_udp_links, _alloc); + + /* destroy IP allocations */ + _destroy_released_ip_allocations(); + while (Ip_allocation *allocation = _ip_allocations.first()) { + _ip_allocations.remove(allocation); + _destroy_ip_allocation(*allocation); + } } @@ -661,3 +969,75 @@ void Interface::print(Output &output) const { Genode::print(output, "\"", _domain.name(), "\""); } + + +/******************* + ** Ip_allocation ** + *******************/ + +Ip_allocation::Ip_allocation(Interface &interface, + Configuration &config, + Ipv4_address const &ip, + Mac_address const &mac, + Timer::Connection &timer, + Microseconds lifetime) +: + _interface(interface), + _config(config), + _ip(ip), + _mac(mac), + _release_timeout(timer, *this, &Ip_allocation::_handle_release_timeout) +{ + _release_timeout.schedule(lifetime); +} + + +void Ip_allocation::lifetime(Microseconds lifetime) +{ + _release_timeout.schedule(lifetime); +} + + +bool Ip_allocation::_higher(Mac_address const &mac) const +{ + return memcmp(mac.addr, _mac.addr, sizeof(_mac.addr)) > 0; +} + + +Ip_allocation &Ip_allocation::find_by_mac(Mac_address const &mac) +{ + if (mac == _mac) { + return *this; } + + Ip_allocation *const allocation = child(_higher(mac)); + if (!allocation) { + throw Ip_allocation_tree::No_match(); } + + return allocation->find_by_mac(mac); +} + + +void Ip_allocation::print(Output &output) const +{ + Genode::print(output, "MAC ", _mac, " IP ", _ip); +} + + +void Ip_allocation::_handle_release_timeout(Duration) +{ + _interface.ip_allocation_expired(*this); +} + + +/************************ + ** Ip_allocation_tree ** + ************************/ + +Ip_allocation & +Ip_allocation_tree::find_by_mac(Mac_address const &mac) const +{ + if (!first()) { + throw No_match(); } + + return first()->find_by_mac(mac); +} diff --git a/repos/os/src/server/nic_router/interface.h b/repos/os/src/server/nic_router/interface.h index 69b801ba6..7c84efd33 100644 --- a/repos/os/src/server/nic_router/interface.h +++ b/repos/os/src/server/nic_router/interface.h @@ -22,6 +22,7 @@ /* Genode includes */ #include +#include namespace Net { @@ -32,12 +33,79 @@ namespace Net { class Transport_rule_list; class Ethernet_frame; class Arp_packet; + class Ip_allocation; + class Ip_allocation_tree; + using Ip_allocation_list = Genode::List; class Interface; + class Dhcp_server; class Configuration; class Domain; } +class Net::Ip_allocation : public Genode::Avl_node, + public Ip_allocation_list::Element +{ + protected: + + Interface &_interface; + Configuration &_config; + Ipv4_address const _ip; + Mac_address const _mac; + Timer::One_shot_timeout _release_timeout; + bool _bound { false }; + + void _handle_release_timeout(Genode::Duration); + + bool _higher(Mac_address const &mac) const; + + public: + + Ip_allocation(Interface &interface, + Configuration &config, + Ipv4_address const &ip, + Mac_address const &mac, + Timer::Connection &timer, + Genode::Microseconds lifetime); + + Ip_allocation &find_by_mac(Mac_address const &mac); + + void lifetime(Genode::Microseconds lifetime); + + + /************** + ** Avl_node ** + **************/ + + bool higher(Ip_allocation *allocation) { return _higher(allocation->_mac); } + + + /********* + ** Log ** + *********/ + + void print(Genode::Output &output) const; + + + /*************** + ** Accessors ** + ***************/ + + Ipv4_address const &ip() const { return _ip; } + bool bound() const { return _bound; } + + void set_bound() { _bound = true; } +}; + + +struct Net::Ip_allocation_tree : public Genode::Avl_tree +{ + struct No_match : Genode::Exception { }; + + Ip_allocation &find_by_mac(Mac_address const &mac) const; +}; + + class Net::Interface { protected: @@ -63,6 +131,8 @@ class Net::Interface Link_side_tree _udp_links; Link_list _closed_tcp_links; Link_list _closed_udp_links; + Ip_allocation_tree _ip_allocations; + Ip_allocation_list _released_ip_allocations; void _new_link(L3_protocol const protocol, Link_side_id const &local_id, @@ -70,6 +140,18 @@ class Net::Interface Interface &remote_interface, Link_side_id const &remote_id); + void _destroy_released_ip_allocations(); + + void _destroy_ip_allocation(Ip_allocation &allocation); + + void _release_ip_allocation(Ip_allocation &allocation); + + void _send_dhcp_reply(Dhcp_server const &dhcp_srv, + Mac_address const &client_mac, + Ipv4_address const &client_ip, + Dhcp_packet::Message_type msg_type, + Genode::uint32_t xid); + Forward_rule_tree &_forward_rules(L3_protocol const prot) const; Transport_rule_list &_transport_rules(L3_protocol const prot) const; @@ -82,6 +164,10 @@ class Net::Interface Genode::size_t const eth_size, Arp_packet &arp); + void _handle_dhcp_request(Ethernet_frame ð, + Genode::size_t eth_size, + Dhcp_packet &dhcp); + void _handle_ip(Ethernet_frame ð, Genode::size_t const eth_size, Packet_descriptor const &pkt); @@ -150,9 +236,12 @@ class Net::Interface public: - struct Bad_transport_protocol : Genode::Exception { }; - struct Bad_network_protocol : Genode::Exception { }; - struct Packet_postponed : Genode::Exception { }; + struct Bad_transport_protocol : Genode::Exception { }; + struct Bad_network_protocol : Genode::Exception { }; + struct Packet_postponed : Genode::Exception { }; + struct Bad_dhcp_request : Genode::Exception { }; + struct Alloc_dhcp_reply_buffer_failed : Genode::Exception { }; + struct Dhcp_reply_buffer_too_small : Genode::Exception { }; Interface(Genode::Entrypoint &ep, Timer::Connection &timer, @@ -165,6 +254,8 @@ class Net::Interface void link_closed(Link &link, L3_protocol const prot); + void ip_allocation_expired(Ip_allocation &allocation); + void dissolve_link(Link_side &link_side, L3_protocol const prot); diff --git a/repos/os/src/server/nic_router/port_allocator.h b/repos/os/src/server/nic_router/port_allocator.h index 131624bb8..248c6d484 100644 --- a/repos/os/src/server/nic_router/port_allocator.h +++ b/repos/os/src/server/nic_router/port_allocator.h @@ -16,9 +16,11 @@ #define _PORT_ALLOCATOR_H_ /* Genode includes */ -#include #include +/* local includes */ +#include + namespace Net { class Port_allocator; diff --git a/repos/ports/run/virtualbox_nic_router.run b/repos/ports/run/virtualbox_nic_router.run new file mode 100644 index 000000000..0b1344f41 --- /dev/null +++ b/repos/ports/run/virtualbox_nic_router.run @@ -0,0 +1,194 @@ +set use_net 1 +set use_ps2 [have_spec ps2] +set use_serial 1 + +set build_components { + core init + drivers/framebuffer + drivers/timer + server/nic_dump + server/nic_router +} + +append build_components virtualbox +set virtualbox_binary "virtualbox-rem" +if {[have_spec muen]} { set virtualbox_binary "virtualbox-muen" } +if {[have_spec nova]} { set virtualbox_binary "virtualbox-nova" } + +source ${genode_dir}/repos/base/run/platform_drv.inc +# override defaults of platform_drv.inc +proc platform_drv_priority {} { return { priority="-1"} } + +lappend_if [expr $use_ps2] build_components drivers/input +lappend_if [expr $use_serial] build_components server/log_terminal +lappend_if [have_spec x86] build_components drivers/rtc + +lappend_if [expr $use_net] build_components drivers/nic + +append_platform_drv_build_components + +build $build_components + +create_boot_directory + +set config { + + + + + + + + + + } + +append_if [have_spec muen] config { + } + +append config { + + + + + + + + + } + +append_platform_drv_config + +append_if [expr $use_ps2] config { + + + + } + +append_if [have_spec framebuffer] config { + + + + } + +append_if [have_spec sdl] config { + + + + + + + } + +append_if [have_spec x86] config { + + + + + + } + +append_if [expr $use_net] config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +append_if [expr $use_serial] config { + + + + + + +} + +append config { + } +append config " + " +append config { + + + + + } + +append_if [expr $use_serial] config { + } + +append config { + + + + + + + + + + +} + +install_config $config + +exec cp ${genode_dir}/repos/ports/run/virtualbox_nic_router.vbox bin/. + +set boot_modules { core nic_dump nic_router ld.lib.so init timer test.iso virtualbox_nic_router.vbox } + +append boot_modules $virtualbox_binary + +# platform-specific modules +lappend_if [expr $use_ps2] boot_modules ps2_drv +lappend_if [have_spec framebuffer] boot_modules fb_drv +lappend_if [have_spec linux] boot_modules fb_sdl +lappend_if [have_spec x86] boot_modules rtc_drv + +append boot_modules { + ld.lib.so libc.lib.so libm.lib.so pthread.lib.so libc_pipe.lib.so + libc_terminal.lib.so libiconv.lib.so stdcxx.lib.so + qemu-usb.lib.so +} + +append_if [expr $use_net] boot_modules { nic_drv } +append_if [expr $use_serial] boot_modules { log_terminal } + +append_platform_drv_boot_modules + +build_boot_image $boot_modules + +if {[have_include "power_on/qemu"]} { + append qemu_args " -m 768 " + + append qemu_args " -cpu phenom " +} + +run_genode_until forever diff --git a/repos/ports/run/virtualbox_nic_router.vbox b/repos/ports/run/virtualbox_nic_router.vbox new file mode 100644 index 000000000..7d303ae0d --- /dev/null +++ b/repos/ports/run/virtualbox_nic_router.vbox @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +