nic_router: incremental L4 checksum updates

The checksums for forwarded/routed UDP, TCP and ICMP, used to be always
re-calculated from scratch in the NIC router although the router changes only
a few packet fields. This commit replaces the old approach whereever sensible
with an algorithm for incremental checksum updates suggested in RFC 1071.
The goal is to improve router performance.

Ref #4555
This commit is contained in:
Martin Stein 2022-07-17 13:54:41 +02:00 committed by Christian Helmuth
parent b6d15fa3ab
commit 9a37ccfe29
12 changed files with 214 additions and 79 deletions

View File

@ -114,6 +114,8 @@ class Net::Icmp_packet
void update_checksum(Genode::size_t data_sz);
void update_checksum(Internet_checksum_diff const &icd);
bool checksum_error(Genode::size_t data_sz) const;
@ -157,6 +159,9 @@ class Net::Icmp_packet
void query_id(Genode::uint16_t v) { _rest_of_header_u16[0] = host_to_big_endian(v); }
void query_seq(Genode::uint16_t v) { _rest_of_header_u16[1] = host_to_big_endian(v); }
void type_and_code(Type t, Code c, Internet_checksum_diff &icd);
void query_id(Genode::uint16_t v, Internet_checksum_diff &icd);
/*********
** log **

View File

@ -76,6 +76,11 @@ namespace Net {
Packed_uint16 const *old_data_ptr,
Genode::size_t data_sz);
/**
* Update this modifier by adding up another modifier
*/
void add_up_diff(Internet_checksum_diff const &icd);
/**
* Return the given checksum with this modifier applied
*/

View File

@ -98,6 +98,9 @@ class Net::Ipv4_packet
void update_checksum(Internet_checksum_diff const &icd);
void update_checksum(Internet_checksum_diff const &icd,
Internet_checksum_diff &caused_icd);
bool checksum_error() const;
private:

View File

@ -72,6 +72,8 @@ class Net::Tcp_packet
Ipv4_address ip_dst,
size_t tcp_size);
void update_checksum(Internet_checksum_diff const &icd);
/***************
** Accessors **
@ -99,6 +101,9 @@ class Net::Tcp_packet
void src_port(Port p) { _src_port = host_to_big_endian(p.value); }
void dst_port(Port p) { _dst_port = host_to_big_endian(p.value); }
void src_port(Port p, Internet_checksum_diff &icd);
void dst_port(Port p, Internet_checksum_diff &icd);
/*********
** log **

View File

@ -84,6 +84,8 @@ class Net::Udp_packet
void update_checksum(Ipv4_address ip_src,
Ipv4_address ip_dst);
void update_checksum(Internet_checksum_diff const &icd);
bool checksum_error(Ipv4_address ip_src,
Ipv4_address ip_dst) const;
@ -103,6 +105,9 @@ class Net::Udp_packet
void src_port_big_endian(Genode::uint16_t v) { _src_port = v; }
void dst_port_big_endian(Genode::uint16_t v) { _dst_port = v; }
void src_port(Port p, Internet_checksum_diff &icd);
void dst_port(Port p, Internet_checksum_diff &icd);
/*********
** log **

View File

@ -34,7 +34,33 @@ void Icmp_packet::update_checksum(size_t data_sz)
}
void Icmp_packet::update_checksum(Internet_checksum_diff const &icd)
{
_checksum = icd.apply_to(_checksum);
}
bool Icmp_packet::checksum_error(size_t data_sz) const
{
return internet_checksum((Packed_uint16 *)this, sizeof(Icmp_packet) + data_sz);
}
void Icmp_packet::query_id(uint16_t v, Internet_checksum_diff &icd)
{
uint16_t const v_be { host_to_big_endian(v) };
icd.add_up_diff((
Packed_uint16 *)&v_be, (Packed_uint16 *)&_rest_of_header_u16[0], 2);
_rest_of_header_u16[0] = v_be;
}
void Icmp_packet::type_and_code(Type t, Code c, Internet_checksum_diff &icd)
{
uint16_t const old_type_and_code { *(uint16_t*)this };
type(t);
code(c);
icd.add_up_diff(
(Packed_uint16 *)&_type, (Packed_uint16 *)&old_type_and_code, 2);
}

View File

@ -96,6 +96,12 @@ uint16_t Net::internet_checksum_pseudo_ip(Packed_uint16 const *data_ptr,
** Internet_checksum_diff **
****************************/
void Internet_checksum_diff::add_up_diff(Internet_checksum_diff const &icd)
{
_value += icd._value;
}
void Internet_checksum_diff::add_up_diff(Packed_uint16 const *new_data_ptr,
Packed_uint16 const *old_data_ptr,
size_t data_sz)

