net: use generic internet checksum

This reduces the redundant implementations of checksum calculation to
one generic implementation, makes the checksum interface conform over
all protocols, and brings performance optimizations. For instance,
the checksum is now calculated directly in big endian which saves us
most of the previously done byte-re-ordering.

Issue #2775
This commit is contained in:
Martin Stein 2018-04-17 00:35:16 +02:00 committed by Christian Helmuth
parent 836df90f6b
commit 6b55790e73
14 changed files with 183 additions and 165 deletions

View File

@ -57,7 +57,9 @@ class Net::Icmp_packet
ECHO_REPLY = 0,
};
Genode::uint16_t calc_checksum(Genode::size_t data_sz) const;
void update_checksum(Genode::size_t data_sz);
bool checksum_error(Genode::size_t data_sz) const;
/***************

View File

@ -0,0 +1,35 @@
/*
* \brief Computing the Internet Checksum (conforms to RFC 1071)
* \author Martin Stein
* \date 2018-03-23
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _NET__INTERNET_CHECKSUM_H_
#define _NET__INTERNET_CHECKSUM_H_
/* Genode includes */
#include <net/ipv4.h>
#include <base/stdint.h>
namespace Net {
Genode::uint16_t internet_checksum(Genode::uint16_t const *addr,
Genode::size_t size,
Genode::addr_t init_sum = 0);
Genode::uint16_t internet_checksum_pseudo_ip(Genode::uint16_t const *addr,
Genode::size_t size,
Genode::uint16_t size_be,
Ipv4_packet::Protocol ip_prot,
Ipv4_address &ip_src,
Ipv4_address &ip_dst);
}
#endif /* _NET__INTERNET_CHECKSUM_H_ */

View File

@ -90,7 +90,9 @@ class Net::Ipv4_packet
static Ipv4_address ip_from_string(const char *ip);
static Genode::uint16_t calculate_checksum(Ipv4_packet const &packet);
void update_checksum();
bool checksum_error() const;
private:

View File

@ -70,6 +70,11 @@ class Net::Tcp_packet
public:
void update_checksum(Ipv4_address ip_src,
Ipv4_address ip_dst,
size_t tcp_size);
/***************
** Accessors **
***************/
@ -97,53 +102,6 @@ class Net::Tcp_packet
void dst_port(Port p) { _dst_port = host_to_big_endian(p.value); }
/**
* TCP checksum is calculated over the tcp datagram + an IPv4
* pseudo header.
*
* IPv4 pseudo header:
*
* --------------------------------------------------------------
* | src-ipaddr | dst-ipaddr | zero-field | prot.-id | tcp-length |
* | 4 bytes | 4 bytes | 1 byte | 1 byte | 2 bytes |
* --------------------------------------------------------------
*/
void update_checksum(Ipv4_address ip_src,
Ipv4_address ip_dst,
size_t tcp_size)
{
/* have to reset the checksum field for calculation */
_checksum = 0;
/* sum up pseudo header */
uint32_t sum = 0;
for (size_t i = 0; i < Ipv4_packet::ADDR_LEN; i += sizeof(uint16_t)) {
uint16_t s = ip_src.addr[i] << 8 | ip_src.addr[i + 1];
uint16_t d = ip_dst.addr[i] << 8 | ip_dst.addr[i + 1];
sum += s + d;
}
uint8_t prot[] = { 0, (uint8_t)Ipv4_packet::Protocol::TCP };
sum += host_to_big_endian(*(uint16_t *)&prot) + tcp_size;
/* sum up TCP packet itself */
size_t max = (tcp_size & 1) ? (tcp_size - 1) : tcp_size;
uint16_t * tcp = (uint16_t *)this;
for (size_t i = 0; i < max; i = i + sizeof(*tcp)) {
sum += host_to_big_endian(*tcp++); }
/* if TCP size is odd, append a zero byte */
if (tcp_size & 1) {
uint8_t last[] = { *((uint8_t *)this + (tcp_size - 1)), 0 };
sum += host_to_big_endian(*(uint16_t *)&last);
}
/* keep the last 16 bits of the 32 bit sum and add the carries */
while (sum >> 16) { sum = (sum & 0xffff) + (sum >> 16); }
/* one's complement of sum */
_checksum = host_to_big_endian((uint16_t)~sum);
}
/*********
** log **
*********/

View File

