Extend the capture to include TCP flags, window and ACK data.

This commit is contained in:
Herbert Wolverson 2023-03-15 17:15:12 +00:00
parent d542bf1660
commit 8288bb3f9b
10 changed files with 187 additions and 77 deletions

View File

@ -129,8 +129,18 @@ pub struct PacketHeader {
pub dst_port: u16,
/// Ip Protocol (see Linux kernel docs)
pub ip_protocol: u8,
/// Tos to decode
pub tos: u8,
/// ECN Flag
pub ecn: u8,
/// DHSCP code
pub dscp: u8,
/// Packet Size
pub size: u32,
/// TCP Flag Bitset
pub tcp_flags: u8,
/// TCP Window Size
pub tcp_window: u16,
/// TCP TSVal
pub tcp_tsval: u32,
/// TCP ECR val
pub tcp_tsecr: u32,
}

View File

@ -15,8 +15,24 @@ pub struct HeimdallEvent {
pub ip_protocol: u8,
pub tos: u8,
pub size: u32,
pub tcp_flags: u8,
pub tcp_window: u16,
pub tcp_tsval: u32,
pub tcp_tsecr: u32,
}
/*
Snippet for tcp_flags decoding
if (hdr->fin) flags |= 1;
if (hdr->syn) flags |= 2;
if (hdr->rst) flags |= 4;
if (hdr->psh) flags |= 8;
if (hdr->ack) flags |= 16;
if (hdr->urg) flags |= 32;
if (hdr->ece) flags |= 64;
if (hdr->cwr) flags |= 128;
*/
/// Callback for the Heimdall Perf map system. Called whenever Heimdall has
/// events for the system to read.
///

View File

