From 7fa222ff2e993ffbbf2cf9918df760f2a9927069 Mon Sep 17 00:00:00 2001 From: Rafael David Tinoco Date: Wed, 13 Dec 2023 02:23:07 -0300 Subject: [PATCH] feature(network): add network flow end base for events - add the base for keeping flow states (for future stats) - flow states allow knowing end flows for tcp and udp --- .../builtin/network/net_flow_tcp_end.md | 68 ++++ mkdocs.yml | 1 + pkg/ebpf/c/common/network.h | 96 ++++- pkg/ebpf/c/tracee.bpf.c | 382 ++++++++++++------ pkg/ebpf/c/tracee.h | 5 +- pkg/ebpf/c/types.h | 4 +- pkg/ebpf/tracee.go | 25 +- pkg/events/core.go | 70 ++-- pkg/events/derive/net_flow.go | 165 ++++++++ pkg/events/derive/net_flow_tcp.go | 154 ------- pkg/events/derive/net_packet_helpers.go | 32 +- 11 files changed, 674 insertions(+), 328 deletions(-) create mode 100644 docs/docs/events/builtin/network/net_flow_tcp_end.md create mode 100644 pkg/events/derive/net_flow.go delete mode 100644 pkg/events/derive/net_flow_tcp.go diff --git a/docs/docs/events/builtin/network/net_flow_tcp_end.md b/docs/docs/events/builtin/network/net_flow_tcp_end.md new file mode 100644 index 000000000000..cf5ddb624d91 --- /dev/null +++ b/docs/docs/events/builtin/network/net_flow_tcp_end.md @@ -0,0 +1,68 @@ + +# NetFlowTCPEnd + +## Intro + +NetFlowTCPEnd - An event derived from base network raw event, designed to +monitor the termination of TCP flows. It leverages cgroup skb eBPF programs, +focusing specifically on the TCP protocol's termination phase, and is +instrumental in analyzing IP and TCP headers data to detect the end of TCP +connections. + +## Description + +`NetFlowTCPEnd` utilizes cgroup skb eBPF programs to intercept and analyze raw +network events at the kernel level, with a particular emphasis on the TCP +protocol's termination phase. It processes IP and TCP headers to pinpoint the +conclusion of TCP communication flows. The event identifies the termination of +TCP connections by analyzing the status of TCP flags, primarily focusing on the +FIN and RST flags. + +By examining these flags, `NetFlowTCPEnd` provides valuable insights into the +end of TCP connections, a critical component for comprehensive network +monitoring and security analysis. + +## Arguments + +1. **connectionDirection** (`string`): Indicates whether the terminated connection was 'incoming' or 'outgoing'. +2. **srcIP** (`string`): The source IP address, extracted from the IP header, from the side terminating the connection. +3. **dstIP** (`string`): The destination IP address, obtained from the IP header, of the side receiving the termination. +4. **srcPort** (`uint16`): The source port number, derived from the TCP header. +5. **dstPort** (`uint16`): The destination port number, ascertained from the TCP header. +6. **srcDomains** (`[]string`): Associated domain names for the source IP, resolved using DNS cache. +7. **dstDomains** (`[]string`): Related domain names for the destination IP, determined through DNS cache. + +## Origin + +### Derived from cgroup skb eBPF Programs + +#### Source + +`NetFlowTCPEnd` originates from cgroup skb eBPF programs, enabling the tracing +of raw network packets at the kernel level. This advanced mechanism is adept at +dissecting TCP traffic, particularly focusing on the termination stage of TCP +connections. + +#### Purpose + +The primary aim of `NetFlowTCPEnd` is to provide detailed visibility into the +termination of TCP connections. By concentrating on FIN and RST flags within TCP +headers, it offers an effective and precise approach to identifying the +conclusion of TCP communication flows, crucial for network security and +performance monitoring. + +## Example Use Case + +Network administrators and security experts can use `NetFlowTCPEnd` to monitor +the termination of TCP connections. This capability is essential for detecting +unusual traffic patterns, potential security threats, or abrupt end of +communication, which are vital for ensuring network security and efficiency. + +## Issues + +While `NetFlowTCPEnd` is designed to minimize system overhead, its performance +may vary based on the volume of network traffic and the complexity of monitored +TCP flows. Efficient data management and analysis are key to leveraging the full +potential of this event without affecting system performance adversely. + +> This document was automatically generated by OpenAI and reviewed by a Human. diff --git a/mkdocs.yml b/mkdocs.yml index a66c3d378b23..10bd4f54cb1d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -69,6 +69,7 @@ nav: - Network Events: - Overview: docs/events/builtin/network/index.md - net_flow_tcp_begin: docs/events/builtin/network/net_flow_tcp_begin.md + - net_flow_tcp_end: docs/events/builtin/network/net_flow_tcp_end.md - net_packet_ipv4: docs/events/builtin/network/net_packet_ipv4.md - net_packet_ipv6: docs/events/builtin/network/net_packet_ipv6.md - net_packet_tcp: docs/events/builtin/network/net_packet_tcp.md diff --git a/pkg/ebpf/c/common/network.h b/pkg/ebpf/c/common/network.h index 7c32fb578a54..3beb78ba304d 100644 --- a/pkg/ebpf/c/common/network.h +++ b/pkg/ebpf/c/common/network.h @@ -18,6 +18,65 @@ typedef union iphdrs_t { struct ipv6hdr ipv6hdr; } iphdrs; +// network flow events + +typedef struct netflow { + u32 host_pid; + u8 proto; + union { + __u8 u6_addr8[16]; + __be16 u6_addr16[8]; + __be32 u6_addr32[4]; + } src, dst; + u16 srcport; + u16 dstport; +} __attribute__((__packed__)) netflow_t; + +#define copy_netflow(flow) \ + (netflow_t) { \ + .host_pid = flow.host_pid, \ + .proto = flow.proto, \ + .src = flow.src, \ + .dst = flow.dst, \ + .srcport = flow.srcport, \ + .dstport = flow.dstport, \ + } + +#define invert_netflow(flow) \ + (netflow_t) { \ + .host_pid = flow.host_pid, \ + .proto = flow.proto, \ + .src = flow.dst, \ + .dst = flow.src, \ + .srcport = flow.dstport, \ + .dstport = flow.srcport, \ + } + +#define flow_unknown 0 +#define flow_incoming 1 +#define flow_outgoing 2 + +// TODO: per flow statistics can be added later +typedef struct netflowvalue { + u8 direction; // 0 = flow_unknown, 1 = flow_incoming, 2 = flow_outgoing + u64 last_update; // last time this flow was updated + // u64 bytes; // total bytes sent/received + // u64 packets; // total packets sent/received + // u64 events; // total events sent/received + // u64 last_bytes; // last bytes sent/received + // u64 last_packets; // last packets sent/received + // u64 last_events; // last events sent/received +} __attribute__((__packed__)) netflowvalue_t; + +// netflowmap (keep track of network flows) + +struct { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 65535); // simultaneous network flows being tracked + __type(key, netflow_t); // the network flow ... + __type(value, netflowvalue_t); // ... linked to flow stats +} netflowmap SEC(".maps"); // relate sockets and tasks + // NOTE: proto header structs need full type in vmlinux.h (for correct skb copy) typedef union protohdrs_t { @@ -49,14 +108,13 @@ typedef enum net_packet { // Layer 7 SUB_NET_PACKET_DNS = 1 << 6, SUB_NET_PACKET_HTTP = 1 << 7, - // Flow Events - SUB_NET_PACKET_TCP_FLOW = 1 << 8, } net_packet_t; typedef struct net_event_contextmd { - u8 submit; // keeping this for a TODO (check should_submit_net_event) + u8 should_flow; // Cache result from should_submit_flow_event u32 header_size; - u8 captured; + u8 captured; // packet has already been captured + netflow_t flow; } __attribute__((__packed__)) net_event_contextmd_t; typedef struct net_event_context { @@ -76,8 +134,8 @@ typedef struct net_event_context { typedef struct { u64 ts; u16 ip_csum; - struct in6_addr ip_saddr; - struct in6_addr ip_daddr; + struct in6_addr src; + struct in6_addr dst; } indexer_t; typedef struct { @@ -155,15 +213,23 @@ struct { // CONSTANTS -// network retval values -#define family_ipv4 (1 << 0) -#define family_ipv6 (1 << 1) -#define proto_http_req (1 << 2) -#define proto_http_resp (1 << 3) -#define packet_ingress (1 << 4) -#define packet_egress (1 << 5) -#define flow_tcp_begin (1 << 6) // syn+ack flag or first flow packet -#define flow_tcp_end (1 << 7) // fin flag or last flow packet +// Network return value (retval) codes + +// Layer 3 Protocol (since no Layer 2 is available) +#define family_ipv4 (1 << 0) +#define family_ipv6 (1 << 1) +// HTTP Direction (request/response) Flag +#define proto_http_req (1 << 2) +#define proto_http_resp (1 << 3) +// Packet Direction (ingress/egress) Flag +#define packet_ingress (1 << 4) +#define packet_egress (1 << 5) +// Flows (begin/end) Flags per Protocol +#define flow_tcp_begin (1 << 6) // syn+ack flag or first flow packet +#define flow_tcp_end (1 << 7) // fin flag or last flow packet +#define flow_udp_begin (1 << 8) // first flow packet +#define flow_udp_end (1 << 9) // last flow packet +#define flow_src_initiator (1 << 10) // src is the flow initiator // payload size: full packets, only headers #define FULL 65536 // 1 << 16 diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 9ec5ac7d6e4e..63e29774bb28 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5071,7 +5071,8 @@ statfunc enum event_id_e net_packet_to_net_event(net_packet_t packet_type) { switch (packet_type) { case CAP_NET_PACKET: - return NET_PACKET_CAP_BASE; + return NET_CAPTURE_BASE; + // Packets case SUB_NET_PACKET_IP: return NET_PACKET_IP; case SUB_NET_PACKET_TCP: @@ -5086,39 +5087,67 @@ statfunc enum event_id_e net_packet_to_net_event(net_packet_t packet_type) return NET_PACKET_DNS; case SUB_NET_PACKET_HTTP: return NET_PACKET_HTTP; - case SUB_NET_PACKET_TCP_FLOW: - return NET_PACKET_TCP_FLOW; }; return MAX_EVENT_ID; } -// The address of &neteventctx->eventctx will be aligned as eventctx is the first member of that -// packed struct. This is a false positive as we do need the neteventctx struct to be all packed. +// The address of &neteventctx->eventctx will be aligned as eventctx is the +// first member of that packed struct. This is a false positive as we do need +// the neteventctx struct to be all packed. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Waddress-of-packed-member" -statfunc int should_submit_net_event(net_event_context_t *neteventctx, +// Return if a network event should to be sumitted: if any of the policies +// matched, submit the network event. This means that if any of the policies +// need a network event, kernel can submit the network base event and let +// userland deal with it (derived events will match the appropriate policies). +statfunc u64 should_submit_net_event(net_event_context_t *neteventctx, net_packet_t packet_type) { enum event_id_e evt_id = net_packet_to_net_event(packet_type); - // Check if event has to be sumitted: if any of the policies matched, - // submit. All network events are base events (to be derived in userland). - // This means that if any of the policies need a network event, kernel can - // submit and let userland deal with it. - event_config_t *evt_config = bpf_map_lookup_elem(&events_map, &evt_id); if (evt_config == NULL) return 0; - // Any policy matched is enough to submit the net event return evt_config->submit_for_policies & neteventctx->eventctx.matched_policies; } -#pragma clang diagnostic pop +#pragma clang diagnostic pop // -Waddress-of-packed-member -statfunc int should_capture_net_event(net_event_context_t *neteventctx, - net_packet_t packet_type) +// Return if a network flow event should be submitted. +statfunc bool should_submit_flow_event(net_event_context_t *neteventctx) +{ + switch (neteventctx->md.should_flow) { + case 0: + break; + case 1: + return true; + case 2: + return false; + } + + u32 evt_id = NET_FLOW_BASE; + + // Again, if any policy matched, submit the flow base event so other flow + // events can be derived in userland and their policies matched in userland. + event_config_t *evt_config = bpf_map_lookup_elem(&events_map, &evt_id); + if (evt_config == NULL) + return 0; + + u64 should = evt_config->submit_for_policies & neteventctx->eventctx.matched_policies; + + // Cache the result so next time we don't need to check again. + if (should) + neteventctx->md.should_flow = 1; // cache result: submit flow events + else + neteventctx->md.should_flow = 2; // cache result: don't submit flow events + + return should ? true : false; +} + +// Return if a network capture event should be submitted. +statfunc u64 should_capture_net_event(net_event_context_t *neteventctx, net_packet_t packet_type) { if (neteventctx->md.captured) // already captured return 0; @@ -5153,73 +5182,175 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_icmpv6); // Network submission functions // -// TODO: check if TCP needs a LRU map of sent events by pkt ID (avoid dups) - -statfunc u32 cgroup_skb_submit(void *map, - struct __sk_buff *ctx, +// Submit a network event (packet, capture, flow) to userland. +statfunc u32 cgroup_skb_submit(void *map, struct __sk_buff *ctx, net_event_context_t *neteventctx, - u32 event_type, - u32 size) + u32 event_type, u32 size) { - u64 flags = BPF_F_CURRENT_CPU; - size = size > FULL ? FULL : size; switch (size) { - case HEADERS: + case HEADERS: // submit only headers size = neteventctx->md.header_size; break; - case FULL: + case FULL: // submit full packet size = ctx->len; break; - default: - size += neteventctx->md.header_size; // add headers size - size = size > ctx->len ? ctx->len : size; // check limits + default: // submit size bytes + size += neteventctx->md.header_size; + size = size > ctx->len ? ctx->len : size; break; } - flags |= (u64) size << 32; + // Flag eBPF subsystem to use current CPU and copy size bytes of payload. + u64 flags = BPF_F_CURRENT_CPU | (u64) size << 32; neteventctx->bytes = size; - // set the event type before submitting event + // Set the event type before submitting event. neteventctx->eventctx.eventid = event_type; - return bpf_perf_event_output(ctx, - map, - flags, - neteventctx, - sizeof_net_event_context_t()); + // Submit the event. + return bpf_perf_event_output(ctx, map, flags, neteventctx, sizeof_net_event_context_t()); } -#define cgroup_skb_submit_event(a,b,c,d) cgroup_skb_submit(&events,a,b,c,d) +// Submit a network event. +#define cgroup_skb_submit_event(a, b, c, d) cgroup_skb_submit(&events, a, b, c, d) + +// Check if a flag is set in the retval. +#define retval_hasflag(flag) (neteventctx->eventctx.retval & flag) == flag + +// Keep track of a flow event if they are enabled and if any policy matched. +// Submit the flow base event so userland can derive the flow events. +statfunc u32 cgroup_skb_submit_flow(struct __sk_buff *ctx, + net_event_context_t *neteventctx, + u32 event_type, u32 size, u32 flow) +{ + netflowvalue_t *netflowvalptr, netflowvalue = { + .last_update = bpf_ktime_get_ns(), + .direction = flow_unknown, + }; + + // Set the current netctx task as the flow task. + neteventctx->md.flow.host_pid = neteventctx->eventctx.task.host_pid; + + // Set the flow event type in retval. + neteventctx->eventctx.retval |= flow; + + // Check if the current packet source is the flow initiator. + bool is_initiator = 0; + + switch (flow) { + // 1) TCP connection is being established. + case flow_tcp_begin: + // Ingress: Remote (src) is sending SYN+ACK: this host (dst) is the initiator. + if (retval_hasflag(packet_ingress)) + netflowvalue.direction = flow_outgoing; + + // Egress: Host (src) is sending SYN+ACK: remote (dst) host is the initiator. + if (retval_hasflag(packet_egress)) + netflowvalue.direction = flow_incoming; + + // Invert src/dst: The flowmap src should always be set to flow initiator. + neteventctx->md.flow = invert_netflow(neteventctx->md.flow); + + // Update the flow map. + bpf_map_update_elem(&netflowmap, &neteventctx->md.flow, &netflowvalue, BPF_NOEXIST); + + break; + + // 2) TCP connection is being closed/terminated. + case flow_tcp_end: + // Any side can close the connection (FIN, RST, etc). Need heuristics. + + // Attempt 01: Try to find the flow using current src/dst. + + for (int n = 0; n < 3; n++) { + netflowvalptr = bpf_map_lookup_elem(&netflowmap, &neteventctx->md.flow); + if (!netflowvalptr) + continue; + } + + // FIN could be sent by either side, by both, or by none (RST). Need heuristics. + + if (!netflowvalptr) { + // Attempt 02: Maybe this packet src wasn't the flow initiator, invert src/dst. + neteventctx->md.flow = invert_netflow(neteventctx->md.flow); + + for (int n = 0; n < 3; n++) { + netflowvalptr = bpf_map_lookup_elem(&netflowmap, &neteventctx->md.flow); + if (!netflowvalptr) + continue; + } + + // After first FIN packet is processed the flow is deleted, so the second + // FIN packet, if ever processed, will not find the flow in the map, and + // that is ok. + if (!netflowvalptr) + return 0; + + // Flow was found using inverted src/dst: current pkt dst was the flow initiator. + is_initiator = 0; + + } else { + // Flow was found using current src/dst: current pkt src was the flow initiator. + is_initiator = 1; + } + + // Pick direction from existing flow. + netflowvalue.direction = netflowvalptr->direction; -// TODO: check if TCP needs a LRU map of sent events by pkt ID (avoid dups) + // Inform userland the flow being terminated started by current packet src. + // This is important so userland knows how to report flow termination correctly. + if (is_initiator) + neteventctx->eventctx.retval |= flow_src_initiator; + // Delete the flow from the map (make sure to delete both sides). + bpf_map_delete_elem(&netflowmap, &neteventctx->md.flow); + neteventctx->md.flow = invert_netflow(neteventctx->md.flow); + bpf_map_delete_elem(&netflowmap, &neteventctx->md.flow); + + break; + + // 3) TODO: UDP flow is considered started when the first packet is sent. + // case flow_udp_begin: + // + // 4) TODO: UDP flow is considered terminated when socket is closed. + // case flow_udp_end: + // + default: + return 0; + }; + + // Submit the flow base event so userland can derive the flow events. + cgroup_skb_submit(&events, ctx, neteventctx, event_type, size); + + return 0; +}; + +// Check if capture event should be submitted, cache the result and submit. +#define cgroup_skb_capture() \ + { \ + if (should_submit_net_event(neteventctx, CAP_NET_PACKET)) { \ + if (neteventctx->md.captured == 0) { \ + cgroup_skb_capture_event(ctx, neteventctx, NET_CAPTURE_BASE); \ + neteventctx->md.captured = 1; \ + } \ + } \ + } + +// Check if packet should be captured and submit the capture base event. statfunc u32 cgroup_skb_capture_event(struct __sk_buff *ctx, net_event_context_t *neteventctx, u32 event_type) { int zero = 0; - // pick network config map to know requested capture length + // Pick the network config map to know the requested capture length. netconfig_entry_t *nc = bpf_map_lookup_elem(&netconfig_map, &zero); if (nc == NULL) return 0; - return cgroup_skb_submit(&net_cap_events, - ctx, - neteventctx, - event_type, - nc->capture_length); -} - -// capture packet a single time (if passing through multiple protocols being submitted to userland) -#define cgroup_skb_capture() { \ - if (should_submit_net_event(neteventctx, CAP_NET_PACKET)) { \ - if (neteventctx->md.captured == 0) { /* do not capture the same packet twice */ \ - cgroup_skb_capture_event(ctx, neteventctx, NET_PACKET_CAP_BASE); \ - neteventctx->md.captured = 1; \ - } \ - } \ + // Submit the capture base event. + return cgroup_skb_submit(&net_cap_events, ctx, neteventctx, event_type, nc->capture_length); } // @@ -5366,7 +5497,7 @@ int BPF_KPROBE(trace_security_sk_clone) if (!netctx) return 0; // e.g. task isn't being traced - u64 nsockptr = (u64)(void *) nsock; + u64 nsockptr = (u64) (void *) nsock; // link the new "sock" to the old inode, so it can be linked to a task later @@ -5598,28 +5729,26 @@ int BPF_KPROBE(cgroup_bpf_run_filter_skb) } // ... and packet direction(ingress/egress) ... - eventctx->retval |= packet_dir_flag; // set to packet_ingress/egress beforehand - - // ... through event ctx ret val - - // read IP/IPv6 headers + eventctx->retval |= packet_dir_flag; + // ... through event ctx ret val. + // Read packet headers from the skb. void *data_ptr = BPF_CORE_READ(skb, head) + BPF_CORE_READ(skb, network_header); bpf_core_read(nethdrs, l3_size, data_ptr); - // prepare the indexer with IP/IPv6 headers - - u8 proto = 0; - + // Prepare the inter-eBPF-program indexer. indexer_t indexer = {0}; indexer.ts = BPF_CORE_READ(skb, tstamp); + u8 proto = 0; + + // Parse the packet layer 3 headers. switch (family) { case PF_INET: if (nethdrs->iphdrs.iphdr.version != 4) // IPv4 return 1; - if (nethdrs->iphdrs.iphdr.ihl > 5) { // re-read IP header if needed + if (nethdrs->iphdrs.iphdr.ihl > 5) { // re-read IP header if needed l3_size -= get_type_size(struct iphdr); l3_size += nethdrs->iphdrs.iphdr.ihl * 4; bpf_core_read(nethdrs, l3_size, data_ptr); @@ -5635,10 +5764,10 @@ int BPF_KPROBE(cgroup_bpf_run_filter_skb) return 1; // ignore other protocols } - // add IPv4 header items to indexer + // Update inter-eBPF-program indexer with IPv4 header items. indexer.ip_csum = nethdrs->iphdrs.iphdr.check; - indexer.ip_saddr.in6_u.u6_addr32[0] = nethdrs->iphdrs.iphdr.saddr; - indexer.ip_daddr.in6_u.u6_addr32[0] = nethdrs->iphdrs.iphdr.daddr; + indexer.src.in6_u.u6_addr32[0] = nethdrs->iphdrs.iphdr.saddr; + indexer.dst.in6_u.u6_addr32[0] = nethdrs->iphdrs.iphdr.daddr; break; case PF_INET6: @@ -5657,9 +5786,9 @@ int BPF_KPROBE(cgroup_bpf_run_filter_skb) return 1; // ignore other protocols } - // add IPv6 header items to indexer - __builtin_memcpy(&indexer.ip_saddr.in6_u, &nethdrs->iphdrs.ipv6hdr.saddr.in6_u, 4 * sizeof(u32)); - __builtin_memcpy(&indexer.ip_daddr.in6_u, &nethdrs->iphdrs.ipv6hdr.daddr.in6_u, 4 * sizeof(u32)); + // Update inter-eBPF-program indexer with IPv6 header items. + __builtin_memcpy(&indexer.src.in6_u, &nethdrs->iphdrs.ipv6hdr.saddr.in6_u, 4 * sizeof(u32)); + __builtin_memcpy(&indexer.dst.in6_u, &nethdrs->iphdrs.ipv6hdr.daddr.in6_u, 4 * sizeof(u32)); break; default: @@ -5760,8 +5889,8 @@ statfunc u32 cgroup_skb_generic(struct __sk_buff *ctx, void *cgrpctxmap) // add IPv4 header items to indexer indexer.ip_csum = nethdrs->iphdrs.iphdr.check; - indexer.ip_saddr.in6_u.u6_addr32[0] = nethdrs->iphdrs.iphdr.saddr; - indexer.ip_daddr.in6_u.u6_addr32[0] = nethdrs->iphdrs.iphdr.daddr; + indexer.src.in6_u.u6_addr32[0] = nethdrs->iphdrs.iphdr.saddr; + indexer.dst.in6_u.u6_addr32[0] = nethdrs->iphdrs.iphdr.daddr; break; case PF_INET6: @@ -5780,8 +5909,8 @@ statfunc u32 cgroup_skb_generic(struct __sk_buff *ctx, void *cgrpctxmap) } // add IPv6 header items to indexer - __builtin_memcpy(&indexer.ip_saddr.in6_u, &nethdrs->iphdrs.ipv6hdr.saddr.in6_u, 4 * sizeof(u32)); - __builtin_memcpy(&indexer.ip_daddr.in6_u, &nethdrs->iphdrs.ipv6hdr.daddr.in6_u, 4 * sizeof(u32)); + __builtin_memcpy(&indexer.src.in6_u, &nethdrs->iphdrs.ipv6hdr.saddr.in6_u, 4 * sizeof(u32)); + __builtin_memcpy(&indexer.dst.in6_u, &nethdrs->iphdrs.ipv6hdr.daddr.in6_u, 4 * sizeof(u32)); break; default: @@ -5837,7 +5966,6 @@ CGROUP_SKB_HANDLE_FUNCTION(proto) // NOTE: might block IP and IPv6 here if needed (return 0) switch (ctx->family) { - case PF_INET: if (nethdrs->iphdrs.iphdr.version != 4) // IPv4 return 1; @@ -5859,6 +5987,10 @@ CGROUP_SKB_HANDLE_FUNCTION(proto) default: return 1; // other protocols are not an error } + + // Update the network flow map indexer with the packet headers. + neteventctx->md.flow.src.u6_addr32[0] = nethdrs->iphdrs.iphdr.saddr; + neteventctx->md.flow.dst.u6_addr32[0] = nethdrs->iphdrs.iphdr.daddr; break; case PF_INET6: @@ -5884,12 +6016,19 @@ CGROUP_SKB_HANDLE_FUNCTION(proto) default: return 1; // other protocols are not an error } + + // Update the network flow map indexer with the packet headers. + __builtin_memcpy(&neteventctx->md.flow.src, &nethdrs->iphdrs.ipv6hdr.saddr.in6_u, 4 * sizeof(u32)); + __builtin_memcpy(&neteventctx->md.flow.dst, &nethdrs->iphdrs.ipv6hdr.daddr.in6_u, 4 * sizeof(u32)); break; default: - return 1; // verifier needs + return 1; // verifier needs as this was already checked } + // Update the network flow map indexer with the packet headers. + neteventctx->md.flow.proto = next_proto; + if (!dest) return 1; // satisfy verifier for clang-12 generated binaries @@ -5908,20 +6047,16 @@ CGROUP_SKB_HANDLE_FUNCTION(proto) if (!(nc->capture_options & NET_CAP_OPT_FILTERED)) cgroup_skb_capture(); // will avoid extra lookups further if not needed - neteventctx->md.header_size += size; // add header size to offset - - // load layer 4 protocol headers + // Update the network event context with payload size. + neteventctx->md.header_size += size; + // Load the next protocol header. if (size) { - if (bpf_skb_load_bytes_relative(ctx, - prev_hdr_size, - dest, size, - BPF_HDR_START_NET)) + if (bpf_skb_load_bytes_relative(ctx, prev_hdr_size, dest, size, BPF_HDR_START_NET)) return 1; } - // call protocol handlers (for more base events to be sent) - + // Call the next protocol handler. switch (next_proto) { case IPPROTO_TCP: return CGROUP_SKB_HANDLE(proto_tcp); @@ -5940,7 +6075,7 @@ CGROUP_SKB_HANDLE_FUNCTION(proto) // applied to the capture pipeline to obey derived events only // filters + capture. - // capture IPv4/IPv6 packets (filtered) + // Capture IPv4/IPv6 packets (filtered). if (should_capture_net_event(neteventctx, SUB_NET_PACKET_IP)) cgroup_skb_capture(); @@ -5971,9 +6106,9 @@ statfunc int net_l7_is_http(struct __sk_buff *skb, u32 l7_off) } // check if HTTP request - if (has_prefix("GET ", http_min_str, 5) || - has_prefix("POST ", http_min_str, 6) || - has_prefix("PUT ", http_min_str, 5) || + if (has_prefix("GET ", http_min_str, 5) || + has_prefix("POST ", http_min_str, 6) || + has_prefix("PUT ", http_min_str, 5) || has_prefix("DELETE ", http_min_str, 8) || has_prefix("HEAD ", http_min_str, 6)) { return proto_http_req; @@ -5988,7 +6123,7 @@ statfunc int net_l7_is_http(struct __sk_buff *skb, u32 l7_off) CGROUP_SKB_HANDLE_FUNCTION(proto_tcp) { - // check flag for dynamic header size (TCP: data offset flag) + // Check TCP header flag for dynamic header size (TCP: data offset flag). if (nethdrs->protohdrs.tcphdr.doff > 5) { // offset flag set u32 doff = nethdrs->protohdrs.tcphdr.doff * (32 / 8); @@ -5996,42 +6131,49 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_tcp) neteventctx->md.header_size += doff; } - // Check if TCP flow started or ended and submit appropriate event if needed. + // Pick src/dst ports. -#define is_syn_ack nethdrs->protohdrs.tcphdr.syn && nethdrs->protohdrs.tcphdr.ack -#define is_fin nethdrs->protohdrs.tcphdr.fin -#define flow_sub should_submit_net_event(neteventctx, SUB_NET_PACKET_TCP_FLOW) + u16 srcport = bpf_ntohs(nethdrs->protohdrs.tcphdr.source); + u16 dstport = bpf_ntohs(nethdrs->protohdrs.tcphdr.dest); - if (is_syn_ack && flow_sub) { - // neteventctx->eventctx.retval |= is_syn_ack ? flow_tcp_begin : flow_tcp_end; - neteventctx->eventctx.retval |= flow_tcp_begin; - cgroup_skb_submit_event(ctx, neteventctx, NET_PACKET_TCP_FLOW, HEADERS); - } + // Update the network flow map indexer with the packet headers. + neteventctx->md.flow.srcport = srcport; + neteventctx->md.flow.dstport = dstport; + + // Check if TCP flow needs to be submitted (only headers). + + bool is_rst = nethdrs->protohdrs.tcphdr.rst; + bool is_syn = nethdrs->protohdrs.tcphdr.syn; + bool is_ack = nethdrs->protohdrs.tcphdr.ack; + bool is_fin = nethdrs->protohdrs.tcphdr.fin; + + // Has TCP flow started ? + if ((is_syn & is_ack) && should_submit_flow_event(neteventctx)) + cgroup_skb_submit_flow(ctx, neteventctx, NET_FLOW_BASE, HEADERS, flow_tcp_begin); + + // Has TCP flow ended ? + if ((is_fin || is_rst) && should_submit_flow_event(neteventctx)) + cgroup_skb_submit_flow(ctx, neteventctx, NET_FLOW_BASE, HEADERS, flow_tcp_end); - // submit TCP base event if needed (only headers) + // Submit TCP base event if needed (only headers) if (should_submit_net_event(neteventctx, SUB_NET_PACKET_TCP)) cgroup_skb_submit_event(ctx, neteventctx, NET_PACKET_TCP, HEADERS); - // fastpath: return if no other L7 network events + // Fastpath: return if no other L7 network events. if (!should_submit_net_event(neteventctx, SUB_NET_PACKET_DNS) && !should_submit_net_event(neteventctx, SUB_NET_PACKET_HTTP)) goto capture; - // guess layer 7 protocols + // Guess layer 7 protocols by src/dst ports ... - u16 source = bpf_ntohs(nethdrs->protohdrs.tcphdr.source); - u16 dest = bpf_ntohs(nethdrs->protohdrs.tcphdr.dest); - - // guess by src/dst ports - - switch (source < dest ? source : dest) { + switch (srcport < dstport ? srcport : dstport) { case TCP_PORT_DNS: return CGROUP_SKB_HANDLE(proto_tcp_dns); } - // guess by analyzing payload + // ... and by analyzing payload. int http_proto = net_l7_is_http(ctx, neteventctx->md.header_size); if (http_proto) { @@ -6039,11 +6181,10 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_tcp) return CGROUP_SKB_HANDLE(proto_tcp_http); } - // continue with net_l7_is_protocol_xxx - // ... + // ... continue with net_l7_is_protocol_xxx capture: - // capture IP or TCP packets (filtered) + // Capture IP or TCP packets (filtered) if (should_capture_net_event(neteventctx, SUB_NET_PACKET_IP) || should_capture_net_event(neteventctx, SUB_NET_PACKET_TCP)) { cgroup_skb_capture(); @@ -6054,37 +6195,36 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_tcp) CGROUP_SKB_HANDLE_FUNCTION(proto_udp) { - // submit UDP base event if needed (only headers) + // Submit UDP base event if needed (only headers). if (should_submit_net_event(neteventctx, SUB_NET_PACKET_UDP)) cgroup_skb_submit_event(ctx, neteventctx, NET_PACKET_UDP, HEADERS); - // fastpath: return if no other L7 network events + // Fastpath: return if no other L7 network events. if (!should_submit_net_event(neteventctx, SUB_NET_PACKET_DNS) && !should_submit_net_event(neteventctx, SUB_NET_PACKET_HTTP)) goto capture; - // guess layer 7 protocols + // Guess layer 7 protocols ... u16 source = bpf_ntohs(nethdrs->protohdrs.udphdr.source); u16 dest = bpf_ntohs(nethdrs->protohdrs.udphdr.dest); - // guess by src/dst ports + // ... by src/dst ports switch (source < dest ? source : dest) { case UDP_PORT_DNS: return CGROUP_SKB_HANDLE(proto_udp_dns); } - // guess by analyzing payload + // ... by analyzing payload // ... - // continue with net_l7_is_protocol_xxx - // ... + // ... continue with net_l7_is_protocol_xxx capture: - // capture IP or UDP packets (filtered) + // Capture IP or UDP packets (filtered). if (should_capture_net_event(neteventctx, SUB_NET_PACKET_IP) || should_capture_net_event(neteventctx, SUB_NET_PACKET_UDP)) { cgroup_skb_capture(); @@ -6115,7 +6255,7 @@ CGROUP_SKB_HANDLE_FUNCTION(proto_icmpv6) if (should_submit_net_event(neteventctx, SUB_NET_PACKET_ICMPV6)) cgroup_skb_submit_event(ctx, neteventctx, NET_PACKET_ICMPV6, FULL); - // capture ip or icmpv6 packets (filtered) + // capture ip or icmpv6 packets (filtered) if (should_capture_net_event(neteventctx, SUB_NET_PACKET_IP) || should_capture_net_event(neteventctx, SUB_NET_PACKET_ICMPV6)) { neteventctx->md.header_size = ctx->len; // full ICMPv6 header diff --git a/pkg/ebpf/c/tracee.h b/pkg/ebpf/c/tracee.h index 6645557eb7f5..873ed1c4c367 100644 --- a/pkg/ebpf/c/tracee.h +++ b/pkg/ebpf/c/tracee.h @@ -13,8 +13,9 @@ statfunc bool is_socket_supported(struct socket *); statfunc u64 sizeof_net_event_context_t(void); statfunc void set_net_task_context(event_data_t *, net_task_context_t *); statfunc enum event_id_e net_packet_to_net_event(net_packet_t); -statfunc int should_submit_net_event(net_event_context_t *, net_packet_t); -statfunc int should_capture_net_event(net_event_context_t *, net_packet_t); +statfunc u64 should_submit_net_event(net_event_context_t *, net_packet_t); +statfunc bool should_submit_flow_event(net_event_context_t *); +statfunc u64 should_capture_net_event(net_event_context_t *, net_packet_t); statfunc u32 cgroup_skb_generic(struct __sk_buff *, void *); statfunc int net_l7_is_http(struct __sk_buff *, u32); statfunc u32 update_net_inodemap(struct socket *, event_data_t *); diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index 9547f1dbd83d..2a6d3bf901b5 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -43,13 +43,13 @@ enum event_id_e NET_PACKET_BASE = 700, NET_PACKET_IP, NET_PACKET_TCP, - NET_PACKET_TCP_FLOW, NET_PACKET_UDP, NET_PACKET_ICMP, NET_PACKET_ICMPV6, NET_PACKET_DNS, NET_PACKET_HTTP, - NET_PACKET_CAP_BASE, + NET_CAPTURE_BASE, + NET_FLOW_BASE, MAX_NET_EVENT_ID, // Common event IDs RAW_SYS_ENTER, diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index 96ac71fcab84..c14f50694cd6 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -688,14 +688,6 @@ func (t *Tracee) initDerivationTable() error { DeriveFunction: derive.NetPacketTCP(), }, }, - events.NetPacketTCPFlowBase: { - events.NetFlowTCPBegin: { - Enabled: shouldSubmit(events.NetFlowTCPBegin), - DeriveFunction: derive.NetFlowTCPBegin( - t.dnsCache, - ), - }, - }, events.NetPacketUDPBase: { events.NetPacketUDP: { Enabled: shouldSubmit(events.NetPacketUDP), @@ -742,6 +734,23 @@ func (t *Tracee) initDerivationTable() error { DeriveFunction: derive.NetPacketHTTPResponse(), }, }, + // + // Network Flow Derivations + // + events.NetPacketFlow: { + events.NetFlowTCPBegin: { + Enabled: shouldSubmit(events.NetFlowTCPBegin), + DeriveFunction: derive.NetFlowTCPBegin( + t.dnsCache, + ), + }, + events.NetFlowTCPEnd: { + Enabled: shouldSubmit(events.NetFlowTCPEnd), + DeriveFunction: derive.NetFlowTCPEnd( + t.dnsCache, + ), + }, + }, } return nil diff --git a/pkg/events/core.go b/pkg/events/core.go index 8b733ac4d66b..85301e4168ba 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -26,13 +26,13 @@ const ( NetPacketBase ID = iota + 700 NetPacketIPBase NetPacketTCPBase - NetPacketTCPFlowBase NetPacketUDPBase NetPacketICMPBase NetPacketICMPv6Base NetPacketDNSBase NetPacketHTTPBase NetPacketCapture + NetPacketFlow MaxNetID // network base events go ABOVE this item SysEnter SysExit @@ -122,7 +122,9 @@ const ( NetPacketHTTP NetPacketHTTPRequest NetPacketHTTPResponse + NetFlowEnd NetFlowTCPBegin + NetFlowTCPEnd MaxUserNetID NetTCPConnect InitNamespaces @@ -11371,22 +11373,6 @@ var CoreEvents = map[ID]Definition{ {Type: "bytes", Name: "payload"}, }, }, - NetPacketTCPFlowBase: { - id: NetPacketTCPFlowBase, - id32Bit: Sys32Undefined, - name: "net_packet_tcp_flow_base", - version: NewVersion(1, 0, 0), - internal: true, - dependencies: Dependencies{ - ids: []ID{ - NetPacketBase, - }, - }, - sets: []string{"network_events"}, - params: []trace.ArgMeta{ - {Type: "bytes", Name: "payload"}, - }, - }, NetPacketTCP: { id: NetPacketTCP, id32Bit: Sys32Undefined, @@ -11648,7 +11634,7 @@ var CoreEvents = map[ID]Definition{ }, }, NetPacketCapture: { - id: NetPacketCapture, // all packets have full payload (sent in a dedicated perfbuffer) + id: NetPacketCapture, // Packets with full payload (sent in a dedicated perfbuffer) id32Bit: Sys32Undefined, name: "net_packet_capture", version: NewVersion(1, 0, 0), @@ -11663,7 +11649,7 @@ var CoreEvents = map[ID]Definition{ }, }, CaptureNetPacket: { - id: CaptureNetPacket, // network packet capture pseudo event + id: CaptureNetPacket, // Pseudo Event: used to capture packets id32Bit: Sys32Undefined, name: "capture_net_packet", version: NewVersion(1, 0, 0), @@ -11674,6 +11660,22 @@ var CoreEvents = map[ID]Definition{ }, }, }, + NetPacketFlow: { + id: NetPacketFlow, + id32Bit: Sys32Undefined, + name: "net_packet_flow_base", + version: NewVersion(1, 0, 0), + internal: true, + dependencies: Dependencies{ + ids: []ID{ + NetPacketBase, + }, + }, + sets: []string{"network_events"}, + params: []trace.ArgMeta{ + {Type: "bytes", Name: "payload"}, + }, + }, NetFlowTCPBegin: { id: NetFlowTCPBegin, id32Bit: Sys32Undefined, @@ -11681,12 +11683,33 @@ var CoreEvents = map[ID]Definition{ version: NewVersion(1, 0, 0), dependencies: Dependencies{ ids: []ID{ - NetPacketTCPFlowBase, + NetPacketFlow, }, }, - sets: []string{"network_events", "flows", "egress"}, + sets: []string{"network_events", "flows"}, params: []trace.ArgMeta{ - {Type: "const char*", Name: "direction"}, + {Type: "const char*", Name: "conn_direction"}, + {Type: "const char*", Name: "src"}, + {Type: "const char*", Name: "dst"}, + {Type: "u16", Name: "src_port"}, + {Type: "u16", Name: "dst_port"}, + {Type: "const char **", Name: "src_dns"}, + {Type: "const char **", Name: "dst_dns"}, + }, + }, + NetFlowTCPEnd: { + id: NetFlowTCPEnd, + id32Bit: Sys32Undefined, + name: "net_flow_tcp_end", + version: NewVersion(1, 0, 0), + dependencies: Dependencies{ + ids: []ID{ + NetPacketFlow, + }, + }, + sets: []string{"network_events", "flows"}, + params: []trace.ArgMeta{ + {Type: "const char*", Name: "conn_direction"}, {Type: "const char*", Name: "src"}, {Type: "const char*", Name: "dst"}, {Type: "u16", Name: "src_port"}, @@ -11695,7 +11718,4 @@ var CoreEvents = map[ID]Definition{ {Type: "const char **", Name: "dst_dns"}, }, }, - // - // End of Network Protocol Event Types (keep them at the end) - // } diff --git a/pkg/events/derive/net_flow.go b/pkg/events/derive/net_flow.go new file mode 100644 index 000000000000..fa302e6822e6 --- /dev/null +++ b/pkg/events/derive/net_flow.go @@ -0,0 +1,165 @@ +package derive + +import ( + "github.com/aquasecurity/tracee/pkg/dnscache" + "github.com/aquasecurity/tracee/pkg/events" + "github.com/aquasecurity/tracee/pkg/logger" + "github.com/aquasecurity/tracee/types/trace" +) + +func NetFlowTCPBegin(cache *dnscache.DNSCache) DeriveFunction { + return deriveSingleEvent(events.NetFlowTCPBegin, + func(event trace.Event) ([]interface{}, error) { + tcpBegin := event.ReturnValue&flowTCPBegin == flowTCPBegin + ingress := event.ReturnValue&packetIngress == packetIngress + egress := event.ReturnValue&packetEgress == packetEgress + + // Sanity check + if (!ingress && !egress) || (ingress && egress) { + logger.Debugw("wrong flow direction", "id", event.EventID) + return nil, nil + } + // Return if not a TCP begin flow event + if !tcpBegin { + return nil, nil + } + + // Get the packet from the event + packet, err := createPacketFromEvent(&event) + if err != nil { + return nil, err + } + // Sanity check + proto, _ := getLayer4ProtoFromPacket(packet) + if proto != IPPROTO_TCP { + return nil, nil // not an tcp packet + } + // Get the packet src and dst IPs + srcIP, dstIP, err := getLayer3SrcDstFromPacket(packet) + if err != nil { + return nil, err + } + // Get the packet src and dst ports + srcPort, dstPort, err := getLayer4SrcPortDstPortFromPacket(packet) + if err != nil { + return nil, err + } + + connectionDirection := "" + switch { + case ingress: + connectionDirection = "outgoing" // SYN+ACK is incoming, connection is outcoming + case egress: + connectionDirection = "incoming" // SYN+ACK is outgoing, connection is incoming + } + + // Packet is SYN_ACK, swap src and dst IPs and ports to get connection orientation. + srcIP, dstIP, srcPort, dstPort = swapSrcDst(srcIP, dstIP, srcPort, dstPort) + + // Pick the src and dst IP addresses domain names from the DNS cache + srcDomains := getDomainsFromCache(srcIP, cache) + dstDomains := getDomainsFromCache(dstIP, cache) + + // Return the derived event arguments. + return []interface{}{ + connectionDirection, + srcIP, + dstIP, + srcPort, + dstPort, + srcDomains, + dstDomains, + }, nil + }, + ) +} + +func NetFlowTCPEnd(cache *dnscache.DNSCache) DeriveFunction { + return deriveSingleEvent(events.NetFlowTCPEnd, + func(event trace.Event) ([]interface{}, error) { + tcpEnd := event.ReturnValue&flowTCPEnd == flowTCPEnd + ingress := event.ReturnValue&packetIngress == packetIngress + egress := event.ReturnValue&packetEgress == packetEgress + srcInitiated := event.ReturnValue&flowSrcInitiator == flowSrcInitiator + + // Sanity check + if (!ingress && !egress) || (ingress && egress) { + logger.Debugw("wrong flow direction", "id", event.EventID) + return nil, nil + } + // Return if not a TCP end flow event + if !tcpEnd { + return nil, nil + } + + // Get the packet from the event + packet, err := createPacketFromEvent(&event) + if err != nil { + return nil, err + } + // Sanity check + proto, _ := getLayer4ProtoFromPacket(packet) + if proto != IPPROTO_TCP { + return nil, nil // not an tcp packet + } + // Get the packet src and dst IPs + srcIP, dstIP, err := getLayer3SrcDstFromPacket(packet) + if err != nil { + return nil, err + } + // Get the packet src and dst ports + srcPort, dstPort, err := getLayer4SrcPortDstPortFromPacket(packet) + if err != nil { + return nil, err + } + + connectionDirection := "" + if srcInitiated { + if ingress { + connectionDirection = "incoming" + } else { + connectionDirection = "outgoing" + } + } else { + if ingress { + connectionDirection = "outgoing" + } else { + connectionDirection = "incoming" + } + // Swap src and dst IPs and ports to get proper flow orientation. + srcIP, dstIP, srcPort, dstPort = swapSrcDst(srcIP, dstIP, srcPort, dstPort) + } + + // Pick the src and dst IP addresses domain names from the DNS cache + srcDomains := getDomainsFromCache(srcIP, cache) + dstDomains := getDomainsFromCache(dstIP, cache) + + // Return the derived event arguments. + return []interface{}{ + connectionDirection, + srcIP, + dstIP, + srcPort, + dstPort, + srcDomains, + dstDomains, + }, nil + }, + ) +} + +// func NetFlowUDPBegin(cache *dnscache.DNSCache) DeriveFunction { +// return deriveSingleEvent(events.NetFlowUDPBegin, +// func(event trace.Event) ([]interface{}, error) { +// return nil, nil +// }, +// ) +// } + +// func NetFlowUDPEnd(cache *dnscache.DNSCache) DeriveFunction { +// return deriveSingleEvent(events.NetFlowUDPBegin, +// func(event trace.Event) ([]interface{}, error) { +// return nil, nil +// }, +// ) +// } diff --git a/pkg/events/derive/net_flow_tcp.go b/pkg/events/derive/net_flow_tcp.go deleted file mode 100644 index 71593c359022..000000000000 --- a/pkg/events/derive/net_flow_tcp.go +++ /dev/null @@ -1,154 +0,0 @@ -package derive - -import ( - "net" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - - "github.com/aquasecurity/tracee/pkg/dnscache" - "github.com/aquasecurity/tracee/pkg/events" - "github.com/aquasecurity/tracee/pkg/logger" - "github.com/aquasecurity/tracee/types/trace" -) - -func NetFlowTCPBegin(cache *dnscache.DNSCache) DeriveFunction { - return deriveSingleEvent(events.NetFlowTCPBegin, deriveNetFlowTCPBeginArgs(cache)) -} - -func deriveNetFlowTCPBeginArgs(cache *dnscache.DNSCache) deriveArgsFunction { - return func(event trace.Event) ([]interface{}, error) { - ret := event.ReturnValue - - pktDirection := getPacketDirection(&event) - ingress := pktDirection == trace.PacketIngress - egress := pktDirection == trace.PacketEgress - - begin := ret&flowTCPBegin == flowTCPBegin - end := ret&flowTCPEnd == flowTCPEnd - - ipv4 := ret&familyIPv4 == familyIPv4 - ipv6 := ret&familyIPv6 == familyIPv6 - - // Return fast if not the proper event (egress/ingress, begin/end). - - if !begin && !end { - logger.Debugw("not a TCP flow event", "id", event.EventID) - return nil, nil - } - if !ingress && !egress { - logger.Debugw("wrong flow direction", "id", event.EventID) - return nil, nil - } - if !ipv4 && !ipv6 { - logger.Debugw("base layer type not supported", "id", event.EventID) - return nil, nil - } - - // Proper event, now parse the packet. - - var srcIP, dstIP net.IP - var srcPort, dstPort uint16 - var layerType gopacket.LayerType - - payload, err := parsePayloadArg(&event) - if err != nil { - return nil, err - } - - // Event return value encodes layer 3 protocol type. - - if ipv4 { - layerType = layers.LayerTypeIPv4 - } else if ipv6 { - layerType = layers.LayerTypeIPv6 - } - - // Parse the packet. - - packet := gopacket.NewPacket( - payload, - layerType, - gopacket.Default, - ) - if packet == nil { - return []interface{}{}, parsePacketError() - } - - layer3 := packet.NetworkLayer() - - switch v := layer3.(type) { - case (*layers.IPv4): - srcIP = v.SrcIP - dstIP = v.DstIP - case (*layers.IPv6): - srcIP = v.SrcIP - dstIP = v.DstIP - default: - return nil, nil - } - - layer4 := packet.TransportLayer() - - switch l4 := layer4.(type) { - case (*layers.TCP): - srcPort = uint16(l4.SrcPort) - dstPort = uint16(l4.DstPort) - default: - return nil, notProtoPacketError("TCP") - } - - connectionDirection := "" - - // Begin means current packet has either SYN or SYN + ACK flags set. - // The packet src and dst are the connection dst and src respectively. - if begin { - if ingress { - connectionDirection = "outgoing" - } else { - connectionDirection = "incoming" - } - srcIP, dstIP, srcPort, dstPort = shift(srcIP, dstIP, srcPort, dstPort) - } - - // Pick the src and dst IP addresses domain names from the DNS cache - srcDomains := getDomainsFromCache(srcIP, cache) - dstDomains := getDomainsFromCache(dstIP, cache) - - // Return the derived event arguments. - return []interface{}{ - connectionDirection, - srcIP, - dstIP, - srcPort, - dstPort, - srcDomains, - dstDomains, - }, nil - } -} - -// shift swaps the source and destination IP addresses and ports. -func shift(s, d net.IP, sp, dp uint16) (net.IP, net.IP, uint16, uint16) { - return d, s, dp, sp -} - -// getDomainsFromCache returns the domain names of an IP address from the DNS cache. -func getDomainsFromCache(ip net.IP, cache *dnscache.DNSCache) []string { - domains := []string{} - if cache != nil { - query, err := cache.Get(ip.String()) - if err != nil { - switch err { - case dnscache.ErrDNSRecordNotFound, dnscache.ErrDNSRecordExpired: - domains = []string{} - default: - logger.Debugw("ip lookup error", "ip", ip, "error", err) - return nil - } - } else { - domains = query.DNSResults() - } - } - return domains -} diff --git a/pkg/events/derive/net_packet_helpers.go b/pkg/events/derive/net_packet_helpers.go index 72a0533e4fe5..eee835714000 100644 --- a/pkg/events/derive/net_packet_helpers.go +++ b/pkg/events/derive/net_packet_helpers.go @@ -11,7 +11,9 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" + "github.com/aquasecurity/tracee/pkg/dnscache" "github.com/aquasecurity/tracee/pkg/events" + "github.com/aquasecurity/tracee/pkg/logger" "github.com/aquasecurity/tracee/types/trace" ) @@ -30,7 +32,10 @@ const ( packetIngress packetEgress flowTCPBegin - flowTCPEnd // TODO: will be implemented soon + flowTCPEnd + flowUDPBegin + flowUDPEnd + flowSrcInitiator ) const httpMinLen int = 7 // longest http command is "DELETE " @@ -106,6 +111,11 @@ func getPktMeta(srcIP, dstIP net.IP, srcPort, dstPort uint16, proto uint8, lengt } } +// swapSrcDst swaps the source and destination IP addresses and ports. +func swapSrcDst(s, d net.IP, sp, dp uint16) (net.IP, net.IP, uint16, uint16) { + return d, s, dp, sp +} + // getPacketDirection returns the packet direction from the event. func getPacketDirection(event *trace.Event) trace.PacketDirection { switch { @@ -155,6 +165,26 @@ func createPacketFromEvent(event *trace.Event) (gopacket.Packet, error) { return packet, nil } +// getDomainsFromCache returns the domain names of an IP address from the DNS cache. +func getDomainsFromCache(ip net.IP, cache *dnscache.DNSCache) []string { + domains := []string{} + if cache != nil { + query, err := cache.Get(ip.String()) + if err != nil { + switch err { + case dnscache.ErrDNSRecordNotFound, dnscache.ErrDNSRecordExpired: + domains = []string{} + default: + logger.Debugw("ip lookup error", "ip", ip, "error", err) + return nil + } + } else { + domains = query.DNSResults() + } + } + return domains +} + // // Layer 3 (Network Layer) //