@ -76,6 +76,9 @@ class Net::Udp_packet
return *Genode::construct_at<T>(_data);
}
void update_checksum(Ipv4_address ip_src,
Ipv4_address ip_dst);
/***************
** Accessors **
@ -91,66 +94,6 @@ class Net::Udp_packet
void dst_port(Port p) { _dst_port = host_to_big_endian(p.value); }
/***************************
** Convenience functions **
***************************/
/**
* UDP checksum is calculated over the udp datagram + an IPv4
* pseudo header.
*
* IPv4 pseudo header:
*
* --------------------------------------------------------------
* | src-ipaddr | dst-ipaddr | zero-field | prot.-id | udp-length |
* | 4 bytes | 4 bytes | 1 byte | 1 byte | 2 bytes |
* --------------------------------------------------------------
*/
void update_checksum(Ipv4_address src,
Ipv4_address dst)
{
/* have to reset the checksum field for calculation */
_checksum = 0;
/*
* sum up pseudo header
*/
Genode::uint32_t sum = 0;
for (unsigned i = 0; i < Ipv4_packet::ADDR_LEN; i=i+2) {
Genode::uint16_t s = src.addr[i] << 8 | src.addr[i + 1];
Genode::uint16_t d = dst.addr[i] << 8 | dst.addr[i + 1];
sum += s + d;
}
Genode::uint8_t prot[] = { 0, (Genode::uint8_t)Ipv4_packet::Protocol::UDP };
sum += host_to_big_endian(*(Genode::uint16_t*)&prot) + length();
/*
* sum up udp packet itself
*/
unsigned max = (length() & 1) ? (length() - 1) : length();
Genode::uint16_t *udp = (Genode::uint16_t*) this;
for (unsigned i = 0; i < max; i=i+2)
sum += host_to_big_endian(*udp++);
/* if udp length is odd, append a zero byte */
if (length() & 1) {
Genode::uint8_t last[] =
{ *((Genode::uint8_t*)this + (length()-1)), 0 };
sum += host_to_big_endian(*(Genode::uint16_t*)&last);
}
/*
* keep only the last 16 bits of the 32 bit calculated sum
* and add the carries
*/
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
/* one's complement of sum */
_checksum = host_to_big_endian((Genode::uint16_t) ~sum);
}
/*********
** log **
*********/

View File

@ -1,4 +1,4 @@
SRC_CC += ethernet.cc ipv4.cc dhcp.cc arp.cc udp.cc tcp.cc mac_address.cc
SRC_CC += icmp.cc
SRC_CC += icmp.cc internet_checksum.cc
vpath %.cc $(REP_DIR)/src/lib/net

View File

