nic_router: dhcp server functionality

One can configure the NIC router to act as DHCP server at interfaces of a
domain by adding the <dhcp> tag to the configuration of the domain like
this:

<domain name="vbox" interface="10.0.1.1/24">
    <dhcp-server ip_first="10.0.1.80"
                 ip_last="10.0.1.100"
                 ip_lease_time_sec="3600"
                 dns_server="10.0.0.2"/>
    ...
</domain>

The attributes ip_first and ip_last define the available IPv4 address
range while ip_lease_time_sec defines the lifetime of an IPv4 address
assignment in seconds. The IPv4 address range must be in the subnet
defined by the interface attribute of the domain tag and must not cover
the IPv4 address in this attribute. The dns_server attribute gives the
IPv4 address of the DNS server that might also be in another subnet.
The 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
This commit is contained in:
Martin Stein 2017-10-06 13:00:05 +02:00 committed by Christian Helmuth
parent 03144093b3
commit 30a96706cb
13 changed files with 1257 additions and 28 deletions

View File

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

View File

@ -84,6 +84,11 @@ class Net::Ethernet_frame
throw No_ethernet_frame();
}
/**
* Constructor for composing a new Ethernet frame
*/
Ethernet_frame() { }
/***************
** Accessors **

View File

@ -51,6 +51,9 @@ struct Net::Ipv4_address : Network_address<IPV4_ADDR_LEN, '.', false>
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;
}
};

View File

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

View File

@ -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 <dhcp> tag to the configuration of the domain like
this:
<domain name="vbox" interface="10.0.1.1/24">
<dhcp-server ip_first="10.0.1.80"
ip_last="10.0.1.100"
ip_lease_time_sec="3600"
dns_server="10.0.0.2"/>
...
</domain>
The attributes ip_first and ip_last define the available IPv4 address
range while ip_lease_time_sec defines the lifetime of an IPv4 address
assignment in seconds. The IPv4 address range must be in the subnet
defined by the interface attribute of the domain tag and must not cover
the IPv4 address in this attribute. The dns_server attribute gives the
IPv4 address of the DNS server that might also be in another subnet.
The 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

View File

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

View File

@ -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<Dhcp_server>::Invalid) { }
}

View File

@ -21,10 +21,13 @@
#include <ip_rule.h>
#include <port_allocator.h>
#include <pointer.h>
#include <bit_allocator_dynamic.h>
/* Genode includes */
#include <util/avl_string.h>
#include <util/xml_node.h>
#include <util/noncopyable.h>
#include <os/duration.h>
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:
@ -90,6 +140,7 @@ class Net::Domain : public Domain_base
Port_allocator _udp_port_alloc;
Nat_rule_tree _nat_rules;
Pointer<Interface> _interface;
Pointer<Dhcp_server> _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;
@ -140,6 +193,7 @@ class Net::Domain : public Domain_base
Pointer<Interface> &interface() { return _interface; }
Configuration &config() const { return _config; }
Domain_avl_member &avl_member() { return _avl_member; }
Dhcp_server &dhcp_server() { return _dhcp_server.deref(); }
};

View File

