genode/repos/os/src/server/nic_router/interface.cc

2073 lines
67 KiB
C++

/*
* \brief A net interface in form of a signal-driven NIC-packet handler
* \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 includes */
#include <net/tcp.h>
#include <net/udp.h>
#include <net/icmp.h>
#include <net/arp.h>
#include <base/quota_guard.h>
/* local includes */
#include <interface.h>
#include <configuration.h>
#include <l3_protocol.h>
using namespace Net;
using Genode::Deallocator;
using Genode::size_t;
using Genode::uint32_t;
using Genode::addr_t;
using Genode::log;
using Genode::error;
using Genode::Exception;
using Genode::Out_of_ram;
using Genode::Out_of_caps;
using Genode::Constructible;
using Genode::Signal_context_capability;
using Genode::Signal_transmitter;
/***************
** Utilities **
***************/
template <typename LINK_TYPE>
static void _destroy_dissolved_links(Link_list &dissolved_links,
Deallocator &dealloc)
{
while (Link *link = dissolved_links.first()) {
dissolved_links.remove(link);
destroy(dealloc, static_cast<LINK_TYPE *>(link));
}
}
template <typename LINK_TYPE>
static void _destroy_link(Link &link,
Link_list &links,
Deallocator &dealloc)
{
link.dissolve(false);
links.remove(&link);
destroy(dealloc, static_cast<LINK_TYPE *>(&link));
}
template <typename LINK_TYPE>
static void _destroy_links(Link_list &links,
Link_list &dissolved_links,
Deallocator &dealloc)
{
_destroy_dissolved_links<LINK_TYPE>(dissolved_links, dealloc);
while (Link *link = links.first()) {
_destroy_link<LINK_TYPE>(*link, links, dealloc); }
}
template <typename LINK_TYPE>
static void _destroy_some_links(Link_list &links,
Link_list &dissolved_links,
Deallocator &dealloc,
unsigned long &max)
{
if (!max) {
return; }
while (Link *link = dissolved_links.first()) {
dissolved_links.remove(link);
destroy(dealloc, static_cast<LINK_TYPE *>(link));
if (!--max) {
return; }
}
while (Link *link = links.first()) {
_destroy_link<LINK_TYPE>(*link, links, dealloc);
if (!--max) {
return; }
}
}
static void _link_packet(L3_protocol const prot,
void *const prot_base,
Link &link,
bool const client)
{
switch (prot) {
case L3_protocol::TCP:
if (client) {
static_cast<Tcp_link *>(&link)->client_packet(*(Tcp_packet *)(prot_base));
return;
} else {
static_cast<Tcp_link *>(&link)->server_packet(*(Tcp_packet *)(prot_base));
return;
}
case L3_protocol::UDP:
if (client) {
static_cast<Udp_link *>(&link)->client_packet();
return;
} else {
static_cast<Udp_link *>(&link)->server_packet();
return;
}
return;
case L3_protocol::ICMP:
if (client) {
static_cast<Icmp_link *>(&link)->client_packet();
return;
} else {
static_cast<Icmp_link *>(&link)->server_packet();
return;
}
return;
default: throw Interface::Bad_transport_protocol(); }
}
static void _update_checksum(L3_protocol const prot,
void *const prot_base,
size_t const prot_size,
Ipv4_address const src,
Ipv4_address const dst,
size_t const ip_size)
{
switch (prot) {
case L3_protocol::TCP:
((Tcp_packet *)prot_base)->update_checksum(src, dst, prot_size);
return;
case L3_protocol::UDP:
((Udp_packet *)prot_base)->update_checksum(src, dst);
return;
case L3_protocol::ICMP:
{
Icmp_packet &icmp = *(Icmp_packet *)prot_base;
icmp.update_checksum(ip_size - sizeof(Ipv4_packet) -
sizeof(Icmp_packet));
return;
}
default: throw Interface::Bad_transport_protocol(); }
}
static Port _dst_port(L3_protocol const prot, void *const prot_base)
{
switch (prot) {
case L3_protocol::TCP: return (*(Tcp_packet *)prot_base).dst_port();
case L3_protocol::UDP: return (*(Udp_packet *)prot_base).dst_port();
case L3_protocol::ICMP: return Port((*(Icmp_packet *)prot_base).query_id());
default: throw Interface::Bad_transport_protocol(); }
}
static void _dst_port(L3_protocol const prot,
void *const prot_base,
Port const port)
{
switch (prot) {
case L3_protocol::TCP: (*(Tcp_packet *)prot_base).dst_port(port); return;
case L3_protocol::UDP: (*(Udp_packet *)prot_base).dst_port(port); return;
case L3_protocol::ICMP: (*(Icmp_packet *)prot_base).query_id(port.value); return;
default: throw Interface::Bad_transport_protocol(); }
}
static Port _src_port(L3_protocol const prot, void *const prot_base)
{
switch (prot) {
case L3_protocol::TCP: return (*(Tcp_packet *)prot_base).src_port();
case L3_protocol::UDP: return (*(Udp_packet *)prot_base).src_port();
case L3_protocol::ICMP: return Port((*(Icmp_packet *)prot_base).query_id());
default: throw Interface::Bad_transport_protocol(); }
}
static void _src_port(L3_protocol const prot,
void *const prot_base,
Port const port)
{
switch (prot) {
case L3_protocol::TCP: ((Tcp_packet *)prot_base)->src_port(port); return;
case L3_protocol::UDP: ((Udp_packet *)prot_base)->src_port(port); return;
case L3_protocol::ICMP: ((Icmp_packet *)prot_base)->query_id(port.value); return;
default: throw Interface::Bad_transport_protocol(); }
}
static void *_prot_base(L3_protocol const prot,
Size_guard &size_guard,
Ipv4_packet &ip)
{
switch (prot) {
case L3_protocol::TCP: return &ip.data<Tcp_packet>(size_guard);
case L3_protocol::UDP: return &ip.data<Udp_packet>(size_guard);
case L3_protocol::ICMP: return &ip.data<Icmp_packet>(size_guard);
default: throw Interface::Bad_transport_protocol(); }
}
/**************************
** Interface_link_stats **
**************************/
void Interface_link_stats::report(Genode::Xml_generator &xml)
{
bool empty = true;
if (refused_for_ram) { xml.node("refused_for_ram", [&] () { xml.attribute("value", refused_for_ram); }); empty = false; }
if (refused_for_ports) { xml.node("refused_for_ports", [&] () { xml.attribute("value", refused_for_ports); }); empty = false; }
if (opening) { xml.node("opening", [&] () { xml.attribute("value", opening); }); empty = false; }
if (open) { xml.node("open", [&] () { xml.attribute("value", open); }); empty = false; }
if (closing) { xml.node("closing", [&] () { xml.attribute("value", closing); }); empty = false; }
if (closed) { xml.node("closed", [&] () { xml.attribute("value", closed); }); empty = false; }
if (dissolved_timeout_opening) { xml.node("dissolved_timeout_opening", [&] () { xml.attribute("value", dissolved_timeout_opening); }); empty = false; }
if (dissolved_timeout_open) { xml.node("dissolved_timeout_open", [&] () { xml.attribute("value", dissolved_timeout_open); }); empty = false; }
if (dissolved_timeout_closing) { xml.node("dissolved_timeout_closing", [&] () { xml.attribute("value", dissolved_timeout_closing); }); empty = false; }
if (dissolved_timeout_closed) { xml.node("dissolved_timeout_closed", [&] () { xml.attribute("value", dissolved_timeout_closed); }); empty = false; }
if (dissolved_no_timeout) { xml.node("dissolved_no_timeout", [&] () { xml.attribute("value", dissolved_no_timeout); }); empty = false; }
if (destroyed) { xml.node("destroyed", [&] () { xml.attribute("value", destroyed); }); empty = false; }
if (empty) { throw Report::Empty(); }
}
/****************************
** Interface_object_stats **
****************************/
void Interface_object_stats::report(Genode::Xml_generator &xml)
{
bool empty = true;
if (alive) { xml.node("alive", [&] () { xml.attribute("value", alive); }); empty = false; }
if (destroyed) { xml.node("destroyed", [&] () { xml.attribute("value", destroyed); }); empty = false; }
if (empty) { throw Report::Empty(); }
}
/***************
** Interface **
***************/
void Interface::_destroy_link(Link &link)
{
L3_protocol const prot = link.protocol();
switch (prot) {
case L3_protocol::TCP: ::_destroy_link<Tcp_link>(link, links(prot), _alloc); break;
case L3_protocol::UDP: ::_destroy_link<Udp_link>(link, links(prot), _alloc); break;
case L3_protocol::ICMP: ::_destroy_link<Icmp_link>(link, links(prot), _alloc); break;
default: throw Bad_transport_protocol(); }
}
void Interface::_pass_prot(Ethernet_frame &eth,
Size_guard &size_guard,
Ipv4_packet &ip,
L3_protocol const prot,
void *const prot_base,
size_t const prot_size)
{
eth.src(_router_mac);
_update_checksum(prot, prot_base, prot_size, ip.src(), ip.dst(), ip.total_length());
_pass_ip(eth, size_guard, ip);
}
void Interface::_pass_ip(Ethernet_frame &eth,
Size_guard &size_guard,
Ipv4_packet &ip)
{
ip.update_checksum();
send(eth, size_guard);
}
Forward_rule_tree &
Interface::_forward_rules(Domain &local_domain, L3_protocol const prot) const
{
switch (prot) {
case L3_protocol::TCP: return local_domain.tcp_forward_rules();
case L3_protocol::UDP: return local_domain.udp_forward_rules();
default: throw Bad_transport_protocol(); }
}
Transport_rule_list &
Interface::_transport_rules(Domain &local_domain, L3_protocol const prot) const
{
switch (prot) {
case L3_protocol::TCP: return local_domain.tcp_rules();
case L3_protocol::UDP: return local_domain.udp_rules();
default: throw Bad_transport_protocol(); }
}
void Interface::_attach_to_domain_raw(Domain &domain)
{
_domain = domain;
Signal_transmitter(_session_link_state_sigh).submit();
_interfaces.remove(this);
domain.attach_interface(*this);
}
void Interface::_detach_from_domain_raw()
{
Domain &domain = _domain();
domain.detach_interface(*this);
_interfaces.insert(this);
_domain = Pointer<Domain>();
Signal_transmitter(_session_link_state_sigh).submit();
domain.tcp_stats().dissolve_interface(_tcp_stats);
domain.udp_stats().dissolve_interface(_udp_stats);
domain.icmp_stats().dissolve_interface(_icmp_stats);
domain.arp_stats().dissolve_interface(_arp_stats);
domain.dhcp_stats().dissolve_interface(_dhcp_stats);
}
void Interface::attach_to_domain()
{
try {
_attach_to_domain_raw(_config().domains().find_by_name(
_policy.determine_domain_name()));
attach_to_domain_finish();
}
catch (Domain_tree::No_match) { }
}
void Interface::attach_to_domain_finish()
{
if (!link_state()) {
return; }
/* if domain has yet no IP config, participate in requesting one */
Domain &domain = _domain();
Ipv4_config const &ip_config = domain.ip_config();
if (!ip_config.valid) {
_dhcp_client.discover();
return;
}
attach_to_ip_config(domain, ip_config);
}
void Interface::attach_to_ip_config(Domain &domain,
Ipv4_config const &ip_config)
{
/* if others wait for ARP at the domain, participate in requesting it */
domain.foreign_arp_waiters().for_each([&] (Arp_waiter_list_element &le) {
_broadcast_arp_request(ip_config.interface.address,
le.object()->ip());
});
}
void Interface::session_link_state_sigh(Signal_context_capability sigh)
{
_session_link_state_sigh = sigh;
}
void Interface::detach_from_ip_config()
{
/* destroy our own ARP waiters */
Domain &domain = _domain();
while (_own_arp_waiters.first()) {
cancel_arp_waiting(*_own_arp_waiters.first()->object());
}
/* destroy links */
_destroy_links<Tcp_link> (_tcp_links, _dissolved_tcp_links, _alloc);
_destroy_links<Udp_link> (_udp_links, _dissolved_udp_links, _alloc);
_destroy_links<Icmp_link>(_icmp_links, _dissolved_icmp_links, _alloc);
/* destroy DHCP allocations */
_destroy_released_dhcp_allocations(domain);
while (Dhcp_allocation *allocation = _dhcp_allocations.first()) {
_dhcp_allocations.remove(*allocation);
_destroy_dhcp_allocation(*allocation, domain);
}
/* dissolve ARP cache entries with the MAC address of this interface */
domain.arp_cache().destroy_entries_with_mac(_mac);
}
void Interface::detach_from_remote_ip_config()
{
/* only the DNS server address of the local DHCP server can be remote */
Signal_transmitter(_session_link_state_sigh).submit();
}
void Interface::attach_to_remote_ip_config()
{
/* only the DNS server address of the local DHCP server can be remote */
Signal_transmitter(_session_link_state_sigh).submit();
}
void Interface::_detach_from_domain()
{
try {
detach_from_ip_config();
_detach_from_domain_raw();
}
catch (Pointer<Domain>::Invalid) { }
}
void
Interface::_new_link(L3_protocol const protocol,
Link_side_id const &local,
Pointer<Port_allocator_guard> remote_port_alloc,
Domain &remote_domain,
Link_side_id const &remote)
{
switch (protocol) {
case L3_protocol::TCP:
try {
new (_alloc)
Tcp_link { *this, local, remote_port_alloc, remote_domain,
remote, _timer, _config(), protocol, _tcp_stats };
}
catch (Out_of_ram) { throw Free_resources_and_retry_handle_eth(L3_protocol::TCP); }
catch (Out_of_caps) { throw Free_resources_and_retry_handle_eth(L3_protocol::TCP); }
break;
case L3_protocol::UDP:
try {
new (_alloc)
Udp_link { *this, local, remote_port_alloc, remote_domain,
remote, _timer, _config(), protocol, _udp_stats };
}
catch (Out_of_ram) { throw Free_resources_and_retry_handle_eth(L3_protocol::UDP); }
catch (Out_of_caps) { throw Free_resources_and_retry_handle_eth(L3_protocol::UDP); }
break;
case L3_protocol::ICMP:
try {
new (_alloc)
Icmp_link { *this, local, remote_port_alloc, remote_domain,
remote, _timer, _config(), protocol, _icmp_stats };
}
catch (Out_of_ram) { throw Free_resources_and_retry_handle_eth(L3_protocol::ICMP); }
catch (Out_of_caps) { throw Free_resources_and_retry_handle_eth(L3_protocol::ICMP); }
break;
default: throw Bad_transport_protocol(); }
}
void Interface::dhcp_allocation_expired(Dhcp_allocation &allocation)
{
_release_dhcp_allocation(allocation, _domain());
_released_dhcp_allocations.insert(&allocation);
}
Link_list &Interface::links(L3_protocol const protocol)
{
switch (protocol) {
case L3_protocol::TCP: return _tcp_links;
case L3_protocol::UDP: return _udp_links;
case L3_protocol::ICMP: return _icmp_links;
default: throw Bad_transport_protocol(); }
}
Link_list &Interface::dissolved_links(L3_protocol const protocol)
{
switch (protocol) {
case L3_protocol::TCP: return _dissolved_tcp_links;
case L3_protocol::UDP: return _dissolved_udp_links;
case L3_protocol::ICMP: return _dissolved_icmp_links;
default: throw Bad_transport_protocol(); }
}
void Interface::_adapt_eth(Ethernet_frame &eth,
Ipv4_address const &dst_ip,
Packet_descriptor const &pkt,
Domain &remote_domain)
{
Ipv4_config const &remote_ip_cfg = remote_domain.ip_config();
if (!remote_ip_cfg.valid) {
throw Drop_packet("target domain has yet no IP config");
}
Ipv4_address const &hop_ip = remote_domain.next_hop(dst_ip);
try { eth.dst(remote_domain.arp_cache().find_by_ip(hop_ip).mac()); }
catch (Arp_cache::No_match) {
remote_domain.interfaces().for_each([&] (Interface &interface) {
interface._broadcast_arp_request(remote_ip_cfg.interface.address,
hop_ip);
});
try { new (_alloc) Arp_waiter { *this, remote_domain, hop_ip, pkt }; }
catch (Out_of_ram) { throw Free_resources_and_retry_handle_eth(); }
catch (Out_of_caps) { throw Free_resources_and_retry_handle_eth(); }
throw Packet_postponed();
}
}
void Interface::_nat_link_and_pass(Ethernet_frame &eth,
Size_guard &size_guard,
Ipv4_packet &ip,
L3_protocol const prot,
void *const prot_base,
size_t const prot_size,
Link_side_id const &local_id,
Domain &local_domain,
Domain &remote_domain)
{
try {
Pointer<Port_allocator_guard> remote_port_alloc;
try {
Nat_rule &nat = remote_domain.nat_rules().find_by_domain(local_domain);
if(_config().verbose()) {
log("[", local_domain, "] using NAT rule: ", nat); }
_src_port(prot, prot_base, nat.port_alloc(prot).alloc());
ip.src(remote_domain.ip_config().interface.address);
remote_port_alloc = nat.port_alloc(prot);
}
catch (Nat_rule_tree::No_match) { }
Link_side_id const remote_id = { ip.dst(), _dst_port(prot, prot_base),
ip.src(), _src_port(prot, prot_base) };
_new_link(prot, local_id, remote_port_alloc, remote_domain, remote_id);
remote_domain.interfaces().for_each([&] (Interface &interface) {
interface._pass_prot(eth, size_guard, ip, prot, prot_base, prot_size);
});
} catch (Port_allocator_guard::Out_of_indices) {
switch (prot) {
case L3_protocol::TCP: _tcp_stats.refused_for_ports++; break;
case L3_protocol::UDP: _udp_stats.refused_for_ports++; break;
case L3_protocol::ICMP: _icmp_stats.refused_for_ports++; break;
default: throw Bad_transport_protocol(); }
}
}
void Interface::_send_dhcp_reply(Dhcp_server const &dhcp_srv,
Mac_address const &eth_dst,
Mac_address const &client_mac,
Ipv4_address const &client_ip,
Dhcp_packet::Message_type msg_type,
uint32_t xid,
Ipv4_address_prefix const &local_intf)
{
enum { PKT_SIZE = 512 };
send(PKT_SIZE, [&] (void *pkt_base, Size_guard &size_guard) {
/* create ETH header of the reply */
Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard);
if (msg_type == Dhcp_packet::Message_type::OFFER) {
eth.dst(Ethernet_frame::broadcast()); }
else {
eth.dst(eth_dst); }
eth.src(_router_mac);
eth.type(Ethernet_frame::Type::IPV4);
/* create IP header of the reply */
size_t const ip_off = size_guard.head_size();
Ipv4_packet &ip = eth.construct_at_data<Ipv4_packet>(size_guard);
ip.header_length(sizeof(Ipv4_packet) / 4);
ip.version(4);
ip.time_to_live(IPV4_TIME_TO_LIVE);
ip.protocol(Ipv4_packet::Protocol::UDP);
ip.src(local_intf.address);
ip.dst(client_ip);
/* create UDP header of the reply */
size_t const udp_off = size_guard.head_size();
Udp_packet &udp = ip.construct_at_data<Udp_packet>(size_guard);
udp.src_port(Port(Dhcp_packet::BOOTPS));
udp.dst_port(Port(Dhcp_packet::BOOTPC));
/* create mandatory DHCP fields of the reply */
Dhcp_packet &dhcp = udp.construct_at_data<Dhcp_packet>(size_guard);
dhcp.op(Dhcp_packet::REPLY);
dhcp.htype(Dhcp_packet::Htype::ETH);
dhcp.hlen(sizeof(Mac_address));
dhcp.xid(xid);
if (msg_type == Dhcp_packet::Message_type::INFORM) {
dhcp.ciaddr(client_ip); }
else {
dhcp.yiaddr(client_ip); }
dhcp.siaddr(local_intf.address);
dhcp.client_mac(client_mac);
dhcp.default_magic_cookie();
/* append DHCP option fields to the reply */
Dhcp_packet::Options_aggregator<Size_guard> dhcp_opts(dhcp, size_guard);
dhcp_opts.append_option<Dhcp_packet::Message_type_option>(msg_type);
dhcp_opts.append_option<Dhcp_packet::Server_ipv4>(local_intf.address);
dhcp_opts.append_option<Dhcp_packet::Ip_lease_time>(dhcp_srv.ip_lease_time().value / 1000 / 1000);
dhcp_opts.append_option<Dhcp_packet::Subnet_mask>(local_intf.subnet_mask());
dhcp_opts.append_option<Dhcp_packet::Router_ipv4>(local_intf.address);
if (dhcp_srv.dns_server().valid()) {
dhcp_opts.append_option<Dhcp_packet::Dns_server_ipv4>(dhcp_srv.dns_server()); }
dhcp_opts.append_option<Dhcp_packet::Broadcast_addr>(local_intf.broadcast_address());
dhcp_opts.append_option<Dhcp_packet::Options_end>();
/* fill in header values that need the packet to be complete already */
udp.length(size_guard.head_size() - udp_off);
udp.update_checksum(ip.src(), ip.dst());
ip.total_length(size_guard.head_size() - ip_off);
ip.update_checksum();
});
}
void Interface::_release_dhcp_allocation(Dhcp_allocation &allocation,
Domain &local_domain)
{
if (_config().verbose()) {
log("[", local_domain, "] release DHCP allocation: ", allocation); }
_dhcp_allocations.remove(allocation);
}
void Interface::_new_dhcp_allocation(Ethernet_frame &eth,
Dhcp_packet &dhcp,
Dhcp_server &dhcp_srv,
Domain &local_domain)
{
try {
Dhcp_allocation &allocation = *new (_alloc)
Dhcp_allocation { *this, dhcp_srv.alloc_ip(), dhcp.client_mac(),
_timer, _config().dhcp_offer_timeout() };
_dhcp_allocations.insert(allocation);
if (_config().verbose()) {
log("[", local_domain, "] offer DHCP allocation: ", allocation); }
_send_dhcp_reply(dhcp_srv, eth.src(), dhcp.client_mac(),
allocation.ip(),
Dhcp_packet::Message_type::OFFER,
dhcp.xid(),
local_domain.ip_config().interface);
}
catch (Out_of_ram) { throw Free_resources_and_retry_handle_eth(); }
catch (Out_of_caps) { throw Free_resources_and_retry_handle_eth(); }
}
void Interface::_handle_dhcp_request(Ethernet_frame &eth,
Dhcp_packet &dhcp,
Domain &local_domain)
{
try {
/* try to get the DHCP server config of this interface */
Dhcp_server &dhcp_srv = local_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 */
Dhcp_allocation &allocation =
_dhcp_allocations.find_by_mac(dhcp.client_mac());
Ipv4_address_prefix const &local_intf =
local_domain.ip_config().interface;
switch (msg_type) {
case Dhcp_packet::Message_type::DISCOVER:
if (allocation.bound()) {
_release_dhcp_allocation(allocation, local_domain);
_destroy_dhcp_allocation(allocation, local_domain);
_new_dhcp_allocation(eth, dhcp, dhcp_srv, local_domain);
return;
} else {
allocation.lifetime(_config().dhcp_offer_timeout());
_send_dhcp_reply(dhcp_srv, eth.src(), dhcp.client_mac(),
allocation.ip(),
Dhcp_packet::Message_type::OFFER,
dhcp.xid(), local_intf);
return;
}
case Dhcp_packet::Message_type::REQUEST:
if (allocation.bound()) {
allocation.lifetime(dhcp_srv.ip_lease_time());
_send_dhcp_reply(dhcp_srv, eth.src(), dhcp.client_mac(),
allocation.ip(),
Dhcp_packet::Message_type::ACK,
dhcp.xid(), local_intf);
return;
} else {
Dhcp_packet::Server_ipv4 &dhcp_srv_ip =
dhcp.option<Dhcp_packet::Server_ipv4>();
if (dhcp_srv_ip.value() == local_intf.address)
{
allocation.set_bound();
allocation.lifetime(dhcp_srv.ip_lease_time());
if (_config().verbose()) {
log("[", local_domain, "] bind DHCP allocation: ",
allocation);
}
_send_dhcp_reply(dhcp_srv, eth.src(), dhcp.client_mac(),
allocation.ip(),
Dhcp_packet::Message_type::ACK,
dhcp.xid(), local_intf);
return;
} else {
_release_dhcp_allocation(allocation, local_domain);
_destroy_dhcp_allocation(allocation, local_domain);
return;
}
}
case Dhcp_packet::Message_type::INFORM:
_send_dhcp_reply(dhcp_srv, eth.src(), dhcp.client_mac(),
allocation.ip(),
Dhcp_packet::Message_type::ACK,
dhcp.xid(), local_intf);
return;
case Dhcp_packet::Message_type::DECLINE:
case Dhcp_packet::Message_type::RELEASE:
_release_dhcp_allocation(allocation, local_domain);
_destroy_dhcp_allocation(allocation, local_domain);
return;
case Dhcp_packet::Message_type::NAK: throw Drop_packet("DHCP NAK from client");
case Dhcp_packet::Message_type::OFFER: throw Drop_packet("DHCP OFFER from client");
case Dhcp_packet::Message_type::ACK: throw Drop_packet("DHCP ACK from client");
default: throw Drop_packet("DHCP request with broken message type");
}
}
catch (Dhcp_allocation_tree::No_match) {
switch (msg_type) {
case Dhcp_packet::Message_type::DISCOVER:
_new_dhcp_allocation(eth, dhcp, dhcp_srv, local_domain);
return;
case Dhcp_packet::Message_type::REQUEST: throw Drop_packet("DHCP REQUEST from client without offered/acked IP");
case Dhcp_packet::Message_type::DECLINE: throw Drop_packet("DHCP DECLINE from client without offered/acked IP");
case Dhcp_packet::Message_type::RELEASE: throw Drop_packet("DHCP RELEASE from client without offered/acked IP");
case Dhcp_packet::Message_type::NAK: throw Drop_packet("DHCP NAK from client");
case Dhcp_packet::Message_type::OFFER: throw Drop_packet("DHCP OFFER from client");
case Dhcp_packet::Message_type::ACK: throw Drop_packet("DHCP ACK from client");
default: throw Drop_packet("DHCP request with broken message type");
}
}
}
catch (Dhcp_packet::Option_not_found exception) {
throw Drop_packet("DHCP request misses required option"); }
}
void Interface::_domain_broadcast(Ethernet_frame &eth,
Size_guard &size_guard,
Domain &local_domain)
{
local_domain.interfaces().for_each([&] (Interface &interface) {
if (&interface != this) {
interface.send(eth, size_guard);
}
});
}
void Interface::_send_icmp_dst_unreachable(Ipv4_address_prefix const &local_intf,
Ethernet_frame const &req_eth,
Ipv4_packet const &req_ip,
Icmp_packet::Code const code)
{
enum {
ICMP_MAX_DATA_SIZE = sizeof(Ipv4_packet) + 8,
PKT_SIZE = sizeof(Ethernet_frame) + sizeof(Ipv4_packet) +
sizeof(Icmp_packet) + ICMP_MAX_DATA_SIZE,
};
send(PKT_SIZE, [&] (void *pkt_base, Size_guard &size_guard) {
/* create ETH header */
Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard);
eth.dst(req_eth.src());
eth.src(_router_mac);
eth.type(Ethernet_frame::Type::IPV4);
/* create IP header */
size_t const ip_off = size_guard.head_size();
Ipv4_packet &ip = eth.construct_at_data<Ipv4_packet>(size_guard);
ip.header_length(sizeof(Ipv4_packet) / 4);
ip.version(4);
ip.time_to_live(IPV4_TIME_TO_LIVE);
ip.protocol(Ipv4_packet::Protocol::ICMP);
ip.src(local_intf.address);
ip.dst(req_ip.src());
/* create ICMP header */
Icmp_packet &icmp = ip.construct_at_data<Icmp_packet>(size_guard);
icmp.type(Icmp_packet::Type::DST_UNREACHABLE);
icmp.code(code);
size_t icmp_data_size = req_ip.total_length();
if (icmp_data_size > ICMP_MAX_DATA_SIZE) {
icmp_data_size = ICMP_MAX_DATA_SIZE;
}
/* create ICMP data */
icmp.copy_to_data(&req_ip, icmp_data_size, size_guard);
/* fill in header values that require the packet to be complete */
icmp.update_checksum(icmp_data_size);
ip.total_length(size_guard.head_size() - ip_off);
ip.update_checksum();
});
}
bool Interface::link_state() const
{
return _domain.valid() && _session_link_state;
}
void Interface::handle_link_state()
{
struct Keep_ip_config : Exception { };
try {
attach_to_domain_finish();
/* if the whole domain is down, discard IP config */
Domain &domain_ = domain();
if (!link_state() && domain_.ip_config().valid) {
domain_.interfaces().for_each([&] (Interface &interface) {
if (interface.link_state()) {
throw Keep_ip_config(); }
});
domain_.discard_ip_config();
}
}
catch (Domain::Ip_config_static) { }
catch (Keep_ip_config) { }
/* force report if configured */
try { _config().report().handle_link_state(); }
catch (Pointer<Report>::Invalid) { }
}
void Interface::_send_icmp_echo_reply(Ethernet_frame &eth,
Ipv4_packet &ip,
Icmp_packet &icmp,
size_t icmp_sz,
Size_guard &size_guard)
{
/* adapt Ethernet header */
Mac_address const eth_src = eth.src();
eth.src(eth.dst());
eth.dst(eth_src);
/* adapt IPv4 header */
Ipv4_address const ip_src = ip.src();
ip.src(ip.dst());
ip.dst(ip_src);
/* adapt ICMP header */
icmp.type(Icmp_packet::Type::ECHO_REPLY);
icmp.code(Icmp_packet::Code::ECHO_REPLY);
/* update checksums and send */
icmp.update_checksum(icmp_sz - sizeof(Icmp_packet));
ip.update_checksum();
send(eth, size_guard);
}
void Interface::_handle_icmp_query(Ethernet_frame &eth,
Size_guard &size_guard,
Ipv4_packet &ip,
Packet_descriptor const &pkt,
L3_protocol prot,
void *prot_base,
size_t prot_size,
Domain &local_domain)
{
Link_side_id const local_id = { ip.src(), _src_port(prot, prot_base),
ip.dst(), _dst_port(prot, prot_base) };
/* try to route via existing ICMP links */
try {
Link_side const &local_side = local_domain.links(prot).find_by_id(local_id);
Link &link = local_side.link();
bool const client = local_side.is_client();
Link_side &remote_side = client ? link.server() : link.client();
Domain &remote_domain = remote_side.domain();
if (_config().verbose()) {
log("[", local_domain, "] using ", l3_protocol_name(prot),
" link: ", link);
}
_adapt_eth(eth, remote_side.src_ip(), pkt, remote_domain);
ip.src(remote_side.dst_ip());
ip.dst(remote_side.src_ip());
_src_port(prot, prot_base, remote_side.dst_port());
_dst_port(prot, prot_base, remote_side.src_port());
remote_domain.interfaces().for_each([&] (Interface &interface) {
interface._pass_prot(eth, size_guard, ip, prot, prot_base, prot_size);
});
_link_packet(prot, prot_base, link, client);
return;
}
catch (Link_side_tree::No_match) { }
/* try to route via ICMP rules */
try {
Ip_rule const &rule =
local_domain.icmp_rules().longest_prefix_match(ip.dst());
if(_config().verbose()) {
log("[", local_domain, "] using ICMP rule: ", rule); }
Domain &remote_domain = rule.domain();
_adapt_eth(eth, local_id.dst_ip, pkt, remote_domain);
_nat_link_and_pass(eth, size_guard, ip, prot, prot_base, prot_size,
local_id, local_domain, remote_domain);
return;
}
catch (Ip_rule_list::No_match) { }
throw Bad_transport_protocol();
}
void Interface::_handle_icmp_error(Ethernet_frame &eth,
Size_guard &size_guard,
Ipv4_packet &ip,
Packet_descriptor const &pkt,
Domain &local_domain,
Icmp_packet &icmp,
size_t icmp_sz)
{
/* drop packet if embedded IP checksum invalid */
Ipv4_packet &embed_ip = icmp.data<Ipv4_packet>(size_guard);
if (embed_ip.checksum_error()) {
throw Drop_packet("bad checksum in IP packet embedded in ICMP error");
}
/* get link identity of the embeddeded transport packet */
L3_protocol const embed_prot = embed_ip.protocol();
void *const embed_prot_base = _prot_base(embed_prot, size_guard, embed_ip);
Link_side_id const local_id = { embed_ip.dst(), _dst_port(embed_prot, embed_prot_base),
embed_ip.src(), _src_port(embed_prot, embed_prot_base) };
try {
/* lookup a link state that matches the embedded transport packet */
Link_side const &local_side = local_domain.links(embed_prot).find_by_id(local_id);
Link &link = local_side.link();
bool const client = local_side.is_client();
Link_side &remote_side = client ? link.server() : link.client();
Domain &remote_domain = remote_side.domain();
/* print out that the link is used */
if (_config().verbose()) {
log("[", local_domain, "] using ", l3_protocol_name(embed_prot),
" link: ", link);
}
/* adapt source and destination of Ethernet frame and IP packet */
_adapt_eth(eth, remote_side.src_ip(), pkt, remote_domain);
if (remote_side.dst_ip() == remote_domain.ip_config().interface.address) {
ip.src(remote_side.dst_ip());
}
ip.dst(remote_side.src_ip());
/* adapt source and destination of embedded IP and transport packet */
embed_ip.src(remote_side.src_ip());
embed_ip.dst(remote_side.dst_ip());
_src_port(embed_prot, embed_prot_base, remote_side.src_port());
_dst_port(embed_prot, embed_prot_base, remote_side.dst_port());
/* update checksum of both IP headers and the ICMP header */
embed_ip.update_checksum();
icmp.update_checksum(icmp_sz - sizeof(Icmp_packet));
ip.update_checksum();
/* send adapted packet to all interfaces of remote domain */
remote_domain.interfaces().for_each([&] (Interface &interface) {
interface.send(eth, size_guard);
});
/* refresh link only if the error is not about an ICMP query */
if (embed_prot != L3_protocol::ICMP) {
_link_packet(embed_prot, embed_prot_base, link, client); }
}
/* drop packet if there is no matching link */
catch (Link_side_tree::No_match) {
throw Drop_packet("no link that matches packet embedded in ICMP error"); }
}
void Interface::_handle_icmp(Ethernet_frame &eth,
Size_guard &size_guard,
Ipv4_packet &ip,
Packet_descriptor const &pkt,
L3_protocol prot,
void *prot_base,
size_t prot_size,
Domain &local_domain,
Ipv4_address_prefix const &local_intf)
{
/* drop packet if ICMP checksum is invalid */
Icmp_packet &icmp = *reinterpret_cast<Icmp_packet *>(prot_base);
if (icmp.checksum_error(size_guard.unconsumed())) {
throw Drop_packet("bad ICMP checksum"); }
/* try to act as ICMP Echo server */
if (icmp.type() == Icmp_packet::Type::ECHO_REQUEST &&
ip.dst() == local_intf.address &&
local_domain.icmp_echo_server())
{
if(_config().verbose()) {
log("[", local_domain, "] act as ICMP Echo server"); }
_send_icmp_echo_reply(eth, ip, icmp, prot_size, size_guard);
return;
}
/* try to act as ICMP router */
switch (icmp.type()) {
case Icmp_packet::Type::ECHO_REPLY:
case Icmp_packet::Type::ECHO_REQUEST: _handle_icmp_query(eth, size_guard, ip, pkt, prot, prot_base, prot_size, local_domain); break;
case Icmp_packet::Type::DST_UNREACHABLE: _handle_icmp_error(eth, size_guard, ip, pkt, local_domain, icmp, prot_size); break;
default: Drop_packet("unhandled type in ICMP"); }
}
void Interface::_handle_ip(Ethernet_frame &eth,
Size_guard &size_guard,
Packet_descriptor const &pkt,
Domain &local_domain)
{
/* read packet information */
Ipv4_packet &ip = eth.data<Ipv4_packet>(size_guard);
Ipv4_address_prefix const &local_intf = local_domain.ip_config().interface;
/* try handling subnet-local IP packets */
if (local_intf.prefix_matches(ip.dst()) &&
ip.dst() != local_intf.address)
{
/*
* Packet targets IP local to the domain's subnet and doesn't target
* the router. Thus, forward it to all other interfaces of the domain.
*/
_domain_broadcast(eth, size_guard, local_domain);
return;
}
/* try to route via transport layer rules */
try {
L3_protocol const prot = ip.protocol();
size_t const prot_size = size_guard.unconsumed();
void *const prot_base = _prot_base(prot, size_guard, ip);
/* try handling DHCP requests before trying any routing */
if (prot == L3_protocol::UDP) {
Udp_packet &udp = *reinterpret_cast<Udp_packet *>(prot_base);
if (Dhcp_packet::is_dhcp(&udp)) {
/* get DHCP packet */
Dhcp_packet &dhcp = udp.data<Dhcp_packet>(size_guard);
if (dhcp.op() == Dhcp_packet::REQUEST) {
try {
_handle_dhcp_request(eth, dhcp, local_domain);
return;
}
catch (Pointer<Dhcp_server>::Invalid) { }
} else {
_dhcp_client.handle_ip(eth, size_guard);
return;
}
}
}
else if (prot == L3_protocol::ICMP) {
_handle_icmp(eth, size_guard, ip, pkt, prot, prot_base, prot_size,
local_domain, local_intf);
return;
}
Link_side_id const local_id = { ip.src(), _src_port(prot, prot_base),
ip.dst(), _dst_port(prot, prot_base) };
/* try to route via existing UDP/TCP links */
try {
Link_side const &local_side = local_domain.links(prot).find_by_id(local_id);
Link &link = local_side.link();
bool const client = local_side.is_client();
Link_side &remote_side = client ? link.server() : link.client();
Domain &remote_domain = remote_side.domain();
if (_config().verbose()) {
log("[", local_domain, "] using ", l3_protocol_name(prot),
" link: ", link);
}
_adapt_eth(eth, remote_side.src_ip(), pkt, remote_domain);
ip.src(remote_side.dst_ip());
ip.dst(remote_side.src_ip());
_src_port(prot, prot_base, remote_side.dst_port());
_dst_port(prot, prot_base, remote_side.src_port());
remote_domain.interfaces().for_each([&] (Interface &interface) {
interface._pass_prot(eth, size_guard, ip, prot, prot_base, prot_size);
});
_link_packet(prot, prot_base, link, client);
return;
}
catch (Link_side_tree::No_match) { }
/* try to route via forward rules */
if (local_id.dst_ip == local_intf.address) {
try {
Forward_rule const &rule =
_forward_rules(local_domain, prot).find_by_port(local_id.dst_port);
if(_config().verbose()) {
log("[", local_domain, "] using forward rule: ",
l3_protocol_name(prot), " ", rule);
}
Domain &remote_domain = rule.domain();
_adapt_eth(eth, rule.to_ip(), pkt, remote_domain);
ip.dst(rule.to_ip());
if (!(rule.to_port() == Port(0))) {
_dst_port(prot, prot_base, rule.to_port());
}
_nat_link_and_pass(eth, size_guard, ip, prot, prot_base,
prot_size, local_id, local_domain, remote_domain);
return;
}
catch (Forward_rule_tree::No_match) { }
}
/* try to route via transport and permit rules */
try {
Transport_rule const &transport_rule =
_transport_rules(local_domain, prot).longest_prefix_match(local_id.dst_ip);
Permit_rule const &permit_rule =
transport_rule.permit_rule(local_id.dst_port);
if(_config().verbose()) {
log("[", local_domain, "] using ", l3_protocol_name(prot),
" rule: ", transport_rule, " ", permit_rule);
}
Domain &remote_domain = permit_rule.domain();
_adapt_eth(eth, local_id.dst_ip, pkt, remote_domain);
_nat_link_and_pass(eth, size_guard, ip, prot, prot_base, prot_size,
local_id, local_domain, remote_domain);
return;
}
catch (Transport_rule_list::No_match) { }
catch (Permit_single_rule_tree::No_match) { }
}
catch (Interface::Bad_transport_protocol) { }
/* try to route via IP rules */
try {
Ip_rule const &rule =
local_domain.ip_rules().longest_prefix_match(ip.dst());
if(_config().verbose()) {
log("[", local_domain, "] using IP rule: ", rule); }
Domain &remote_domain = rule.domain();
_adapt_eth(eth, ip.dst(), pkt, remote_domain);
remote_domain.interfaces().for_each([&] (Interface &interface) {
interface._pass_ip(eth, size_guard, ip);
});
return;
}
catch (Ip_rule_list::No_match) { }
/* give up and drop packet */
_send_icmp_dst_unreachable(local_intf, eth, ip,
Icmp_packet::Code::DST_NET_UNREACHABLE);
if (_config().verbose()) {
log("[", local_domain, "] unroutable packet"); }
}
void Interface::_broadcast_arp_request(Ipv4_address const &src_ip,
Ipv4_address const &dst_ip)
{
enum {
ETH_HDR_SZ = sizeof(Ethernet_frame),
ETH_DAT_SZ = sizeof(Arp_packet) + ETH_HDR_SZ >= Ethernet_frame::MIN_SIZE ?
sizeof(Arp_packet) :
Ethernet_frame::MIN_SIZE - ETH_HDR_SZ,
ETH_CRC_SZ = sizeof(Genode::uint32_t),
PKT_SIZE = ETH_HDR_SZ + ETH_DAT_SZ + ETH_CRC_SZ,
};
send(PKT_SIZE, [&] (void *pkt_base, Size_guard &size_guard) {
/* write Ethernet header */
Ethernet_frame &eth = Ethernet_frame::construct_at(pkt_base, size_guard);
eth.dst(Mac_address(0xff));
eth.src(_router_mac);
eth.type(Ethernet_frame::Type::ARP);
/* write ARP header */
Arp_packet &arp = eth.construct_at_data<Arp_packet>(size_guard);
arp.hardware_address_type(Arp_packet::ETHERNET);
arp.protocol_address_type(Arp_packet::IPV4);
arp.hardware_address_size(sizeof(Mac_address));
arp.protocol_address_size(sizeof(Ipv4_address));
arp.opcode(Arp_packet::REQUEST);
arp.src_mac(_router_mac);
arp.src_ip(src_ip);
arp.dst_mac(Mac_address(0xff));
arp.dst_ip(dst_ip);
});
}
void Interface::_handle_arp_reply(Ethernet_frame &eth,
Size_guard &size_guard,
Arp_packet &arp,
Domain &local_domain)
{
try {
/* check wether a matching ARP cache entry already exists */
local_domain.arp_cache().find_by_ip(arp.src_ip());
if (_config().verbose()) {
log("[", local_domain, "] ARP entry already exists"); }
}
catch (Arp_cache::No_match) {
/* by now, no matching ARP cache entry exists, so create one */
Ipv4_address const ip = arp.src_ip();
local_domain.arp_cache().new_entry(ip, arp.src_mac());
/* continue handling of packets that waited for the entry */
for (Arp_waiter_list_element *waiter_le = local_domain.foreign_arp_waiters().first();
waiter_le; )
{
Arp_waiter &waiter = *waiter_le->object();
waiter_le = waiter_le->next();
if (ip != waiter.ip()) { continue; }
waiter.src()._continue_handle_eth(local_domain, waiter.packet());
destroy(waiter.src()._alloc, &waiter);
}
}
Ipv4_address_prefix const &local_intf = local_domain.ip_config().interface;
if (local_intf.prefix_matches(arp.dst_ip()) &&
arp.dst_ip() != local_intf.address)
{
/*
* Packet targets IP local to the domain's subnet and doesn't target
* the router. Thus, forward it to all other interfaces of the domain.
*/
if (_config().verbose()) {
log("[", local_domain, "] forward ARP reply for local IP "
"to all interfaces of the sender domain"); }
_domain_broadcast(eth, size_guard, local_domain);
}
}
void Interface::_send_arp_reply(Ethernet_frame &eth,
Size_guard &size_guard,
Arp_packet &arp)
{
/* interchange source and destination MAC and IP addresses */
Ipv4_address dst_ip = arp.dst_ip();
arp.dst_ip(arp.src_ip());
arp.dst_mac(arp.src_mac());
eth.dst(eth.src());
arp.src_ip(dst_ip);
arp.src_mac(_router_mac);
eth.src(_router_mac);
/* mark packet as reply and send it back to its sender */
arp.opcode(Arp_packet::REPLY);
send(eth, size_guard);
}
void Interface::_handle_arp_request(Ethernet_frame &eth,
Size_guard &size_guard,
Arp_packet &arp,
Domain &local_domain)
{
Ipv4_config const &local_ip_cfg = local_domain.ip_config();
Ipv4_address_prefix const &local_intf = local_ip_cfg.interface;
if (local_intf.prefix_matches(arp.dst_ip())) {
/* ARP request for an IP local to the domain's subnet */
if (arp.src_ip() == arp.dst_ip()) {
/* gratuitous ARP requests are not really necessary */
throw Drop_packet("gratuitous ARP request");
} else if (arp.dst_ip() == local_intf.address) {
/* ARP request for the routers IP at this domain */
if (_config().verbose()) {
log("[", local_domain, "] answer ARP request for router IP "
"with router MAC"); }
_send_arp_reply(eth, size_guard, arp);
} else {
/* forward request to all other interfaces of the domain */
if (_config().verbose()) {
log("[", local_domain, "] forward ARP request for local IP "
"to all interfaces of the sender domain"); }
_domain_broadcast(eth, size_guard, local_domain);
}
} else {
/* ARP request for an IP foreign to the domain's subnet */
if (local_ip_cfg.gateway_valid) {
/* leave request up to the gateway of the domain */
throw Drop_packet("leave ARP request up to gateway");
} else {
/* try to act as gateway for the domain as none is configured */
if (_config().verbose()) {
log("[", local_domain, "] answer ARP request for foreign IP "
"with router MAC"); }
_send_arp_reply(eth, size_guard, arp);
}
}
}
void Interface::_handle_arp(Ethernet_frame &eth,
Size_guard &size_guard,
Domain &local_domain)
{
/* ignore ARP regarding protocols other than IPv4 via ethernet */
Arp_packet &arp = eth.data<Arp_packet>(size_guard);
if (!arp.ethernet_ipv4()) {
throw Drop_packet("ARP for unknown protocol"); }
switch (arp.opcode()) {
case Arp_packet::REPLY: _handle_arp_reply(eth, size_guard, arp, local_domain); break;
case Arp_packet::REQUEST: _handle_arp_request(eth, size_guard, arp, local_domain); break;
default: throw Drop_packet("unknown ARP operation"); }
}
void Interface::_handle_pkt()
{
Packet_descriptor const pkt = _sink.get_packet();
Size_guard size_guard(pkt.size());
try {
_handle_eth(_sink.packet_content(pkt), size_guard, pkt);
_ack_packet(pkt);
}
catch (Packet_postponed) { }
catch (Genode::Packet_descriptor::Invalid_packet) { }
}
void Interface::_ready_to_submit()
{
unsigned long const max_pkts = _config().max_packets_per_signal();
if (max_pkts) {
for (unsigned long i = 0; _sink.packet_avail(); i++) {
if (i >= max_pkts) {
Signal_transmitter(_sink_submit).submit();
break;
}
_handle_pkt();
}
} else {
while (_sink.packet_avail()) {
_handle_pkt(); }
}
}
void Interface::_continue_handle_eth(Domain const &domain,
Packet_descriptor const &pkt)
{
Size_guard size_guard(pkt.size());
try { _handle_eth(_sink.packet_content(pkt), size_guard, pkt); }
catch (Packet_postponed) {
if (domain.verbose_packet_drop()) {
log("[", domain, "] drop packet (handling postponed twice)"); }
}
catch (Genode::Packet_descriptor::Invalid_packet) {
if (domain.verbose_packet_drop()) {
log("[", domain, "] invalid Nic packet received");
}
}
_ack_packet(pkt);
}
void Interface::_ready_to_ack()
{
while (_source.ack_avail()) {
_source.release_packet(_source.get_acked_packet()); }
}
void Interface::_destroy_dhcp_allocation(Dhcp_allocation &allocation,
Domain &local_domain)
{
try { local_domain.dhcp_server().free_ip(allocation.ip()); }
catch (Pointer<Dhcp_server>::Invalid) { }
destroy(_alloc, &allocation);
}
void Interface::_destroy_released_dhcp_allocations(Domain &local_domain)
{
while (Dhcp_allocation *allocation = _released_dhcp_allocations.first()) {
_released_dhcp_allocations.remove(allocation);
_destroy_dhcp_allocation(*allocation, local_domain);
}
}
void Interface::_handle_eth(Ethernet_frame &eth,
Size_guard &size_guard,
Packet_descriptor const &pkt,
Domain &local_domain)
{
if (local_domain.ip_config().valid) {
switch (eth.type()) {
case Ethernet_frame::Type::ARP: _handle_arp(eth, size_guard, local_domain); break;
case Ethernet_frame::Type::IPV4: _handle_ip(eth, size_guard, pkt, local_domain); break;
default: throw Bad_network_protocol(); }
} else {
switch (eth.type()) {
case Ethernet_frame::Type::IPV4: _dhcp_client.handle_ip(eth, size_guard); break;
default: throw Bad_network_protocol(); }
}
}
void Interface::_handle_eth(void *const eth_base,
Size_guard &size_guard,
Packet_descriptor const &pkt)
{
try {
Domain &local_domain = _domain();
local_domain.raise_rx_bytes(size_guard.total_size());
try {
Ethernet_frame &eth = Ethernet_frame::cast_from(eth_base, size_guard);
try {
/* do garbage collection over transport-layer links and DHCP allocations */
_destroy_dissolved_links<Icmp_link>(_dissolved_icmp_links, _alloc);
_destroy_dissolved_links<Udp_link>(_dissolved_udp_links, _alloc);
_destroy_dissolved_links<Tcp_link>(_dissolved_tcp_links, _alloc);
_destroy_released_dhcp_allocations(local_domain);
/* log received packet if desired */
if (local_domain.verbose_packets()) {
log("[", local_domain, "] rcv ", eth); }
/* try to handle ethernet frame */
try { _handle_eth(eth, size_guard, pkt, local_domain); }
catch (Free_resources_and_retry_handle_eth) {
try {
if (_config().verbose()) {
log("[", local_domain, "] free resources and retry to handle packet"); }
/*
* Resources do not suffice, destroy some links
*
* Limit number of links to destroy because otherwise,
* this could block the router for a significant
* amount of time.
*/
unsigned long max = MAX_FREE_OPS_PER_EMERGENCY;
_destroy_some_links<Tcp_link> (_tcp_links, _dissolved_tcp_links, _alloc, max);
_destroy_some_links<Udp_link> (_udp_links, _dissolved_udp_links, _alloc, max);
_destroy_some_links<Icmp_link>(_icmp_links, _dissolved_icmp_links, _alloc, max);
/* retry to handle ethernet frame */
_handle_eth(eth, size_guard, pkt, local_domain);
}
catch (Free_resources_and_retry_handle_eth exception) {
if (exception.prot != (L3_protocol)0) {
switch (exception.prot) {
case L3_protocol::TCP: _tcp_stats.refused_for_ram++; break;
case L3_protocol::UDP: _udp_stats.refused_for_ram++; break;
case L3_protocol::ICMP: _icmp_stats.refused_for_ram++; break;
default: throw Bad_transport_protocol(); }
}
/* give up if the resources still not suffice */
throw Drop_packet("insufficient resources");
}
}
}
catch (Dhcp_server::Alloc_ip_failed) {
if (_config().verbose()) {
log("[", local_domain, "] failed to allocate IP for DHCP "
"client");
}
}
catch (Port_allocator_guard::Out_of_indices) {
if (_config().verbose()) {
log("[", local_domain, "] no available NAT ports"); }
}
catch (Domain::No_next_hop) {
if (_config().verbose()) {
log("[", local_domain, "] cannot find next hop"); }
}
catch (Alloc_dhcp_msg_buffer_failed) {
if (_config().verbose()) {
log("[", local_domain, "] failed to allocate buffer for "
"DHCP reply");
}
}
catch (Bad_network_protocol) {
if (_config().verbose()) {
log("[", local_domain, "] unknown network layer "
"protocol");
}
}
catch (Drop_packet exception) {
if (local_domain.verbose_packet_drop()) {
log("[", local_domain, "] drop packet (",
exception.reason, ")");
}
}
}
catch (Size_guard::Exceeded) {
if (_config().verbose()) {
log("[", local_domain, "] drop packet: packet size-guard "
"exceeded");
}
}
}
catch (Pointer<Domain>::Invalid) {
try {
Ethernet_frame &eth = Ethernet_frame::cast_from(eth_base, size_guard);
if (_config().verbose_packets()) {
log("[?] rcv ", eth); }
}
catch (Size_guard::Exceeded) {
if (_config().verbose_packets()) {
log("[?] rcv ?"); }
}
if (_config().verbose()) {
log("[?] drop packet: no domain"); }
}
}
void Interface::send(Ethernet_frame &eth,
Size_guard &size_guard)
{
send(size_guard.total_size(), [&] (void *pkt_base, Size_guard &size_guard) {
Genode::memcpy(pkt_base, (void *)&eth, size_guard.total_size());
});
}
void Interface::_send_alloc_pkt(Packet_descriptor &pkt,
void * &pkt_base,
size_t pkt_size)
{
pkt = _source.alloc_packet(pkt_size);
pkt_base = _source.packet_content(pkt);
}
void Interface::_send_submit_pkt(Packet_descriptor &pkt,
void * &pkt_base,
size_t pkt_size)
{
Domain &local_domain = _domain();
local_domain.raise_tx_bytes(pkt_size);
if (local_domain.verbose_packets()) {
try {
Size_guard size_guard(pkt_size);
log("[", local_domain, "] snd ",
Ethernet_frame::cast_from(pkt_base, size_guard));
}
catch (Size_guard::Exceeded) { log("[", local_domain, "] snd ?"); }
}
_source.submit_packet(pkt);
}
Interface::Interface(Genode::Entrypoint &ep,
Timer::Connection &timer,
Mac_address const router_mac,
Genode::Allocator &alloc,
Mac_address const mac,
Configuration &config,
Interface_list &interfaces,
Packet_stream_sink &sink,
Packet_stream_source &source,
bool &session_link_state,
Interface_policy &policy)
:
_sink { sink },
_source { source },
_session_link_state { session_link_state },
_sink_ack { ep, *this, &Interface::_ack_avail },
_sink_submit { ep, *this, &Interface::_ready_to_submit },
_source_ack { ep, *this, &Interface::_ready_to_ack },
_source_submit { ep, *this, &Interface::_packet_avail },
_router_mac { router_mac },
_mac { mac },
_config { config },
_policy { policy },
_timer { timer },
_alloc { alloc },
_interfaces { interfaces }
{
_interfaces.insert(this);
}
void Interface::_dismiss_link_log(Link &link,
char const *reason)
{
if (!_config().verbose()) {
return;
}
log("[", link.client().domain(), "] dismiss link client: ", link.client(), " (", reason, ")");
log("[", link.server().domain(), "] dismiss link server: ", link.server(), " (", reason, ")");
}
void Interface::_update_link_check_nat(Link &link,
Domain &new_srv_dom,
L3_protocol prot,
Domain &cln_dom)
{
/* if server domain or its IP config changed, dismiss link */
Domain &old_srv_dom = link.server().domain();
if (old_srv_dom.name() != new_srv_dom.name() ||
old_srv_dom.ip_config() != new_srv_dom.ip_config())
{
_dismiss_link_log(link, "rule targets other domain");
throw Dismiss_link();
}
Pointer<Port_allocator_guard> remote_port_alloc_ptr;
if (link.client().src_ip() == link.server().dst_ip()) {
link.handle_config(cln_dom, new_srv_dom, remote_port_alloc_ptr, _config());
return;
}
try {
if (link.server().dst_ip() != new_srv_dom.ip_config().interface.address) {
_dismiss_link_log(link, "NAT IP");
throw Dismiss_link();
}
Nat_rule &nat = new_srv_dom.nat_rules().find_by_domain(cln_dom);
Port_allocator_guard &remote_port_alloc = nat.port_alloc(prot);
remote_port_alloc.alloc(link.server().dst_port());
remote_port_alloc_ptr = remote_port_alloc;
link.handle_config(cln_dom, new_srv_dom, remote_port_alloc_ptr, _config());
return;
}
catch (Nat_rule_tree::No_match) { _dismiss_link_log(link, "no NAT rule"); }
catch (Port_allocator::Allocation_conflict) { _dismiss_link_log(link, "no NAT-port"); }
catch (Port_allocator_guard::Out_of_indices) { _dismiss_link_log(link, "no NAT-port quota"); }
throw Dismiss_link();
}
void Interface::_update_udp_tcp_links(L3_protocol prot,
Domain &cln_dom)
{
links(prot).for_each([&] (Link &link) {
try {
/* try to find forward rule that matches the server port */
Forward_rule const &rule =
_forward_rules(cln_dom, prot).
find_by_port(link.client().dst_port());
/* if destination IP of forwarding changed, dismiss link */
if (rule.to_ip() != link.server().src_ip()) {
_dismiss_link_log(link, "other forward-rule to");
throw Dismiss_link();
}
/*
* If destination port of forwarding was set and then was
* modified or unset, dismiss link
*/
if (!(link.server().src_port() == link.client().dst_port())) {
if (!(rule.to_port() == link.server().src_port())) {
_dismiss_link_log(link, "other forward-rule to_port");
throw Dismiss_link();
}
}
/*
* If destination port of forwarding was not set and then was
* set, dismiss link
*/
else {
if (!(rule.to_port() == link.server().src_port()) &&
!(rule.to_port() == Port(0)))
{
_dismiss_link_log(link, "new forward-rule to_port");
throw Dismiss_link();
}
}
_update_link_check_nat(link, rule.domain(), prot, cln_dom);
return;
}
catch (Forward_rule_tree::No_match) {
try {
/* try to find transport rule that matches the server IP */
Transport_rule const &transport_rule =
_transport_rules(cln_dom, prot).
longest_prefix_match(link.client().dst_ip());
/* try to find permit rule that matches the server port */
Permit_rule const &permit_rule =
transport_rule.permit_rule(link.client().dst_port());
_update_link_check_nat(link, permit_rule.domain(), prot, cln_dom);
return;
}
catch (Transport_rule_list::No_match) { _dismiss_link_log(link, "no transport/forward rule"); }
catch (Permit_single_rule_tree::No_match) { _dismiss_link_log(link, "no permit rule"); }
catch (Dismiss_link) { }
}
catch (Dismiss_link) { }
_destroy_link(link);
});
}
void Interface::_update_icmp_links(Domain &cln_dom)
{
L3_protocol const prot = L3_protocol::ICMP;
links(prot).for_each([&] (Link &link) {
try {
Ip_rule const &rule = cln_dom.icmp_rules().
longest_prefix_match(link.client().dst_ip());
_update_link_check_nat(link, rule.domain(), prot, cln_dom);
return;
}
catch (Ip_rule_list::No_match) { _dismiss_link_log(link, "no ICMP rule"); }
catch (Dismiss_link) { }
_destroy_link(link);
});
}
void Interface::_update_dhcp_allocations(Domain &old_domain,
Domain &new_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()) {
throw Pointer<Dhcp_server>::Invalid();
}
if (old_dhcp_srv.ip_lease_time().value !=
new_dhcp_srv.ip_lease_time().value)
{
throw Pointer<Dhcp_server>::Invalid();
}
_dhcp_allocations.for_each([&] (Dhcp_allocation &allocation) {
try {
new_dhcp_srv.alloc_ip(allocation.ip());
/* keep DHCP allocation */
if (_config().verbose()) {
log("[", new_domain, "] update DHCP allocation: ",
allocation);
}
return;
}
catch (Dhcp_server::Alloc_ip_failed) {
if (_config().verbose()) {
log("[", new_domain, "] dismiss DHCP allocation: ",
allocation, " (no IP)");
}
}
/* dismiss DHCP allocation */
_dhcp_allocations.remove(allocation);
_destroy_dhcp_allocation(allocation, old_domain);
});
}
catch (Pointer<Dhcp_server>::Invalid) {
/* dismiss all DHCP allocations */
while (Dhcp_allocation *allocation = _dhcp_allocations.first()) {
if (_config().verbose()) {
log("[", new_domain, "] dismiss DHCP allocation: ",
*allocation, " (other/no DHCP server)");
}
_dhcp_allocations.remove(*allocation);
_destroy_dhcp_allocation(*allocation, old_domain);
}
}
}
void Interface::_update_own_arp_waiters(Domain &domain)
{
bool const verbose = _config().verbose();
_own_arp_waiters.for_each([&] (Arp_waiter_list_element &le) {
Arp_waiter &arp_waiter = *le.object();
try {
Domain &dst = _config().domains().find_by_name(arp_waiter.dst().name());
if (dst.ip_config() != arp_waiter.dst().ip_config()) {
if (verbose) {
log("[", domain, "] dismiss ARP waiter: ", arp_waiter,
" (IP config changed)");
}
throw Dismiss_arp_waiter();
}
/* keep ARP waiter */
arp_waiter.handle_config(dst);
if (verbose) {
log("[", domain, "] update ARP waiter: ", arp_waiter);
}
return;
}
/* dismiss ARP waiter */
catch (Domain_tree::No_match) {
if (verbose) {
log("[", domain, "] dismiss ARP waiter: ", arp_waiter,
" (domain disappeared)");
}
}
catch (Dismiss_arp_waiter) { }
cancel_arp_waiting(*_own_arp_waiters.first()->object());
});
}
void Interface::handle_config_1(Configuration &config)
{
/* update config and policy */
_config = config;
_policy.handle_config(config);
Domain_name const &new_domain_name = _policy.determine_domain_name();
try {
/* destroy state objects that are not needed anymore */
Domain &old_domain = domain();
_destroy_dissolved_links<Icmp_link>(_dissolved_icmp_links, _alloc);
_destroy_dissolved_links<Udp_link> (_dissolved_udp_links, _alloc);
_destroy_dissolved_links<Tcp_link> (_dissolved_tcp_links, _alloc);
_destroy_released_dhcp_allocations(old_domain);
/* do not consider to reuse IP config if the domains differ */
if (old_domain.name() != new_domain_name) {
return; }
/* interface stays with its domain, so, try to reuse IP config */
Domain &new_domain = config.domains().find_by_name(new_domain_name);
new_domain.try_reuse_ip_config(old_domain);
return;
}
catch (Domain_tree::No_match) { }
catch (Pointer<Domain>::Invalid) { }
}
void Interface::_failed_to_send_packet_link()
{
if (_config().verbose()) {
log("[", _domain(), "] failed to send packet (link down)"); }
}
void Interface::_failed_to_send_packet_alloc()
{
if (_config().verbose()) {
log("[", _domain(), "] failed to send packet (packet alloc failed)"); }
}
void Interface::handle_config_2()
{
Domain_name const &new_domain_name = _policy.determine_domain_name();
try {
/* if the domains differ, detach completely from the domain */
Domain &old_domain = domain();
Domain &new_domain = _config().domains().find_by_name(new_domain_name);
if (old_domain.name() != new_domain_name) {
_detach_from_domain();
_attach_to_domain_raw(new_domain);
return;
}
/* move to new domain object without considering any state objects */
_detach_from_domain_raw();
_attach_to_domain_raw(new_domain);
/* remember that the interface stays attached to the same domain */
_update_domain.construct(old_domain, new_domain);
return;
}
catch (Domain_tree::No_match) {
/* the interface no longer has a domain */
_detach_from_domain();
}
catch (Pointer<Domain>::Invalid) {
/* the interface had no domain but now it may get one */
try { _attach_to_domain_raw(_config().domains().find_by_name(new_domain_name)); }
catch (Domain_tree::No_match) { }
}
}
void Interface::handle_config_3()
{
try {
/*
* Update the domain object only if handle_config_2 determined that
* the interface stays attached to the same domain. Otherwise the
* interface already got detached from its old domain and there is
* nothing to update.
*/
Update_domain &update_domain = *_update_domain;
Domain &old_domain = update_domain.old_domain;
Domain &new_domain = update_domain.new_domain;
_update_domain.destruct();
/* if the IP configs differ, detach completely from the IP config */
if (old_domain.ip_config() != new_domain.ip_config()) {
detach_from_ip_config();
attach_to_domain_finish();
return;
}
/* if there was/is no IP config, there is nothing more to update */
if (!new_domain.ip_config().valid) {
return;
}
/* update state objects */
_update_udp_tcp_links(L3_protocol::TCP, new_domain);
_update_udp_tcp_links(L3_protocol::UDP, new_domain);
_update_icmp_links(new_domain);
_update_dhcp_allocations(old_domain, new_domain);
_update_own_arp_waiters(new_domain);
}
catch (Constructible<Update_domain>::Deref_unconstructed_object) {
/* if the interface moved to another domain, finish the operation */
try { attach_to_domain_finish(); }
catch (Pointer<Domain>::Invalid) { }
}
}
void Interface::_ack_packet(Packet_descriptor const &pkt)
{
if (!_sink.ready_to_ack()) {
if (_config().verbose()) {
log("[", _domain(), "] leak packet (sink not ready to "
"acknowledge)");
}
return;
}
_sink.acknowledge_packet(pkt);
}
void Interface::cancel_arp_waiting(Arp_waiter &waiter)
{
try {
Domain &domain = _domain();
if (domain.verbose_packet_drop()) {
log("[", domain, "] drop packet (ARP got cancelled)"); }
}
catch (Pointer<Domain>::Invalid) {
if (_config().verbose_packet_drop()) {
log("[?] drop packet (ARP got cancelled)"); }
}
_ack_packet(waiter.packet());
destroy(_alloc, &waiter);
}
Interface::~Interface()
{
_detach_from_domain();
_interfaces.remove(this);
}
void Interface::report(Genode::Xml_generator &xml)
{
xml.node("interface", [&] () {
bool empty { true };
xml.attribute("label", _policy.label());
if (_config().report().link_state()) {
xml.attribute("link_state", link_state());
empty = false;
}
if (_config().report().stats()) {
try {
_policy.report(xml);
empty = false;
}
catch (Report::Empty) { }
try { xml.node("tcp-links", [&] () { _tcp_stats.report(xml); }); empty = false; } catch (Report::Empty) { }
try { xml.node("udp-links", [&] () { _udp_stats.report(xml); }); empty = false; } catch (Report::Empty) { }
try { xml.node("icmp-links", [&] () { _icmp_stats.report(xml); }); empty = false; } catch (Report::Empty) { }
try { xml.node("arp-waiters", [&] () { _arp_stats.report(xml); }); empty = false; } catch (Report::Empty) { }
try { xml.node("dhcp-allocations", [&] () { _dhcp_stats.report(xml); }); empty = false; } catch (Report::Empty) { }
}
if (empty) { throw Report::Empty(); }
});
}
/**************************
** Interface_link_stats **
**************************/
Interface_link_stats::~Interface_link_stats()
{
if (opening ||
open ||
closing ||
closed)
{
error("closing interface has dangling links");
}
}
/****************************
** Interface_object_stats **
****************************/
Interface_object_stats::~Interface_object_stats()
{
if (alive) {
error("closing interface has dangling links"); }
}