@ -209,7 +209,7 @@ void Main::_handle_ip(Ethernet_frame &eth,
return;
}
/* drop packet if IP checksum is invalid */
if (Ipv4_packet::calculate_checksum(ip) != ip.checksum()) {
if (ip.checksum_error()) {
if (_verbose) {
log("bad IP checksum"); }
return;
@ -293,7 +293,7 @@ void Main::_handle_icmp_dst_unreachbl(Ipv4_packet &ip,
{
/* drop packet if embedded IP checksum is invalid */
Ipv4_packet &embed_ip = icmp.data<Ipv4_packet>(icmp_data_sz);
if (Ipv4_packet::calculate_checksum(embed_ip) != embed_ip.checksum()) {
if (embed_ip.checksum_error()) {
if (_verbose) {
log("bad IP checksum in payload of ICMP error"); }
return;
@ -330,7 +330,7 @@ void Main::_handle_icmp(Ipv4_packet &ip,
size_t const icmp_sz = ip_size - sizeof(Ipv4_packet);
Icmp_packet &icmp = ip.data<Icmp_packet>(icmp_sz);
size_t const icmp_data_sz = icmp_sz - sizeof(Icmp_packet);
if (icmp.calc_checksum(icmp_data_sz) != icmp.checksum()) {
if (icmp.checksum_error(icmp_data_sz)) {
if (_verbose) {
log("bad ICMP checksum"); }
return;
@ -509,9 +509,9 @@ void Main::_send_ping(Duration)
chr = chr < 'z' ? chr + 1 : 'a';
}
/* fill in header values that require the packet to be complete */
icmp.checksum(icmp.calc_checksum(_icmp_data_sz));
icmp.update_checksum(_icmp_data_sz);
ip.total_length(size.curr() - ip_off);
ip.checksum(Ipv4_packet::calculate_checksum(ip));
ip.update_checksum();
});
_send_time = _timer.curr_time().trunc_to_plain_us();
}

View File

@ -12,6 +12,7 @@
*/
/* Genode includes */
#include <net/internet_checksum.h>
#include <net/icmp.h>
#include <base/output.h>
@ -26,29 +27,14 @@ void Net::Icmp_packet::print(Output &output) const
}
uint16_t Icmp_packet::calc_checksum(size_t data_sz) const
void Icmp_packet::update_checksum(size_t data_sz)
{
/* do not sum-up checksum itself */
register long sum = _type + _code;
addr_t addr = (addr_t)&_rest_of_header_u32;
size_t count = sizeof(Icmp_packet) + data_sz - sizeof(_type) -
sizeof(_code) - sizeof(_checksum);
/* sum-up rest of header and data */
while (count > 1) {
sum += *(uint16_t *)addr;
addr += sizeof(uint16_t);
count -= sizeof(uint16_t);
}
/* add left-over byte, if any */
if (count) {
sum += *(uint8_t *)addr;
}
/* fold 32-bit sum to 16 bits */
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
/* write to header */
uint16_t sum16 = ~sum;
return host_to_big_endian(sum16);
_checksum = 0;
_checksum = internet_checksum((uint16_t *)this, sizeof(Icmp_packet) + data_sz);
}
bool Icmp_packet::checksum_error(size_t data_sz) const
{
return internet_checksum((uint16_t *)this, sizeof(Icmp_packet) + data_sz);
}

View File

@ -0,0 +1,64 @@
/*
* \brief Computing the Internet Checksum (conforms to RFC 1071)
* \author Martin Stein
* \date 2018-03-23
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <net/internet_checksum.h>
using namespace Net;
using namespace Genode;
uint16_t Net::internet_checksum(uint16_t const *addr,
size_t size,
addr_t init_sum)
{
/* add up bytes in pairs */
addr_t sum = init_sum;
for (; size > 1; size -= 2)
sum += *addr++;
/* add left-over byte, if any */
if (size > 0)
sum += *(uint8_t *)addr;
/* fold sum to 16-bit value */
while (addr_t const sum_rsh = sum >> 16)
sum = (sum & 0xffff) + sum_rsh;
/* return one's complement */
return ~sum;
}
uint16_t Net::internet_checksum_pseudo_ip(uint16_t const *ip_data,
size_t ip_data_sz,
uint16_t ip_data_sz_be,
Ipv4_packet::Protocol ip_prot,
Ipv4_address &ip_src,
Ipv4_address &ip_dst)
{
/*
* Add up pseudo IP header:
*
* --------------------------------------------------------------
* | src-ipaddr | dst-ipaddr | zero-field | prot.-id | data size |
* | 4 bytes | 4 bytes | 1 byte | 1 byte | 2 bytes |
* --------------------------------------------------------------
*/
addr_t sum = host_to_big_endian((uint16_t)ip_prot) + ip_data_sz_be;
for (size_t i = 0; i < Ipv4_packet::ADDR_LEN; i += 2)
sum += *(uint16_t*)&ip_src.addr[i] + *(uint16_t*)&ip_dst.addr[i];
/* add up IP data bytes */
return internet_checksum(ip_data, ip_data_sz, sum);
}

View File

@ -11,9 +11,10 @@
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <util/token.h>
#include <util/string.h>
#include <net/internet_checksum.h>
#include <net/udp.h>
#include <net/tcp.h>
#include <net/icmp.h>
@ -133,17 +134,15 @@ Ipv4_address Ipv4_packet::ip_from_string(const char *ip)
return ip_addr;
}
Genode::uint16_t Ipv4_packet::calculate_checksum(Ipv4_packet const &packet)
void Ipv4_packet::update_checksum()
{
Genode::uint16_t const *data = (Genode::uint16_t *)&packet;
Genode::uint32_t const sum = host_to_big_endian(data[0])
+ host_to_big_endian(data[1])
+ host_to_big_endian(data[2])
+ host_to_big_endian(data[3])
+ host_to_big_endian(data[4])
+ host_to_big_endian(data[6])
+ host_to_big_endian(data[7])
+ host_to_big_endian(data[8])
+ host_to_big_endian(data[9]);
return ~((0xFFFF & sum) + (sum >> 16));
_checksum = 0;
_checksum = internet_checksum((uint16_t *)this, sizeof(Ipv4_packet));
}
bool Ipv4_packet::checksum_error() const
{
return internet_checksum((uint16_t *)this, sizeof(Ipv4_packet));
}

View File

@ -12,9 +12,13 @@
*/
/* Genode includes */
#include <net/internet_checksum.h>
#include <net/tcp.h>
#include <base/output.h>
using namespace Net;
using namespace Genode;
void Net::Tcp_packet::print(Genode::Output &output) const
{
@ -32,3 +36,14 @@ void Net::Tcp_packet::print(Genode::Output &output) const
if (ns()) { Genode::print(output, "n"); }
Genode::print(output, "' ");
}
void Net::Tcp_packet::update_checksum(Ipv4_address ip_src,
Ipv4_address ip_dst,
size_t tcp_size)
{
_checksum = 0;
_checksum = internet_checksum_pseudo_ip((uint16_t*)this, tcp_size,
host_to_big_endian((uint16_t)tcp_size),
Ipv4_packet::Protocol::TCP, ip_src, ip_dst);
}

View File

@ -1,6 +1,7 @@
/*
* \brief User datagram protocol.
* \author Stefan Kalkowski
* \author Martin Stein
* \date 2010-08-19
*/
@ -11,11 +12,15 @@
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode */
/* Genode includes */
#include <net/internet_checksum.h>
#include <net/udp.h>
#include <net/dhcp.h>
#include <base/output.h>
using namespace Net;
using namespace Genode;
void Net::Udp_packet::print(Genode::Output &output) const
{
@ -25,3 +30,12 @@ void Net::Udp_packet::print(Genode::Output &output) const
Genode::print(output, *reinterpret_cast<Dhcp_packet const *>(_data));
}
}
void Net::Udp_packet::update_checksum(Ipv4_address ip_src,
Ipv4_address ip_dst)
{
_checksum = 0;
_checksum = internet_checksum_pseudo_ip((uint16_t*)this, length(), _length,
Ipv4_packet::Protocol::UDP, ip_src, ip_dst);
}

View File

@ -267,6 +267,6 @@ void Dhcp_client::_send(Message_type msg_type,
udp.length(size.curr() - udp_off);
udp.update_checksum(ip.src(), ip.dst());
ip.total_length(size.curr() - ip_off);
ip.checksum(Ipv4_packet::calculate_checksum(ip));
ip.update_checksum();
});
}