@ -1,12 +1,13 @@
use std::time::Duration;
use dashmap::DashSet;
use lqos_bus::PacketHeader;
use lqos_bus::{PacketHeader, tos_parser};
use lqos_utils::{unix_time::time_since_boot, XdpIpAddress};
use once_cell::sync::Lazy;
use crate::perf_interface::HeimdallEvent;
impl HeimdallEvent {
fn as_header(&self) -> PacketHeader {
let (dscp, ecn) = tos_parser(self.tos);
PacketHeader {
timestamp: self.timestamp,
src: self.src.as_ip().to_string(),
@ -14,8 +15,12 @@ impl HeimdallEvent {
src_port: self.src_port,
dst_port: self.dst_port,
ip_protocol: self.ip_protocol,
tos: self.tos,
ecn, dscp,
size: self.size,
tcp_flags: self.tcp_flags,
tcp_window: self.tcp_window,
tcp_tsecr: self.tcp_tsecr,
tcp_tsval: self.tcp_tsval,
}
}
}

View File

@ -538,16 +538,6 @@
});
}
function ecn(n) {
switch (n) {
case 0: return "-";
case 1: return "L4S";
case 2: return "ECT0";
case 3: return "CE";
default: return "???";
}
}
function icmpType(n) {
switch (n) {
case 0: return "ECHO REPLY";

View File

@ -89,6 +89,32 @@
}
}
/*
Snippet for tcp_flags decoding
if (hdr->fin) flags |= 1;
if (hdr->syn) flags |= 2;
if (hdr->rst) flags |= 4;
if (hdr->psh) flags |= 8;
if (hdr->ack) flags |= 16;
if (hdr->urg) flags |= 32;
if (hdr->ece) flags |= 64;
if (hdr->cwr) flags |= 128;
*/
function tcp_flags(n) {
let result = "";
if (n & 1) result += "FIN-";
if (n & 2) result += "SYN-";
if (n & 4) result += "RST-";
if (n & 8) result += "PSH-";
if (n & 16) result += "ACK-";
if (n & 32) result += "URG-";
if (n & 64) result += "ECE-";
if (n & 128) result += "CWR-";
return result;
}
function zoomIn() {
PAGE_SIZE /= 2;
current_page /= 2;
@ -125,7 +151,7 @@
console.log("OOps");
}
let html = "<table class='table table-striped'>";
html += "<thead><th>Time (nanos)</th><th>Proto</th><th>Flow</th><th>Bytes</th><th>TOS</th></thead>";
html += "<thead><th>Time (nanos)</th><th>Proto</th><th>TCP Flags</th><th>Sequence</th><th>Window</th><th>Flow</th><th>Bytes</th><th>ECN</th><th>DHSCP</th></thead>";
let x_axis = [];
let y1_axis = [];
let y2_axis = [];
@ -133,13 +159,23 @@
html += "<tr>";
html += "<td>" + packets[i].timestamp + "</td>";
html += "<td>" + proto(packets[i].ip_protocol) + "</td>";
if (packets[i].ip_protocol == 6) {
html += "<td>" + tcp_flags(packets[i].tcp_flags) + "</td>";
html += "<td>" + packets[i].tcp_tsval + "/" + packets[i].tcp_tsecr + "</td>";
html += "<td>" + packets[i].tcp_window + "</td>";
} else {
html += "<td></td><td></td><td></td>";
}
if (packets[i].ip_protocol != 1) {
html += "<td>" + packets[i].src + ":" + packets[i].src_port + " -> " + packets[i].dst + ":" + packets[i].dst_port + "</td>";
} else {
html += "<td>" + packets[i].src + " -> " + packets[i].dst + "</td>";
}
html += "<td>" + packets[i].size + "</td>";
html += "<td>" + packets[i].tos + "</td>";
html += "<td>" + ecn(packets[i].ecn) + "</td>";
html += "<td>0x" + packets[i].dscp.toString(16) + "</td>";
html += "</tr>";
x_axis.push(packets[i].timestamp);
if (packets[i].src == target) {
@ -177,7 +213,7 @@
target = params.ip;
$.get("/api/packet_dump/" + params.ip, (data) => {
data.sort((a,b) => a<b);
data.sort((a,b) => a.timestamp - b.timestamp);
let min_ts = null;
for (let i=0; i<data.length; ++i) {
if (min_ts == null || min_ts > data[i].timestamp) {

View File

@ -368,4 +368,14 @@ class RttHistogram {
let graph = document.getElementById(target_div);
Plotly.newPlot(graph, gData, { margin: { l: 0, r: 0, b: 35, t: 0 }, xaxis: { title: 'TCP Round-Trip Time (ms)' } }, { responsive: true });
}
}
function ecn(n) {
switch (n) {
case 0: return "-";
case 1: return "L4S";
case 2: return "ECT0";
case 3: return "CE";
default: return "???";
}
}

View File

@ -9,6 +9,7 @@
#include "../common/debug.h"
#include "../common/ip_hash.h"
#include "../common/bifrost.h"
#include "../common/tcp_opts.h"
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/tcp.h>
@ -47,6 +48,10 @@ struct dissector_t
__u16 src_port;
__u16 dst_port;
__u8 tos;
__u8 tcp_flags;
__u16 window;
__u32 tsval;
__u32 tsecr;
};
// Representation of the VLAN header type.
@ -325,6 +330,20 @@ static __always_inline void snoop(struct dissector_t *dissector)
}
dissector->src_port = hdr->source;
dissector->dst_port = hdr->dest;
__u8 flags = 0;
if (hdr->fin) flags |= 1;
if (hdr->syn) flags |= 2;
if (hdr->rst) flags |= 4;
if (hdr->psh) flags |= 8;
if (hdr->ack) flags |= 16;
if (hdr->urg) flags |= 32;
if (hdr->ece) flags |= 64;
if (hdr->cwr) flags |= 128;
dissector->tcp_flags = flags;
dissector->window = hdr->window;
parse_tcp_ts(hdr, dissector->end, &dissector->tsval, &dissector->tsecr);
}
}
break;

View File

