I am trying to record TCP flows - if new, create an entry into a hash map with the 5-tuple as the key and then push out a new flow event (identified by SYN flag).
The problem I encounter is with the function bpf_map_update_elem()
. When I use this function, all the member values of the structures are rendered as zero. However, when I disable this function (do not add new flows in the hash map), the values of the member variables are as expected.
struct flow_state
{
__u64 t_start;
__u64 t_end;
struct network_tuple network_tuple;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, struct network_tuple);
__type(value, struct flow_state);
__uint(max_entries, 16384);
} flow_state_map SEC(".maps");
static __always_inline int
update_flow_state(struct pkt_info *pkt, __u32 flow_status)
{
// bpf_printk("flow last seq2: %llu", pkt->tcp_seq);
// bpf_printk("dport121: %d", bpf_ntohs(pkt->network_tuple.dport));
if (FLOW_START == flow_status)
{
bpf_printk("|updateFlow|sport: %d|dport: %d|seq: %llu",
bpf_ntohs(pkt->network_tuple.sport),
bpf_ntohs(pkt->network_tuple.dport),
bpf_ntohl(pkt->tcp_seq));
struct flow_state flow_state = { 0 };
flow_state.t_start = bpf_ktime_get_ns();
flow_state.t_last_pkt = flow_state.t_start;
// network tuple is the flow id
// bpf_printk("dport133: %d", bpf_ntohs(pkt->network_tuple.dport));
flow_state.network_tuple.saddr[0] = pkt->network_tuple.saddr[0];
flow_state.network_tuple.daddr[0] = pkt->network_tuple.daddr[0];
flow_state.network_tuple.sport = pkt->network_tuple.sport;
flow_state.network_tuple.dport = pkt->network_tuple.dport;
flow_state.network_tuple.l4_proto = pkt->network_tuple.l4_proto;
bpf_printk("|updateFlow|sport: %d|dport: %d",
bpf_ntohs(flow_state.network_tuple.sport),
bpf_ntohs(flow_state.network_tuple.dport)
);
// Adding the key in the map is causing the problem. When disabled, program runs as expected
bpf_map_update_elem(&flow_state_map, &pkt->network_tuple, &flow_state, BPF_NOEXIST);
}
}
static __always_inline int
output_syn_event(struct pkt_info *pkt)
{
struct syn_event *syn_event = { 0 };
syn_event = bpf_ringbuf_reserve(&rb, sizeof(*syn_event), 0);
if (!syn_event)
{
bpf_printk("ringbuffer not reserved!\n");
return -1;
}
syn_event->event_type = EVENT_TYPE_NEW_FLOW_TCP;
// fill up other members of syn_event struct
bpf_printk("|SYN|sport: %d|dport: %d|seq: %llu",
bpf_ntohs(pkt->network_tuple.sport),
bpf_ntohs(pkt->network_tuple.dport),
bpf_ntohl(pkt->tcp_seq));
bpf_ringbuf_submit(syn_event, 0);
return 0;
}
static __always_inline struct pkt_info *
extract_pkt_details(struct sk_buff *skb, __u16 probe_idx)
{
// ... extracting the packet details
}
static __always_inline int
trace_skb(struct sk_buff *skb)
{
struct pkt_info *pkt = extract_pkt_details(skb, probe_idx);
// all the pkt details are received correctly at this point
if (pkt->network_tuple.l4_proto == IPPROTO_TCP)
{
if (!bpf_map_lookup_elem(&flow_state_map, &pkt->network_tuple) && pkt->syn && !pkt->ack)
{
update_flow_state(pkt, FLOW_START);
output_syn_event(pkt);
}
}
return 0;
}
Console output when the function bpf_map_update_elem()
is used in function update_flow_state()
<idle>-0 [005] dNs.1 4596129.654890: bpf_trace_printk: |updateFlow|sport: 43802|dport: 8080|seq: 3135364954
<idle>-0 [005] dNs.1 4596129.654891: bpf_trace_printk: |updateFlow|sport: 0|dport: 0
<idle>-0 [005] dNs.1 4596129.654892: bpf_trace_printk: |SYN|sport: 0|dport: 0|seq: 0
Console output when the function bpf_map_update_elem()
is NOT used in function update_flow_state()
<idle>-0 [005] d.s.1 4596188.487842: bpf_trace_printk: |updateFlow|sport: 49278|dport: 8080|seq: 502719687
<idle>-0 [005] d.s.1 4596188.487843: bpf_trace_printk: |updateFlow|sport: 49278|dport: 8080
<idle>-0 [005] d.s.1 4596188.487844: bpf_trace_printk: |SYN|sport: 49278|dport: 8080|seq: 502719687
I am not able to find out where I am going wrong! Any ideas?