View File

@ -113,8 +113,8 @@ static void _update_checksum(L3_protocol const prot,
case L3_protocol::ICMP:
{
Icmp_packet &icmp = *(Icmp_packet *)prot_base;
icmp.checksum(icmp.calc_checksum(ip_size - sizeof(Ipv4_packet) -
sizeof(Icmp_packet)));
icmp.update_checksum(ip_size - sizeof(Ipv4_packet) -
sizeof(Icmp_packet));
return;
}
default: throw Interface::Bad_transport_protocol(); }
@ -208,7 +208,7 @@ void Interface::_pass_ip(Ethernet_frame &eth,
size_t const eth_size,
Ipv4_packet &ip)
{
ip.checksum(Ipv4_packet::calculate_checksum(ip));
ip.update_checksum();
send(eth, eth_size);
}
@ -516,7 +516,7 @@ void Interface::_send_dhcp_reply(Dhcp_server const &dhcp_srv,
udp.length(size.curr() - udp_off);
udp.update_checksum(ip.src(), ip.dst());
ip.total_length(size.curr() - ip_off);
ip.checksum(Ipv4_packet::calculate_checksum(ip));
ip.update_checksum();
});
}
@ -733,9 +733,9 @@ void Interface::_send_icmp_dst_unreachable(Ipv4_address_prefix const &local_intf
Genode::memcpy(&icmp.data<char>(~0), &req_ip, icmp_data_size);
/* fill in header values that require the packet to be complete */
icmp.checksum(icmp.calc_checksum(icmp_data_size));
icmp.update_checksum(icmp_data_size);
ip.total_length(size.curr() - ip_off);
ip.checksum(Ipv4_packet::calculate_checksum(ip));
ip.update_checksum();
});
}
@ -808,7 +808,7 @@ void Interface::_handle_icmp_error(Ethernet_frame &eth,
/* drop packet if embedded IP checksum invalid */
size_t const embed_ip_sz = icmp_sz - sizeof(Icmp_packet);
Ipv4_packet &embed_ip = icmp.data<Ipv4_packet>(embed_ip_sz);
if (Ipv4_packet::calculate_checksum(embed_ip) != embed_ip.checksum()) {
if (embed_ip.checksum_error()) {
throw Drop_packet_inform("bad checksum in IP packet embedded in ICMP error");
}
/* get link identity of the embeddeded transport packet */
@ -843,9 +843,9 @@ void Interface::_handle_icmp_error(Ethernet_frame &eth,
_dst_port(embed_prot, embed_prot_base, remote_side.dst_port());
/* update checksum of both IP headers and the ICMP header */
embed_ip.checksum(Ipv4_packet::calculate_checksum(embed_ip));
icmp.checksum(icmp.calc_checksum(icmp_sz - sizeof(Icmp_packet)));
ip.checksum(Ipv4_packet::calculate_checksum(ip));
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) {
@ -874,7 +874,7 @@ void Interface::_handle_icmp(Ethernet_frame &eth,
size_t const icmp_sz = ip.total_length() - sizeof(Ipv4_packet);
Icmp_packet &icmp = ip.data<Icmp_packet>(icmp_sz);
size_t const icmp_data_sz = icmp_sz - sizeof(Icmp_packet);
if (icmp.calc_checksum(icmp_data_sz) != icmp.checksum()) {
if (icmp.checksum_error(icmp_data_sz)) {
throw Drop_packet_inform("bad ICMP checksum");
}
/* select ICMP message type */