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

@ -346,7 +346,8 @@ RecordValPtr ICMP_Analyzer::ExtractICMP4Context(int len, const u_char*& data)
bad_hdr_len = 0;
ip_len = ip_hdr->TotalLen();
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();
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,
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 &&
! endpoint->ValidChecksum(tp, len) )
! endpoint->ValidChecksum(tp, len, ipv4) )
{
Weird("bad_TCP_checksum");
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* peer = endpoint->peer;
if ( ! ValidateChecksum(tp, endpoint, len, caplen) )
if ( ! ValidateChecksum(tp, endpoint, len, caplen, ip->IP4_Hdr()) )
return;
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
// case also updates the status history of the 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);

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();
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()
@ -121,17 +113,12 @@ void TCP_Endpoint::SizeBufferedData(uint64_t& waiting_on_hole,
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;
if ( len % 2 == 1 )
// Add in pad byte.
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);
auto sum = detail::ip_in_cksum(ipv4, src_addr, dst_addr, IPPROTO_TCP,
reinterpret_cast<const uint8_t*>(tp), tcp_len);
return sum == 0xffff;
}

View file

@ -165,7 +165,7 @@ public:
// WARNING: this is an O(n) operation and potentially very slow.
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.
void ChecksumError();
@ -211,7 +211,6 @@ public:
TCP_Reassembler* contents_processor;
TCP_Analyzer* tcp_analyzer;
FilePtr contents_file;
uint32_t checksum_base;
double start_time, last_time;
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)
{
uint32_t sum;
if ( len % 2 == 1 )
// 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);
auto sum = detail::ip_in_cksum(ip->IP4_Hdr(), ip->SrcAddr(), ip->DstAddr(),
IPPROTO_UDP,
reinterpret_cast<const uint8_t*>(up), len);
return sum == 0xffff;
}