View File

@ -176,3 +176,15 @@ void Ipv4_packet::update_checksum(Internet_checksum_diff const &icd)
{
_checksum = icd.apply_to(_checksum);
}
void
Ipv4_packet::update_checksum(Internet_checksum_diff const &icd,
Internet_checksum_diff &caused_icd)
{
uint16_t const new_checksum { icd.apply_to(_checksum) };
caused_icd.add_up_diff(
(Packed_uint16 *)&new_checksum, (Packed_uint16 *)&_checksum, 2);
_checksum = new_checksum;
}

View File

@ -47,3 +47,25 @@ void Net::Tcp_packet::update_checksum(Ipv4_address ip_src,
host_to_big_endian((uint16_t)tcp_size),
Ipv4_packet::Protocol::TCP, ip_src, ip_dst);
}
void Net::Tcp_packet::update_checksum(Internet_checksum_diff const &icd)
{
_checksum = icd.apply_to(_checksum);
}
void Net::Tcp_packet::src_port(Port p, Internet_checksum_diff &icd)
{
uint16_t const p_be { host_to_big_endian(p.value) };
icd.add_up_diff((Packed_uint16 *)&p_be, (Packed_uint16 *)&_src_port, 2);
_src_port = p_be;
}
void Net::Tcp_packet::dst_port(Port p, Internet_checksum_diff &icd)
{
uint16_t const p_be { host_to_big_endian(p.value) };
icd.add_up_diff((Packed_uint16 *)&p_be, (Packed_uint16 *)&_dst_port, 2);
_dst_port = p_be;
}

View File

@ -41,9 +41,31 @@ void Net::Udp_packet::update_checksum(Ipv4_address ip_src,
}
void Net::Udp_packet::update_checksum(Internet_checksum_diff const &icd)
{
_checksum = icd.apply_to(_checksum);
}
bool Net::Udp_packet::checksum_error(Ipv4_address ip_src,
Ipv4_address ip_dst) const
{
return internet_checksum_pseudo_ip((Packed_uint16 *)this, length(), _length,
Ipv4_packet::Protocol::UDP, ip_src, ip_dst);
}
void Net::Udp_packet::src_port(Port p, Internet_checksum_diff &icd)
{
uint16_t const p_be { host_to_big_endian(p.value) };
icd.add_up_diff((Packed_uint16 *)&p_be, (Packed_uint16 *)&_src_port, 2);
_src_port = p_be;
}
void Net::Udp_packet::dst_port(Port p, Internet_checksum_diff &icd)
{
uint16_t const p_be { host_to_big_endian(p.value) };
icd.add_up_diff((Packed_uint16 *)&p_be, (Packed_uint16 *)&_dst_port, 2);
_dst_port = p_be;
}

View File