@ -24,6 +24,28 @@
using namespace Net;
using namespace Genode;
/**
* Utility to ensure that a size value doesn't exceed a limit
*/
template <size_t MAX, typename EXCEPTION>
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 &eth,
}
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<BUF_SIZE, Dhcp_reply_buffer_too_small> reply_size;
reply_size.add(sizeof(Ethernet_frame));
Ethernet_frame &reply_eth = *reinterpret_cast<Ethernet_frame *>(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<Ipv4_packet>();
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<Udp_packet>();
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<Dhcp_packet>();
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<Size_guard<BUF_SIZE, Dhcp_reply_buffer_too_small> >
reply_dhcp_opts(reply_dhcp, reply_size);
reply_dhcp_opts.append_option<Dhcp_packet::Message_type_option>(msg_type);
reply_dhcp_opts.append_option<Dhcp_packet::Server_ipv4>(_router_ip());
reply_dhcp_opts.append_option<Dhcp_packet::Ip_lease_time>(dhcp_srv.ip_lease_time().value / 1000 / 1000);
reply_dhcp_opts.append_option<Dhcp_packet::Subnet_mask>(_domain.interface_attr().subnet_mask());
reply_dhcp_opts.append_option<Dhcp_packet::Router_ipv4>(_router_ip());
if (dhcp_srv.dns_server().valid()) {
reply_dhcp_opts.append_option<Dhcp_packet::Dns_server_ipv4>(dhcp_srv.dns_server()); }
reply_dhcp_opts.append_option<Dhcp_packet::Broadcast_addr>(_domain.interface_attr().broadcast_address());
reply_dhcp_opts.append_option<Dhcp_packet::Options_end>();
/* 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 &eth,
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<Dhcp_packet::Message_type_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<Dhcp_packet::Server_ipv4>();
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 &eth,
Genode::size_t const eth_size,
Packet_descriptor const &pkt)
@ -315,6 +565,28 @@ void Interface::_handle_ip(Ethernet_frame &eth,
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);
/* try handling DHCP requests before trying any routing */
if (prot == L3_protocol::UDP) {
Udp_packet &udp = *new (ip.data<void>())
Udp_packet(eth_size - sizeof(Ipv4_packet));
if (Dhcp_packet::is_dhcp(&udp)) {
/* get DHCP packet */
Dhcp_packet &dhcp = *new (udp.data<void>())
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<Dhcp_server>::Invalid) { }
}
}
}
Link_side_id const local = { ip.src(), _src_port(prot, prot_base),
ip.dst(), _dst_port(prot, prot_base) };
@ -325,7 +597,7 @@ void Interface::_handle_ip(Ethernet_frame &eth,
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<Udp_link>(_closed_udp_links, _alloc);
_destroy_closed_links<Tcp_link>(_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<Interface>::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_link>(_tcp_links, _closed_tcp_links, _alloc);
_destroy_links<Udp_link>(_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);
}

View File

@ -22,6 +22,7 @@
/* Genode includes */
#include <nic_session/nic_session.h>
#include <net/dhcp.h>
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<Ip_allocation>;
class Interface;
class Dhcp_server;
class Configuration;
class Domain;
}
class Net::Ip_allocation : public Genode::Avl_node<Ip_allocation>,
public Ip_allocation_list::Element
{
protected:
Interface &_interface;
Configuration &_config;
Ipv4_address const _ip;
Mac_address const _mac;
Timer::One_shot_timeout<Ip_allocation> _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<Ip_allocation>
{
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 &eth,
Genode::size_t eth_size,
Dhcp_packet &dhcp);
void _handle_ip(Ethernet_frame &eth,
Genode::size_t const eth_size,
Packet_descriptor const &pkt);
@ -153,6 +239,9 @@ class Net::Interface
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);

View File