@ -50,6 +50,10 @@ struct heimdall_event {
__u8 ip_protocol;
__u8 tos;
__u32 size;
__u8 tcp_flags;
__u16 tcp_window;
__u32 tsval;
__u32 tsecr;
};
static __always_inline __u8 get_heimdall_mode()
@ -91,6 +95,10 @@ static __always_inline void update_heimdall(struct dissector_t *dissector, __u32
event.ip_protocol = dissector->ip_protocol;
event.tos = dissector->tos;
event.size = size;
event.tcp_flags = dissector->tcp_flags;
event.tcp_window = dissector->window;
event.tsval = dissector->tsval;
event.tsecr = dissector->tsecr;
long err = bpf_ringbuf_output(&heimdall_events, &event, sizeof(event), 0);
if (err != 0) {
bpf_debug("Failed to send perf event %d", err);

View File

@ -0,0 +1,75 @@
#pragma once
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/pkt_cls.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <bpf/bpf_endian.h>
#include <stdbool.h>
#define MAX_TCP_OPTIONS 10
/*
* Parses the TSval and TSecr values from the TCP options field. If sucessful
* the TSval and TSecr values will be stored at tsval and tsecr (in network
* byte order).
* Returns 0 if sucessful and -1 on failure
*/
static __always_inline int parse_tcp_ts(
struct tcphdr *tcph,
void *data_end,
__u32 *tsval,
__u32 *tsecr
) {
int len = tcph->doff << 2;
void *opt_end = (void *)tcph + len;
__u8 *pos = (__u8 *)(tcph + 1); // Current pos in TCP options
__u8 i, opt;
volatile __u8
opt_size; // Seems to ensure it's always read of from stack as u8
if (tcph + 1 > data_end || len <= sizeof(struct tcphdr))
return -1;
#pragma unroll // temporary solution until we can identify why the non-unrolled loop gets stuck in an infinite loop
for (i = 0; i < MAX_TCP_OPTIONS; i++)
{
if (pos + 1 > opt_end || pos + 1 > data_end)
return -1;
opt = *pos;
if (opt == 0) // Reached end of TCP options
return -1;
if (opt == 1)
{ // TCP NOP option - advance one byte
pos++;
continue;
}
// Option > 1, should have option size
if (pos + 2 > opt_end || pos + 2 > data_end)
return -1;
opt_size = *(pos + 1);
if (opt_size < 2) // Stop parsing options if opt_size has an invalid value
return -1;
// Option-kind is TCP timestap (yey!)
if (opt == 8 && opt_size == 10)
{
if (pos + 10 > opt_end || pos + 10 > data_end)
return -1;
*tsval = bpf_ntohl(*(__u32 *)(pos + 2));
*tsecr = bpf_ntohl(*(__u32 *)(pos + 6));
return 0;
}
// Some other TCP option - advance option-length bytes
pos += opt_size;
}
return -1;
}

View File

@ -36,6 +36,7 @@ My modifications are Copyright 2022, Herbert Wolverson
#include "debug.h"
#include "ip_hash.h"
#include "dissector_tc.h"
#include "tcp_opts.h"
#define MAX_MEMCMP_SIZE 128
@ -226,66 +227,6 @@ static __always_inline struct flow_state *fstate_from_dfkey(
return is_dfkey ? &df_state->dir1 : &df_state->dir2;
}
/*
* Parses the TSval and TSecr values from the TCP options field. If sucessful
* the TSval and TSecr values will be stored at tsval and tsecr (in network
* byte order).
* Returns 0 if sucessful and -1 on failure
*/
static __always_inline int parse_tcp_ts(
struct tcphdr *tcph,
void *data_end,
__u32 *tsval,
__u32 *tsecr
) {
int len = tcph->doff << 2;
void *opt_end = (void *)tcph + len;
__u8 *pos = (__u8 *)(tcph + 1); // Current pos in TCP options
__u8 i, opt;
volatile __u8
opt_size; // Seems to ensure it's always read of from stack as u8
if (tcph + 1 > data_end || len <= sizeof(struct tcphdr))
return -1;
#pragma unroll // temporary solution until we can identify why the non-unrolled loop gets stuck in an infinite loop
for (i = 0; i < MAX_TCP_OPTIONS; i++)
{
if (pos + 1 > opt_end || pos + 1 > data_end)
return -1;
opt = *pos;
if (opt == 0) // Reached end of TCP options
return -1;
if (opt == 1)
{ // TCP NOP option - advance one byte
pos++;
continue;
}
// Option > 1, should have option size
if (pos + 2 > opt_end || pos + 2 > data_end)
return -1;
opt_size = *(pos + 1);
if (opt_size < 2) // Stop parsing options if opt_size has an invalid value
return -1;
// Option-kind is TCP timestap (yey!)
if (opt == 8 && opt_size == 10)
{
if (pos + 10 > opt_end || pos + 10 > data_end)
return -1;
*tsval = bpf_ntohl(*(__u32 *)(pos + 2));
*tsecr = bpf_ntohl(*(__u32 *)(pos + 6));
return 0;
}
// Some other TCP option - advance option-length bytes
pos += opt_size;
}
return -1;
}
/*
* Attempts to fetch an identifier for TCP packets, based on the TCP timestamp
* option.