@ -155,25 +155,21 @@ static void _link_packet(L3_protocol const prot,
}
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)
static void _l4_update_checksum(L3_protocol const prot,
void *const prot_base,
Internet_checksum_diff const &prot_icd)
{
switch (prot) {
case L3_protocol::TCP:
((Tcp_packet *)prot_base)->update_checksum(src, dst, prot_size);
((Tcp_packet *)prot_base)->update_checksum(prot_icd);
return;
case L3_protocol::UDP:
((Udp_packet *)prot_base)->update_checksum(src, dst);
((Udp_packet *)prot_base)->update_checksum(prot_icd);
return;
case L3_protocol::ICMP:
{
Icmp_packet &icmp = *(Icmp_packet *)prot_base;
icmp.update_checksum(ip_size - sizeof(Ipv4_packet) -
sizeof(Icmp_packet));
icmp.update_checksum(prot_icd);
return;
}
default: throw Interface::Bad_transport_protocol(); }
@ -190,14 +186,15 @@ static Port _dst_port(L3_protocol const prot, void *const prot_base)
}
static void _dst_port(L3_protocol const prot,
void *const prot_base,
Port const port)
static void _dst_port(L3_protocol const prot,
void *const prot_base,
Internet_checksum_diff &prot_icd,
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;
case L3_protocol::TCP: (*(Tcp_packet *)prot_base).dst_port(port, prot_icd); return;
case L3_protocol::UDP: (*(Udp_packet *)prot_base).dst_port(port, prot_icd); return;
case L3_protocol::ICMP: (*(Icmp_packet *)prot_base).query_id(port.value, prot_icd); return;
default: throw Interface::Bad_transport_protocol(); }
}
@ -212,14 +209,15 @@ static Port _src_port(L3_protocol const prot, void *const prot_base)
}
static void _src_port(L3_protocol const prot,
void *const prot_base,
Port const port)
static void _src_port(L3_protocol const prot,
void *const prot_base,
Internet_checksum_diff &prot_icd,
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;
case L3_protocol::TCP: ((Tcp_packet *)prot_base)->src_port(port, prot_icd); return;
case L3_protocol::UDP: ((Udp_packet *)prot_base)->src_port(port, prot_icd); return;
case L3_protocol::ICMP: ((Icmp_packet *)prot_base)->query_id(port.value, prot_icd); return;
default: throw Interface::Bad_transport_protocol(); }
}
@ -301,11 +299,9 @@ void Interface::_pass_prot_to_domain(Domain &domain,
Internet_checksum_diff const &ip_icd,
L3_protocol const prot,
void *const prot_base,
size_t const prot_size)
Internet_checksum_diff const &prot_icd)
{
_update_checksum(
prot, prot_base, prot_size, ip.src(), ip.dst(), ip.total_length());
_l4_update_checksum(prot, prot_base, prot_icd);
ip.update_checksum(ip_icd);
domain.interfaces().for_each([&] (Interface &interface)
{
@ -584,7 +580,7 @@ void Interface::_nat_link_and_pass(Ethernet_frame &eth,
Internet_checksum_diff &ip_icd,
L3_protocol const prot,
void *const prot_base,
size_t const prot_size,
Internet_checksum_diff &prot_icd,
Link_side_id const &local_id,
Domain &local_domain,
Domain &remote_domain)
@ -598,8 +594,13 @@ void Interface::_nat_link_and_pass(Ethernet_frame &eth,
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, ip_icd);
_src_port(prot, prot_base, prot_icd, nat.port_alloc(prot).alloc());
Internet_checksum_diff icd { };
ip.src(remote_domain.ip_config().interface().address, icd);
ip_icd.add_up_diff(icd);
if (prot == L3_protocol::TCP || prot == L3_protocol::UDP) {
prot_icd.add_up_diff(icd);
}
remote_port_alloc = nat.port_alloc(prot);
},
[&] /* no_match */ () { }
@ -609,7 +610,7 @@ void Interface::_nat_link_and_pass(Ethernet_frame &eth,
_new_link(prot, local_id, remote_port_alloc, remote_domain, remote_id);
_pass_prot_to_domain(
remote_domain, eth, size_guard, ip, ip_icd, prot, prot_base,
prot_size);
prot_icd);
} catch (Port_allocator_guard::Out_of_indices) {
switch (prot) {
@ -945,11 +946,11 @@ void Interface::handle_interface_link_state()
}
void Interface::_send_icmp_echo_reply(Ethernet_frame &eth,
Ipv4_packet &ip,
Icmp_packet &icmp,
size_t icmp_sz,
Size_guard &size_guard)
void Interface::_send_icmp_echo_reply(Ethernet_frame &eth,
Ipv4_packet &ip,
Icmp_packet &icmp,
Internet_checksum_diff &icmp_icd,
Size_guard &size_guard)
{
/* adapt Ethernet header */
Mac_address const eth_src = eth.src();
@ -962,8 +963,9 @@ void Interface::_send_icmp_echo_reply(Ethernet_frame &eth,
ip.dst(ip_src);
/* adapt ICMP header */
icmp.type(Icmp_packet::Type::ECHO_REPLY);
icmp.code(Icmp_packet::Code::ECHO_REPLY);
icmp.type_and_code(Icmp_packet::Type::ECHO_REPLY,
Icmp_packet::Code::ECHO_REPLY,
icmp_icd);
/*
* Update checksums and send
@ -971,7 +973,7 @@ void Interface::_send_icmp_echo_reply(Ethernet_frame &eth,
* Skip updating the IPv4 checksum because we have only swapped SRC and
* DST and these changes cancel each other out in checksum calculation.
*/
icmp.update_checksum(icmp_sz - sizeof(Icmp_packet));
icmp.update_checksum(icmp_icd);
send(eth, size_guard);
}
@ -983,7 +985,7 @@ void Interface::_handle_icmp_query(Ethernet_frame &eth,
Packet_descriptor const &pkt,
L3_protocol prot,
void *prot_base,
size_t prot_size,
Internet_checksum_diff &prot_icd,
Domain &local_domain)
{
Link_side_id const local_id = { ip.src(), _src_port(prot, prot_base),
@ -1004,13 +1006,18 @@ void Interface::_handle_icmp_query(Ethernet_frame &eth,
" link: ", link);
}
_adapt_eth(eth, remote_side.src_ip(), pkt, remote_domain);
ip.src(remote_side.dst_ip(), ip_icd);
ip.dst(remote_side.src_ip(), ip_icd);
_src_port(prot, prot_base, remote_side.dst_port());
_dst_port(prot, prot_base, remote_side.src_port());
Internet_checksum_diff icd { };
ip.src(remote_side.dst_ip(), icd);
ip.dst(remote_side.src_ip(), icd);
ip_icd.add_up_diff(icd);
if (prot == L3_protocol::TCP || prot == L3_protocol::UDP) {
prot_icd.add_up_diff(icd);
}
_src_port(prot, prot_base, prot_icd, remote_side.dst_port());
_dst_port(prot, prot_base, prot_icd, remote_side.src_port());
_pass_prot_to_domain(
remote_domain, eth, size_guard, ip, ip_icd, prot,
prot_base, prot_size);
prot_base, prot_icd);
_link_packet(prot, prot_base, link, client);
done = true;
@ -1032,7 +1039,7 @@ void Interface::_handle_icmp_query(Ethernet_frame &eth,
Domain &remote_domain = rule.domain();
_adapt_eth(eth, local_id.dst_ip, pkt, remote_domain);
_nat_link_and_pass(eth, size_guard, ip, ip_icd, prot, prot_base,
prot_size, local_id, local_domain,
prot_icd, local_id, local_domain,
remote_domain);
done = true;
@ -1054,7 +1061,7 @@ void Interface::_handle_icmp_error(Ethernet_frame &eth,
Packet_descriptor const &pkt,
Domain &local_domain,
Icmp_packet &icmp,
size_t icmp_sz)
Internet_checksum_diff &icmp_icd)
{
Ipv4_packet &embed_ip { icmp.data<Ipv4_packet>(size_guard) };
Internet_checksum_diff embed_ip_icd { };
@ -1092,14 +1099,18 @@ void Interface::_handle_icmp_error(Ethernet_frame &eth,
ip.dst(remote_side.src_ip(), ip_icd);
/* adapt source and destination of embedded IP and transport packet */
embed_ip.src(remote_side.src_ip(), embed_ip_icd);
embed_ip.dst(remote_side.dst_ip(), embed_ip_icd);
_src_port(embed_prot, embed_prot_base, remote_side.src_port());
_dst_port(embed_prot, embed_prot_base, remote_side.dst_port());
Internet_checksum_diff icd { };
embed_ip.src(remote_side.src_ip(), icd);
embed_ip.dst(remote_side.dst_ip(), icd);
embed_ip_icd.add_up_diff(icd);
_src_port(embed_prot, embed_prot_base, icd, remote_side.src_port());
_dst_port(embed_prot, embed_prot_base, icd, remote_side.dst_port());
/* update checksum of both IP headers and the ICMP header */
embed_ip.update_checksum(embed_ip_icd);
icmp.update_checksum(icmp_sz - sizeof(Icmp_packet));
embed_ip.update_checksum(embed_ip_icd, icd);
icmp_icd.add_up_diff(icd);
icmp.update_checksum(icmp_icd);
ip.update_checksum(ip_icd);
/* send adapted packet to all interfaces of remote domain */
@ -1126,7 +1137,7 @@ void Interface::_handle_icmp(Ethernet_frame &eth,
Packet_descriptor const &pkt,
L3_protocol prot,
void *prot_base,
size_t prot_size,
Internet_checksum_diff &prot_icd,
Domain &local_domain,
Ipv4_address_prefix const &local_intf)
{
@ -1143,14 +1154,14 @@ void Interface::_handle_icmp(Ethernet_frame &eth,
if(_config().verbose()) {
log("[", local_domain, "] act as ICMP Echo server"); }
_send_icmp_echo_reply(eth, ip, icmp, prot_size, size_guard);
_send_icmp_echo_reply(eth, ip, icmp, prot_icd, 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, ip_icd, pkt, prot, prot_base, prot_size, local_domain); break;
case Icmp_packet::Type::DST_UNREACHABLE: _handle_icmp_error(eth, size_guard, ip, ip_icd, pkt, local_domain, icmp, prot_size); break;
case Icmp_packet::Type::ECHO_REQUEST: _handle_icmp_query(eth, size_guard, ip, ip_icd, pkt, prot, prot_base, prot_icd, local_domain); break;
case Icmp_packet::Type::DST_UNREACHABLE: _handle_icmp_error(eth, size_guard, ip, ip_icd, pkt, local_domain, icmp, prot_icd); break;
default: Drop_packet("unhandled type in ICMP"); }
}
@ -1190,9 +1201,9 @@ void Interface::_handle_ip(Ethernet_frame &eth,
/* try to route via transport layer rules */
bool done { false };
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);
L3_protocol const prot { ip.protocol() };
void *const prot_base { _prot_base(prot, size_guard, ip) };
Internet_checksum_diff prot_icd { };
/* try handling DHCP requests before trying any routing */
if (prot == L3_protocol::UDP) {
@ -1235,7 +1246,7 @@ void Interface::_handle_ip(Ethernet_frame &eth,
}
else if (prot == L3_protocol::ICMP) {
_handle_icmp(eth, size_guard, ip, ip_icd, pkt, prot, prot_base,
prot_size, local_domain, local_intf);
prot_icd, local_domain, local_intf);
return;
}
@ -1258,13 +1269,20 @@ void Interface::_handle_ip(Ethernet_frame &eth,
" link: ", link);
}
_adapt_eth(eth, remote_side.src_ip(), pkt, remote_domain);
ip.src(remote_side.dst_ip(), ip_icd);
ip.dst(remote_side.src_ip(), ip_icd);
_src_port(prot, prot_base, remote_side.dst_port());
_dst_port(prot, prot_base, remote_side.src_port());
Internet_checksum_diff icd { };
ip.src(remote_side.dst_ip(), icd);
ip.dst(remote_side.src_ip(), icd);
ip_icd.add_up_diff(icd);
if (prot == L3_protocol::TCP || prot == L3_protocol::UDP) {
prot_icd.add_up_diff(icd);
}
_src_port(prot, prot_base, prot_icd, remote_side.dst_port());
_dst_port(prot, prot_base, prot_icd, remote_side.src_port());
_pass_prot_to_domain(
remote_domain, eth, size_guard, ip, ip_icd, prot,
prot_base, prot_size);
prot_base, prot_icd);
_link_packet(prot, prot_base, link, client);
done = true;
@ -1288,13 +1306,19 @@ void Interface::_handle_ip(Ethernet_frame &eth,
}
Domain &remote_domain = rule.domain();
_adapt_eth(eth, rule.to_ip(), pkt, remote_domain);
ip.dst(rule.to_ip(), ip_icd);
Internet_checksum_diff icd { };
ip.dst(rule.to_ip(), icd);
ip_icd.add_up_diff(icd);
if (prot == L3_protocol::TCP || prot == L3_protocol::UDP) {
prot_icd.add_up_diff(icd);
}
if (!(rule.to_port() == Port(0))) {
_dst_port(prot, prot_base, rule.to_port());
_dst_port(prot, prot_base, prot_icd, rule.to_port());
}
_nat_link_and_pass(
eth, size_guard, ip, ip_icd, prot, prot_base,
prot_size, local_id, local_domain, remote_domain);
prot_icd, local_id, local_domain, remote_domain);
done = true;
},
@ -1319,7 +1343,7 @@ void Interface::_handle_ip(Ethernet_frame &eth,
Domain &remote_domain = permit_rule.domain();
_adapt_eth(eth, local_id.dst_ip, pkt, remote_domain);
_nat_link_and_pass(
eth, size_guard, ip, ip_icd, prot, prot_base, prot_size,
eth, size_guard, ip, ip_icd, prot, prot_base, prot_icd,
local_id, local_domain, remote_domain);
done = true;

View File

@ -189,11 +189,11 @@ class Net::Interface : private Interface_list::Element
Genode::uint32_t xid,
Ipv4_address_prefix const &local_intf);
void _send_icmp_echo_reply(Ethernet_frame &eth,
Ipv4_packet &ip,
Icmp_packet &icmp,
Genode::size_t icmp_sz,
Size_guard &size_guard);
void _send_icmp_echo_reply(Ethernet_frame &eth,
Ipv4_packet &ip,
Icmp_packet &icmp,
Internet_checksum_diff &icmp_icd,
Size_guard &size_guard);
Forward_rule_tree &_forward_rules(Domain &local_domain,
L3_protocol const prot) const;
@ -234,7 +234,7 @@ class Net::Interface : private Interface_list::Element
Packet_descriptor const &pkt,
L3_protocol prot,
void *prot_base,
Genode::size_t prot_size,
Internet_checksum_diff &prot_icd,
Domain &local_domain);
void _handle_icmp_error(Ethernet_frame &eth,
@ -244,7 +244,7 @@ class Net::Interface : private Interface_list::Element
Packet_descriptor const &pkt,
Domain &local_domain,
Icmp_packet &icmp,
Genode::size_t icmp_sz);
Internet_checksum_diff &icmp_icd);
void _handle_icmp(Ethernet_frame &eth,
Size_guard &size_guard,
@ -253,7 +253,7 @@ class Net::Interface : private Interface_list::Element
Packet_descriptor const &pkt,
L3_protocol prot,
void *prot_base,
Genode::size_t prot_size,
Internet_checksum_diff &prot_icd,
Domain &local_domain,
Ipv4_address_prefix const &local_intf);
@ -268,7 +268,7 @@ class Net::Interface : private Interface_list::Element
Internet_checksum_diff &ip_icd,
L3_protocol const prot,
void *const prot_base,
Genode::size_t const prot_size,
Internet_checksum_diff &prot_icd,
Link_side_id const &local_id,
Domain &local_domain,
Domain &remote_domain);
@ -287,7 +287,7 @@ class Net::Interface : private Interface_list::Element
Internet_checksum_diff const &ip_icd,
L3_protocol const prot,
void *const prot_base,
Genode::size_t const prot_size);
Internet_checksum_diff const &prot_icd);
void _handle_pkt();