Switch one's complement checksum implementation

Borrows the `in_cksum` code from tcpdump, which borrowed from FreeBSD.
It handles unaligned data better and also unrolls the inner loop to
process 16 two-byte values at a time versus 2 one-byte values at a time
in the previous version.  Generally measured as ~1.5x faster in a
release build.  The new API should generally be more amenable to any
future optimization explorations since all relevant data blocks are
available within a single call rather than spread across multiple.
This commit is contained in:
Jon Siwek 2020-09-24 08:47:50 -07:00
parent 8feca7291b
commit d070709c57
12 changed files with 283 additions and 74 deletions

View file

@ -250,6 +250,39 @@ PROJECT (https://github.com/zeek) UNDER BSD LICENCE.
============================================================================== ==============================================================================
%%% in_cksum.cc
==============================================================================
Copyright (c) 1988, 1992, 1993
The Regents of the University of California. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
==============================================================================
%%% Patricia.c %%% Patricia.c
============================================================================== ==============================================================================

View file

@ -296,6 +296,7 @@ set(MAIN_SRCS
legacy-netvar-init.cc legacy-netvar-init.cc
bsd-getopt-long.c bsd-getopt-long.c
bro_inet_ntop.c bro_inet_ntop.c
in_cksum.cc
patricia.c patricia.c
setsignal.c setsignal.c
strsep.c strsep.c

View file

@ -269,7 +269,7 @@ void NetSessions::DoNextPacket(double t, const Packet* pkt, const IP_Hdr* ip_hdr
return; return;
if ( ! pkt->l2_checksummed && ! zeek::detail::ignore_checksums && ip4 && if ( ! pkt->l2_checksummed && ! zeek::detail::ignore_checksums && ip4 &&
ones_complement_checksum((void*) ip4, ip_hdr_len, 0) != 0xffff ) detail::in_cksum(reinterpret_cast<const uint8_t*>(ip4), ip_hdr_len) != 0xffff )
{ {
Weird("bad_IP_checksum", pkt, encapsulation); Weird("bad_IP_checksum", pkt, encapsulation);
return; return;

View file

@ -346,7 +346,8 @@ RecordValPtr ICMP_Analyzer::ExtractICMP4Context(int len, const u_char*& data)
bad_hdr_len = 0; bad_hdr_len = 0;
ip_len = ip_hdr->TotalLen(); ip_len = ip_hdr->TotalLen();
bad_checksum = ! run_state::current_pkt->l3_checksummed && bad_checksum = ! run_state::current_pkt->l3_checksummed &&
(ones_complement_checksum((void*) ip_hdr->IP4_Hdr(), ip_hdr_len, 0) != 0xffff); (detail::in_cksum(reinterpret_cast<const uint8_t*>(ip_hdr->IP4_Hdr()),
ip_hdr_len) != 0xffff);
src_addr = ip_hdr->SrcAddr(); src_addr = ip_hdr->SrcAddr();
dst_addr = ip_hdr->DstAddr(); dst_addr = ip_hdr->DstAddr();

View file

@ -273,10 +273,10 @@ const struct tcphdr* TCP_Analyzer::ExtractTCP_Header(const u_char*& data,
} }
bool TCP_Analyzer::ValidateChecksum(const struct tcphdr* tp, bool TCP_Analyzer::ValidateChecksum(const struct tcphdr* tp,
TCP_Endpoint* endpoint, int len, int caplen) TCP_Endpoint* endpoint, int len, int caplen, bool ipv4)
{ {
if ( ! run_state::current_pkt->l3_checksummed && ! detail::ignore_checksums && caplen >= len && if ( ! run_state::current_pkt->l3_checksummed && ! detail::ignore_checksums && caplen >= len &&
! endpoint->ValidChecksum(tp, len) ) ! endpoint->ValidChecksum(tp, len, ipv4) )
{ {
Weird("bad_TCP_checksum"); Weird("bad_TCP_checksum");
endpoint->ChecksumError(); endpoint->ChecksumError();
@ -1060,7 +1060,7 @@ void TCP_Analyzer::DeliverPacket(int len, const u_char* data, bool is_orig,
TCP_Endpoint* endpoint = is_orig ? orig : resp; TCP_Endpoint* endpoint = is_orig ? orig : resp;
TCP_Endpoint* peer = endpoint->peer; TCP_Endpoint* peer = endpoint->peer;
if ( ! ValidateChecksum(tp, endpoint, len, caplen) ) if ( ! ValidateChecksum(tp, endpoint, len, caplen, ip->IP4_Hdr()) )
return; return;
uint32_t tcp_hdr_len = data - (const u_char*) tp; uint32_t tcp_hdr_len = data - (const u_char*) tp;

View file

@ -95,7 +95,7 @@ protected:
// Returns true if the checksum is valid, false if not (and in which // Returns true if the checksum is valid, false if not (and in which
// case also updates the status history of the endpoint). // case also updates the status history of the endpoint).
bool ValidateChecksum(const struct tcphdr* tp, TCP_Endpoint* endpoint, bool ValidateChecksum(const struct tcphdr* tp, TCP_Endpoint* endpoint,
int len, int caplen); int len, int caplen, bool ipv4);
void SetPartialStatus(TCP_Flags flags, bool is_orig); void SetPartialStatus(TCP_Flags flags, bool is_orig);

View file

@ -41,14 +41,6 @@ TCP_Endpoint::TCP_Endpoint(TCP_Analyzer* arg_analyzer, bool arg_is_orig)
src_addr = is_orig ? Conn()->RespAddr() : Conn()->OrigAddr(); src_addr = is_orig ? Conn()->RespAddr() : Conn()->OrigAddr();
dst_addr = is_orig ? Conn()->OrigAddr() : Conn()->RespAddr(); dst_addr = is_orig ? Conn()->OrigAddr() : Conn()->RespAddr();
checksum_base = ones_complement_checksum(src_addr, 0);
checksum_base = ones_complement_checksum(dst_addr, checksum_base);
// Note, for IPv6, strictly speaking this field is 32 bits
// rather than 16 bits. But because the upper bits are all zero,
// we get the same checksum either way. The same applies to
// later when we add in the data length in ValidChecksum().
checksum_base += htons(IPPROTO_TCP);
} }
TCP_Endpoint::~TCP_Endpoint() TCP_Endpoint::~TCP_Endpoint()
@ -121,17 +113,12 @@ void TCP_Endpoint::SizeBufferedData(uint64_t& waiting_on_hole,
waiting_on_hole = waiting_on_ack = 0; waiting_on_hole = waiting_on_ack = 0;
} }
bool TCP_Endpoint::ValidChecksum(const struct tcphdr* tp, int len) const bool TCP_Endpoint::ValidChecksum(const struct tcphdr* tp, int len, bool ipv4) const
{ {
uint32_t sum = checksum_base;
int tcp_len = tp->th_off * 4 + len; int tcp_len = tp->th_off * 4 + len;
if ( len % 2 == 1 ) auto sum = detail::ip_in_cksum(ipv4, src_addr, dst_addr, IPPROTO_TCP,
// Add in pad byte. reinterpret_cast<const uint8_t*>(tp), tcp_len);
sum += htons(((const u_char*) tp)[tcp_len - 1] << 8);
sum += htons((unsigned short) tcp_len); // fill out pseudo header
sum = ones_complement_checksum((void*) tp, tcp_len, sum);
return sum == 0xffff; return sum == 0xffff;
} }

View file

@ -165,7 +165,7 @@ public:
// WARNING: this is an O(n) operation and potentially very slow. // WARNING: this is an O(n) operation and potentially very slow.
void SizeBufferedData(uint64_t& waiting_on_hole, uint64_t& waiting_on_ack); void SizeBufferedData(uint64_t& waiting_on_hole, uint64_t& waiting_on_ack);
bool ValidChecksum(const struct tcphdr* tp, int len) const; bool ValidChecksum(const struct tcphdr* tp, int len, bool ipv4) const;
// Called to inform endpoint that it has generated a checksum error. // Called to inform endpoint that it has generated a checksum error.
void ChecksumError(); void ChecksumError();
@ -211,7 +211,6 @@ public:
TCP_Reassembler* contents_processor; TCP_Reassembler* contents_processor;
TCP_Analyzer* tcp_analyzer; TCP_Analyzer* tcp_analyzer;
FilePtr contents_file; FilePtr contents_file;
uint32_t checksum_base;
double start_time, last_time; double start_time, last_time;
IPAddr src_addr; // the other endpoint IPAddr src_addr; // the other endpoint

View file

@ -260,22 +260,9 @@ void UDP_Analyzer::ChecksumEvent(bool is_orig, uint32_t threshold)
bool UDP_Analyzer::ValidateChecksum(const IP_Hdr* ip, const udphdr* up, int len) bool UDP_Analyzer::ValidateChecksum(const IP_Hdr* ip, const udphdr* up, int len)
{ {
uint32_t sum; auto sum = detail::ip_in_cksum(ip->IP4_Hdr(), ip->SrcAddr(), ip->DstAddr(),
IPPROTO_UDP,
if ( len % 2 == 1 ) reinterpret_cast<const uint8_t*>(up), len);
// Add in pad byte.
sum = htons(((const u_char*) up)[len - 1] << 8);
else
sum = 0;
sum = ones_complement_checksum(ip->SrcAddr(), sum);
sum = ones_complement_checksum(ip->DstAddr(), sum);
// Note, for IPv6, strictly speaking the protocol and length fields are
// 32 bits rather than 16 bits. But because the upper bits are all zero,
// we get the same checksum either way.
sum += htons(IPPROTO_UDP);
sum += htons((unsigned short) len);
sum = ones_complement_checksum((void*) up, len, sum);
return sum == 0xffff; return sum == 0xffff;
} }

141
src/in_cksum.cc Normal file
View file

@ -0,0 +1,141 @@
// Modified from tcpdump v4.9.3's in_cksum.c (which itself was a modified
// version of FreeBSD's in_cksum.c).
/* in_cksum.c
* 4.4-Lite-2 Internet checksum routine, modified to take a vector of
* pointers/lengths giving the pieces to be checksummed. Also using
* Tahoe/CGI version of ADDCARRY(x) macro instead of from portable version.
*/
/*
* Copyright (c) 1988, 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)in_cksum.c 8.1 (Berkeley) 6/10/93
*/
#include "net_util.h"
namespace zeek::detail {
#define ADDCARRY(x) {if ((x) > 65535) (x) -= 65535;}
#define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; ADDCARRY(sum);}
uint16_t in_cksum(const struct checksum_block *vec, int veclen)
{
const uint16_t *w;
int sum = 0;
int mlen = 0;
int byte_swapped = 0;
union {
uint8_t c[2];
uint16_t s;
} s_util;
union {
uint16_t s[2];
uint32_t l;
} l_util;
for (; veclen != 0; vec++, veclen--) {
if (vec->len == 0)
continue;
w = (const uint16_t *)(const void *)vec->block;
if (mlen == -1) {
/*
* The first byte of this chunk is the continuation
* of a word spanning between this chunk and the
* last chunk.
*
* s_util.c[0] is already saved when scanning previous
* chunk.
*/
s_util.c[1] = *(const uint8_t *)w;
sum += s_util.s;
w = (const uint16_t *)(const void *)((const uint8_t *)w + 1);
mlen = vec->len - 1;
} else
mlen = vec->len;
/*
* Force to even boundary.
*/
if ((1 & (uintptr_t) w) && (mlen > 0)) {
REDUCE;
sum <<= 8;
s_util.c[0] = *(const uint8_t *)w;
w = (const uint16_t *)(const void *)((const uint8_t *)w + 1);
mlen--;
byte_swapped = 1;
}
/*
* Unroll the loop to make overhead from
* branches &c small.
*/
while ((mlen -= 32) >= 0) {
sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];
sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11];
sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15];
w += 16;
}
mlen += 32;
while ((mlen -= 8) >= 0) {
sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
w += 4;
}
mlen += 8;
if (mlen == 0 && byte_swapped == 0)
continue;
REDUCE;
while ((mlen -= 2) >= 0) {
sum += *w++;
}
if (byte_swapped) {
REDUCE;
sum <<= 8;
byte_swapped = 0;
if (mlen == -1) {
s_util.c[1] = *(const uint8_t *)w;
sum += s_util.s;
mlen = 0;
} else
mlen = -1;
} else if (mlen == -1)
s_util.c[0] = *(const uint8_t *)w;
}
if (mlen == -1) {
/* The last mbuf has odd # of bytes. Follow the
standard (the odd byte may be shifted left by 8 bits
or not as determined by endian-ness of the machine) */
s_util.c[1] = 0;
sum += s_util.s;
}
REDUCE;
return sum;
}
} // namespace zeek

View file

@ -16,8 +16,49 @@
namespace zeek { namespace zeek {
// - adapted from tcpdump uint16_t detail::ip4_in_cksum(const IPAddr& src, const IPAddr& dst,
// Returns the ones-complement checksum of a chunk of b short-aligned bytes. uint8_t next_proto, const uint8_t* data, int len)
{
constexpr auto nblocks = 2;
detail::checksum_block blocks[nblocks];
ipv4_pseudo_hdr ph;
memset(&ph, 0, sizeof(ph));
src.CopyIPv4(&ph.src);
dst.CopyIPv4(&ph.dst);
ph.len = htons(static_cast<uint16_t>(len));
ph.next_proto = next_proto;
blocks[0].block = reinterpret_cast<const uint8_t*>(&ph);
blocks[0].len = sizeof(ph);
blocks[1].block = data;
blocks[1].len = len;
return in_cksum(blocks, nblocks);
}
uint16_t detail::ip6_in_cksum(const IPAddr& src, const IPAddr& dst,
uint8_t next_proto, const uint8_t* data, int len)
{
constexpr auto nblocks = 2;
detail::checksum_block blocks[nblocks];
ipv6_pseudo_hdr ph;
memset(&ph, 0, sizeof(ph));
src.CopyIPv6(&ph.src);
dst.CopyIPv6(&ph.dst);
ph.len = htonl(static_cast<uint32_t>(len));
ph.next_proto = next_proto;
blocks[0].block = reinterpret_cast<const uint8_t*>(&ph);
blocks[0].len = sizeof(ph);
blocks[1].block = data;
blocks[1].len = len;
return in_cksum(blocks, nblocks);
}
// Returns the ones-complement checksum of a chunk of 'b' bytes.
int ones_complement_checksum(const void* p, int b, uint32_t sum) int ones_complement_checksum(const void* p, int b, uint32_t sum)
{ {
const unsigned char* sp = (unsigned char*) p; const unsigned char* sp = (unsigned char*) p;
@ -46,17 +87,7 @@ int ones_complement_checksum(const IPAddr& a, uint32_t sum)
int icmp_checksum(const struct icmp* icmpp, int len) int icmp_checksum(const struct icmp* icmpp, int len)
{ {
uint32_t sum; return detail::in_cksum(reinterpret_cast<const uint8_t*>(icmpp), len);
if ( len % 2 == 1 )
// Add in pad byte.
sum = htons(((const u_char*) icmpp)[len - 1] << 8);
else
sum = 0;
sum = ones_complement_checksum((void*) icmpp, len, sum);
return sum;
} }
#ifdef ENABLE_MOBILE_IPV6 #ifdef ENABLE_MOBILE_IPV6
@ -89,26 +120,8 @@ int icmp6_checksum(const struct icmp* icmpp, const IP_Hdr* ip, int len)
{ {
// ICMP6 uses the same checksum function as ICMP4 but a different // ICMP6 uses the same checksum function as ICMP4 but a different
// pseudo-header over which it is computed. // pseudo-header over which it is computed.
uint32_t sum; return detail::ip6_in_cksum(ip->SrcAddr(), ip->DstAddr(), IPPROTO_ICMPV6,
reinterpret_cast<const uint8_t*>(icmpp), len);
if ( len % 2 == 1 )
// Add in pad byte.
sum = htons(((const u_char*) icmpp)[len - 1] << 8);
else
sum = 0;
// Pseudo-header as for UDP over IPv6 above.
sum = ones_complement_checksum(ip->SrcAddr(), sum);
sum = ones_complement_checksum(ip->DstAddr(), sum);
uint32_t l = htonl(len);
sum = ones_complement_checksum((void*) &l, 4, sum);
uint32_t addl_pseudo = htons(IPPROTO_ICMPV6);
sum = ones_complement_checksum((void*) &addl_pseudo, 4, sum);
sum = ones_complement_checksum((void*) icmpp, len, sum);
return sum;
} }

View file

@ -121,7 +121,54 @@ ZEEK_FORWARD_DECLARE_NAMESPACED(IP_Hdr, zeek);
namespace zeek { namespace zeek {
// Returns the ones-complement checksum of a chunk of b short-aligned bytes. namespace detail {
struct checksum_block {
const uint8_t* block;
int len;
};
struct ipv4_pseudo_hdr {
in_addr src;
in_addr dst;
uint8_t zero;
uint8_t next_proto;
uint16_t len;
};
struct ipv6_pseudo_hdr {
in6_addr src;
in6_addr dst;
uint32_t len;
uint8_t zero[3];
uint8_t next_proto;
};
extern uint16_t in_cksum(const checksum_block* blocks, int num_blocks);
inline uint16_t in_cksum(const uint8_t* data, int len)
{
checksum_block cb{data, len};
return in_cksum(&cb, 1);
}
extern uint16_t ip4_in_cksum(const IPAddr& src, const IPAddr& dst,
uint8_t next_proto, const uint8_t* data, int len);
extern uint16_t ip6_in_cksum(const IPAddr& src, const IPAddr& dst,
uint8_t next_proto, const uint8_t* data, int len);
inline uint16_t ip_in_cksum(bool is_ipv4, const IPAddr& src, const IPAddr& dst,
uint8_t next_proto, const uint8_t* data, int len)
{
if ( is_ipv4 )
return ip4_in_cksum(src, dst, next_proto, data, len);
return ip6_in_cksum(src, dst, next_proto, data, len);
}
} // namespace zeek::detail
// Returns the ones-complement checksum of a chunk of 'b' bytes.
extern int ones_complement_checksum(const void* p, int b, uint32_t sum); extern int ones_complement_checksum(const void* p, int b, uint32_t sum);
extern int ones_complement_checksum(const IPAddr& a, uint32_t sum); extern int ones_complement_checksum(const IPAddr& a, uint32_t sum);