Cleanup ICMP tracking, add documentation to source code.

This should make the Heimdall branch usable.
This commit is contained in:
Herbert Wolverson 2023-03-13 19:50:27 +00:00
parent d3feb64911
commit 7d055d9796
6 changed files with 134 additions and 38 deletions

View File

@ -67,24 +67,43 @@ pub struct XdpPpingResult {
pub samples: u32,
}
/// Defines an IP protocol for display in the flow
/// tracking (Heimdall) system.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum FlowProto {
TCP, UDP, ICMP
/// A TCP flow
TCP,
/// A UDP flow
UDP,
/// An ICMP flow
ICMP
}
/// Defines the display data for a flow in Heimdall.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct FlowTransport {
/// The Source IP address
pub src: String,
/// The Destination IP address
pub dst: String,
/// The flow protocol (see `FlowProto`)
pub proto: FlowProto,
/// The source port, which is overridden to ICMP code on ICMP flows.
pub src_port: u16,
/// The destination port, which isn't useful at all on ICMP flows.
pub dst_port: u16,
/// The number of bytes since we started tracking this flow.
pub bytes: u64,
/// The number of packets since we started tracking this flow.
pub packets: u64,
/// Detected DSCP code if any
pub dscp: u8,
/// Detected ECN bit status (0-3)
pub ecn: u8,
}
/// Extract the 6-bit DSCP and 2-bit ECN code from a TOS field
/// in an IP header.
pub fn tos_parser(tos: u8) -> (u8, u8) {
// Format: 2 bits of ECN, 6 bits of DSCP
const ECN: u8 = 0b00000011;

View File

@ -549,6 +549,24 @@
}
}
function icmpType(n) {
switch (n) {
case 0: return "ECHO REPLY";
case 3: return "DESTINATION UNREACHABLE";
case 4: return "SOURCE QUENCH";
case 8: return "ECHO REQUEST";
case 11: return "TIME EXCEEDED";
case 12: return "PARAMETER PROBLEM";
case 13: return "TIMESTAMP REQUEST";
case 14: return "TIMESTAMP REPLY";
case 15: return "INFO REQUEST";
case 16: return "INFO REPLY";
case 17: return "ADDRESS REQUEST";
case 18: return "ADDRESS REPLY";
default: return "?";
}
}
function getFlows() {
let ip_list = "";
for (let i=0; i<ips.length; ++i) {
@ -587,9 +605,21 @@
html += "<tr>";
html += "<td>" + data[i][0].proto + "</td>";
html += "<td>" + data[i][0].src + "</td>";
html += "<td>" + data[i][0].src_port + "</td>";
if (data[i][0].proto == "ICMP") {
html += "<td>" + icmpType(data[i][0].src_port) + "</td>";
} else {
html += "<td>" + data[i][0].src_port + "</td>";
}
html += "<td>" + data[i][0].dst + "</td>";
html += "<td>" + data[i][0].dst_port + "</td>";
if (data[i][0].proto == "ICMP") {
if (data[i][1] != null) {
html += "<td>" + icmpType(data[i][1].src_port) + "</td>";
} else {
html += "<td></td>";
}
} else {
html += "<td>" + data[i][0].dst_port + "</td>";
}
html += "<td>" + data[i][0].packets + "</td>";
html += "<td>" + rpackets + "</td>";
html += "<td>" + scaleNumber(data[i][0].bytes) + "</td>";

View File

@ -346,12 +346,13 @@ static __always_inline void snoop(struct dissector_t *dissector)
struct icmphdr *hdr = get_icmp_header(dissector);
if (hdr != NULL)
{
if (hdr + 1 > dissector->end)
if ((char *)hdr + sizeof(struct icmphdr) > dissector->end)
{
return;
}
dissector->src_port = hdr->type;
dissector->dst_port = hdr->code;
dissector->ip_protocol = 1;
dissector->src_port = bpf_ntohs(hdr->type);
dissector->dst_port = bpf_ntohs(hdr->type);
}
}
break;

View File

@ -8,28 +8,31 @@
#include "dissector.h"
// Array containing one element, the Heimdall configuration
struct heimdall_config_t {
struct heimdall_config_t
{
__u32 monitor_mode; // 0 = Off, 1 = Targets only, 2 = Analysis Mode
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, struct heimdall_config_t);
__uint(max_entries, 2);
__uint(pinning, LIBBPF_PIN_BY_NAME);
struct
{
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, struct heimdall_config_t);
__uint(max_entries, 2);
__uint(pinning, LIBBPF_PIN_BY_NAME);
} heimdall_config SEC(".maps");
struct
{
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, struct in6_addr);
__type(value, __u32);
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, struct in6_addr);
__type(value, __u32);
__uint(max_entries, 64);
__uint(pinning, LIBBPF_PIN_BY_NAME);
__uint(pinning, LIBBPF_PIN_BY_NAME);
} heimdall_watching SEC(".maps");
struct heimdall_key {
struct heimdall_key
{
struct in6_addr src;
struct in6_addr dst;
__u8 ip_protocol;
@ -37,7 +40,8 @@ struct heimdall_key {
__u16 dst_port;
};
struct heimdall_data {
struct heimdall_data
{
__u64 last_seen;
__u64 bytes;
__u64 packets;
@ -47,48 +51,64 @@ struct heimdall_data {
struct
{
__uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH);
__type(key, struct heimdall_key);
__type(value, struct heimdall_data);
__uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH);
__type(key, struct heimdall_key);
__type(value, struct heimdall_data);
__uint(max_entries, MAX_FLOWS);
__uint(pinning, LIBBPF_PIN_BY_NAME);
__uint(pinning, LIBBPF_PIN_BY_NAME);
} heimdall SEC(".maps");
static __always_inline __u8 get_heimdall_mode() {
static __always_inline __u8 get_heimdall_mode()
{
__u32 index = 0;
struct heimdall_config_t * cfg = (struct heimdall_config_t *)bpf_map_lookup_elem(&heimdall_config, &index);
if (cfg) {
struct heimdall_config_t *cfg = (struct heimdall_config_t *)bpf_map_lookup_elem(&heimdall_config, &index);
if (cfg)
{
return cfg->monitor_mode;
} else {
}
else
{
return 0;
}
}
static __always_inline bool is_heimdall_watching(struct dissector_t *dissector) {
__u32 * watching = bpf_map_lookup_elem(&heimdall_watching, &dissector->src_ip);
if (watching) return true;
static __always_inline bool is_heimdall_watching(struct dissector_t *dissector)
{
__u32 *watching = bpf_map_lookup_elem(&heimdall_watching, &dissector->src_ip);
if (watching)
return true;
watching = bpf_map_lookup_elem(&heimdall_watching, &dissector->dst_ip);
if (watching) return true;
if (watching)
return true;
return false;
}
static __always_inline void update_heimdall(struct dissector_t * dissector, __u32 size, int dir) {
if (dissector->src_port == 0 || dissector->dst_port == 0) return;
static __always_inline void update_heimdall(struct dissector_t *dissector, __u32 size, int dir)
{
// Don't report any non-ICMP without ports
if (dissector->ip_protocol != 1 && (dissector->src_port == 0 || dissector->dst_port == 0))
return;
// Don't report ICMP with invalid numbers
if (dissector->ip_protocol == 1 && dissector->src_port > 18) return;
struct heimdall_key key = {0};
key.src = dissector->src_ip;
key.dst = dissector->dst_ip;
key.ip_protocol = dissector->ip_protocol;
key.src_port = bpf_ntohs(dissector->src_port);
key.dst_port = bpf_ntohs(dissector->dst_port);
struct heimdall_data * counter = (struct heimdall_data *)bpf_map_lookup_elem(&heimdall, &key);
if (counter) {
struct heimdall_data *counter = (struct heimdall_data *)bpf_map_lookup_elem(&heimdall, &key);
if (counter)
{
counter->last_seen = bpf_ktime_get_boot_ns();
counter->packets += 1;
counter->bytes += size;
if (dissector->tos != 0) {
if (dissector->tos != 0)
{
counter->tos = dissector->tos;
}
} else {
}
else
{
struct heimdall_data counter = {0};
counter.last_seen = bpf_ktime_get_boot_ns();
counter.bytes = size;
@ -97,7 +117,8 @@ static __always_inline void update_heimdall(struct dissector_t * dissector, __u3
counter.reserved[0] = 0;
counter.reserved[1] = 0;
counter.reserved[2] = 0;
if (bpf_map_update_elem(&heimdall, &key, &counter, BPF_NOEXIST) != 0) {
if (bpf_map_update_elem(&heimdall, &key, &counter, BPF_NOEXIST) != 0)
{
bpf_debug("Failed to insert tracking");
}
}

View File

@ -5,23 +5,35 @@ use once_cell::sync::Lazy;
use crate::{bpf_per_cpu_map::BpfPerCpuMap, XdpIpAddress, bpf_map::BpfMap};
/// Representation of the eBPF `heimdall_key` type.
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct HeimdallKey {
/// Mapped `XdpIpAddress` source for the flow.
pub src_ip: XdpIpAddress,
/// Mapped `XdpIpAddress` destination for the flow
pub dst_ip: XdpIpAddress,
/// IP protocol (see the Linux kernel!)
pub ip_protocol: u8,
/// Source port number, or ICMP type.
pub src_port: u16,
/// Destination port number.
pub dst_port: u16,
}
/// Mapped representation of the eBPF `heimdall_data` type.
#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct HeimdallData {
/// Last seen, in nanoseconds (since boot time).
pub last_seen: u64,
/// Number of bytes since the flow started being tracked
pub bytes: u64,
/// Number of packets since the flow started being tracked
pub packets: u64,
/// IP header TOS value
pub tos: u8,
/// Reserved to pad the structure
pub reserved: [u8; 3],
}
@ -37,10 +49,15 @@ pub fn heimdall_for_each(
}
}
/// Currently unused, represents the current operation mode of the Heimdall
/// sub-system. Defaults to 1.
#[repr(u8)]
pub enum HeimdallMode {
/// Do not monitor
Off = 0,
/// Only look at flows on hosts we are watching via the circuit monitor
WatchOnly = 1,
/// Capture everything (this may set your CPU on fire)
Analysis = 2,
}
@ -87,6 +104,8 @@ impl HeimdallWatching {
static HEIMDALL_WATCH_LIST: Lazy<DashMap<XdpIpAddress, HeimdallWatching>> = Lazy::new(DashMap::new);
/// Run this periodically (once per second) to expire any watched traffic
/// flows that haven't received traffic in the last 30 seconds.
pub fn heimdall_expire() {
if let Ok(now) = time_since_boot() {
let now = Duration::from(now).as_nanos();
@ -99,6 +118,9 @@ pub fn heimdall_expire() {
}
}
/// Instruct Heimdall to start watching an IP address.
/// You want to call this when you refresh a flow; it will auto-expire
/// in 30 seconds.
pub fn heimdall_watch_ip(ip: XdpIpAddress) {
if HEIMDALL_WATCH_LIST.contains_key(&ip) {
return;

View File

@ -131,6 +131,9 @@ pub fn get_flow_stats(ip: &str) -> BusResponse {
}
}
result.sort_by(|a,b| {
b.0.bytes.cmp(&a.0.bytes)
});
return BusResponse::FlowData(result);
}