@ -16,9 +16,11 @@
#define _PORT_ALLOCATOR_H_
/* Genode includes */
#include <util/bit_allocator.h>
#include <net/port.h>
/* local includes */
#include <util/bit_allocator.h>
namespace Net {
class Port_allocator;

View File

@ -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 {
<config prio_levels="4">
<parent-provides>
<service name="ROM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>}
append_if [have_spec muen] config {
<service name="VM"/>}
append config {
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>}
append_platform_drv_config
append_if [expr $use_ps2] config {
<start name="ps2_drv" priority="-1">
<resource name="RAM" quantum="1M"/>
<provides><service name="Input"/></provides>
</start>}
append_if [have_spec framebuffer] config {
<start name="fb_drv" priority="-1" caps="150">
<resource name="RAM" quantum="4M"/>
<provides><service name="Framebuffer"/></provides>
</start>}
append_if [have_spec sdl] config {
<start name="fb_sdl" priority="-1">
<resource name="RAM" quantum="4M"/>
<provides>
<service name="Input"/>
<service name="Framebuffer"/>
</provides>
</start>}
append_if [have_spec x86] config {
<start name="rtc_drv" priority="-1">
<resource name="RAM" quantum="1M"/>
<provides>
<service name="Rtc"/>
</provides>
</start>}
append_if [expr $use_net] config {
<start name="nic_drv" priority="-1">
<resource name="RAM" quantum="4M"/>
<provides><service name="Nic"/></provides>
</start>
<start name="nic_router" caps="200">
<resource name="RAM" quantum="10M"/>
<provides><service name="Nic"/></provides>
<config rtt_sec="6" verbose="yes">
<policy label_prefix="vbox1" domain="vbox"/>
<domain name="vbox" interface="10.0.1.79/24">
<dhcp-server ip_first="10.0.1.80"
ip_last="10.0.1.100"
ip_lease_time_sec="20"
dns_server="10.0.0.2"/>
<tcp dst="0.0.0.0/0"><permit-any domain="uplink"/></tcp>
<udp dst="0.0.0.0/0"><permit-any domain="uplink"/></udp>
</domain>
<domain name="uplink" interface="10.0.0.72/24" gateway="10.0.0.1">
<nat domain="vbox" tcp-ports="999" udp-ports="999"/>
</domain>
</config>
<route>
<service name="Nic"> <child name="nic_drv"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
}
append_if [expr $use_serial] config {
<start name="log_terminal" priority="-1">
<resource name="RAM" quantum="2M"/>
<provides>
<service name="Terminal"/>
</provides>
</start>
}
append config {
<start name="vbox1" priority="-2" caps="500">}
append config "
<binary name=\"$virtualbox_binary\"/>"
append config {
<resource name="RAM" quantum="2G"/>
<config vbox_file="virtualbox_nic_router.vbox" vm_name="TestVM">
<libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc"/>
<vfs>
<dir name="dev"> <log/> <rtc/> </dir>}
append_if [expr $use_serial] config {
<dir name="dev"> <terminal/> </dir>}
append config {
<rom name="virtualbox_nic_router.vbox" />
<rom name="test.iso" />
</vfs>
</config>
<route>
<service name="Nic"> <child name="nic_router"/> </service>
<any-service> <parent /> <any-child /> </any-service>
</route>
</start>
</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

View File

@ -0,0 +1,101 @@
<?xml version="1.0"?>
<!--
** DO NOT EDIT THIS FILE.
** If you make changes to this file while any VirtualBox related application
** is running, your changes will be overwritten later, without taking effect.
** Use VBoxManage or the VirtualBox Manager GUI to make changes.
-->
<VirtualBox xmlns="http://www.innotek.de/VirtualBox-settings" version="1.14-linux">
<Machine uuid="{410f6221-6237-4833-b02c-a54000b7b190}" name="Tinycore" OSType="Linux26" snapshotFolder="Snapshots" lastStateChange="2014-10-30T13:26:17Z">
<MediaRegistry>
<HardDisks/>
<DVDImages>
<Image uuid="{81763434-9a51-49e8-9444-528a5a28c4bc}" location="test.iso"/>
</DVDImages>
<FloppyImages/>
</MediaRegistry>
<ExtraData>
<ExtraDataItem name="GUI/LastGuestSizeHint" value="720,400"/>
<ExtraDataItem name="GUI/LastNormalWindowPosition" value="513,100,720,422"/>
</ExtraData>
<Hardware version="2">
<CPU count="1" hotplug="false">
<HardwareVirtEx enabled="true"/>
<HardwareVirtExNestedPaging enabled="true"/>
<HardwareVirtExVPID enabled="true"/>
<HardwareVirtExUX enabled="false"/>
<PAE enabled="false"/>
<LongMode enabled="false"/>
<HardwareVirtExLargePages enabled="false"/>
<HardwareVirtForce enabled="false"/>
</CPU>
<Memory RAMSize="256" PageFusion="false"/>
<HID Pointing="PS2Mouse" Keyboard="PS2Keyboard"/>
<HPET enabled="false"/>
<Chipset type="PIIX3"/>
<Boot>
<Order position="1" device="Floppy"/>
<Order position="2" device="DVD"/>
<Order position="3" device="HardDisk"/>
<Order position="4" device="None"/>
</Boot>
<Display VRAMSize="12" monitorCount="1" accelerate3D="false" accelerate2DVideo="false"/>
<VideoCapture enabled="false" screens="18446744073709551615" horzRes="1024" vertRes="768" rate="512" fps="25"/>
<RemoteDisplay enabled="false" authType="Null" authTimeout="5000"/>
<BIOS>
<ACPI enabled="true"/>
<IOAPIC enabled="true"/>
<Logo fadeIn="true" fadeOut="true" displayTime="0"/>
<BootMenu mode="MessageAndMenu"/>
<TimeOffset value="0"/>
<PXEDebug enabled="false"/>
</BIOS>
<USB>
<Controllers>
<Controller name="OHCI" type="OHCI"/>
</Controllers>
<DeviceFilters/>
</USB>
<Network>
<Adapter slot="0" enabled="true" MACAddress="0800271D7901" cable="true" speed="0" type="82540EM">
<HostInterface/>
<DisabledModes/>
</Adapter>
</Network>
<UART>
<Port slot="0" enabled="true" IOBase="0x3f8" IRQ="4" path="/dev/terminal" hostMode="HostDevice"/>
<Port slot="1" enabled="false" IOBase="0x2f8" IRQ="3" hostMode="Disconnected"/>
</UART>
<LPT>
<Port slot="0" enabled="false" IOBase="0x378" IRQ="7"/>
<Port slot="1" enabled="false" IOBase="0x378" IRQ="7"/>
</LPT>
<AudioAdapter controller="AC97" driver="Pulse" enabled="false"/>
<RTC localOrUTC="UTC"/>
<SharedFolders/>
<Clipboard mode="Disabled"/>
<DragAndDrop mode="Disabled"/>
<IO>
<IoCache enabled="true" size="5"/>
<BandwidthGroups/>
</IO>
<HostPci>
<Devices/>
</HostPci>
<EmulatedUSB>
<CardReader enabled="false"/>
</EmulatedUSB>
<Guest memoryBalloonSize="0"/>
<GuestProperties>
<GuestProperty name="/VirtualBox/HostInfo/GUI/LanguageID" value="en_US" timestamp="1414675524029545000" flags=""/>
</GuestProperties>
</Hardware>
<StorageControllers>
<StorageController name="IDE" type="PIIX4" PortCount="2" useHostIOCache="true" Bootable="true">
<AttachedDevice passthrough="false" tempeject="true" type="DVD" port="1" device="0">
<Image uuid="{81763434-9a51-49e8-9444-528a5a28c4bc}"/>
</AttachedDevice>
</StorageController>
</StorageControllers>
</Machine>
</VirtualBox>