-
Notifications
You must be signed in to change notification settings - Fork 65
lib: log: support packet 5-tuple logging #578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -290,7 +290,7 @@ tcp\.flags { BEGIN(STATE_MATCHER_TCP_FLAGS); yylval.sval = strdup(yytext); | |
| } | ||
| } | ||
|
|
||
| [a-zA-Z0-9_]+ { yylval.sval = strdup(yytext); return STRING; } | ||
| [a-zA-Z0-9_-]+ { yylval.sval = strdup(yytext); return STRING; } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude: suggestion: The pattern change from
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is probably fine as is. The weird part is that |
||
|
|
||
| . { return *yytext; } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -677,6 +677,55 @@ static void _bf_chain_log_sock_addr(const struct bf_log *log) | |
| bf_logger_get_color(BF_COLOR_RESET, BF_STYLE_RESET)); | ||
| } | ||
|
|
||
| static void _bf_chain_log_5_tuple(const struct bf_log *log) | ||
| { | ||
| char src_addr[INET6_ADDRSTRLEN]; | ||
| char dst_addr[INET6_ADDRSTRLEN]; | ||
| const char *protocol; | ||
| int family; | ||
|
|
||
| assert(log); | ||
|
|
||
| if (log->l3_proto == ETH_P_IP) { | ||
| family = AF_INET; | ||
| } else if (log->l3_proto == ETH_P_IPV6) { | ||
| family = AF_INET6; | ||
| } else { | ||
| (void)fprintf(stdout, " 5-tuple : <unknown protocol 0x%04x>\n", | ||
| log->l3_proto); | ||
| return; | ||
| } | ||
|
|
||
|
yaakov-stein marked this conversation as resolved.
|
||
| inet_ntop(family, log->pkt_5_tuple.saddr, src_addr, sizeof(src_addr)); | ||
| inet_ntop(family, log->pkt_5_tuple.daddr, dst_addr, sizeof(dst_addr)); | ||
| protocol = bf_ipproto_to_str(log->l4_proto); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment that this only works because
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha, will add comment for improved documentation and understanding. |
||
| /* Tuple logging only emits TCP or UDP records, both of which are known to | ||
| * bf_ipproto_to_str(). */ | ||
|
|
||
| (void)fprintf(stdout, " 5-tuple : %s%s%s ", | ||
| bf_logger_get_color(BF_COLOR_LIGHT_MAGENTA, BF_STYLE_BOLD), | ||
| protocol ?: "unknown", | ||
| bf_logger_get_color(BF_COLOR_RESET, BF_STYLE_RESET)); | ||
|
|
||
| if (family == AF_INET6) { | ||
| (void)fprintf(stdout, "%s[%s]:%u%s → %s[%s]:%u%s\n", | ||
| bf_logger_get_color(BF_COLOR_LIGHT_CYAN, BF_STYLE_BOLD), | ||
| src_addr, log->pkt_5_tuple.sport, | ||
| bf_logger_get_color(BF_COLOR_RESET, BF_STYLE_RESET), | ||
| bf_logger_get_color(BF_COLOR_LIGHT_CYAN, BF_STYLE_BOLD), | ||
| dst_addr, log->pkt_5_tuple.dport, | ||
| bf_logger_get_color(BF_COLOR_RESET, BF_STYLE_RESET)); | ||
| } else { | ||
| (void)fprintf(stdout, "%s%s:%u%s → %s%s:%u%s\n", | ||
| bf_logger_get_color(BF_COLOR_CYAN, BF_STYLE_BOLD), | ||
| src_addr, log->pkt_5_tuple.sport, | ||
| bf_logger_get_color(BF_COLOR_RESET, BF_STYLE_RESET), | ||
| bf_logger_get_color(BF_COLOR_CYAN, BF_STYLE_BOLD), | ||
| dst_addr, log->pkt_5_tuple.dport, | ||
| bf_logger_get_color(BF_COLOR_RESET, BF_STYLE_RESET)); | ||
| } | ||
| } | ||
|
|
||
| void bfc_print_log(const struct bf_log *log) | ||
| { | ||
| assert(log); | ||
|
|
@@ -695,6 +744,9 @@ void bfc_print_log(const struct bf_log *log) | |
| case BF_LOG_TYPE_SOCK_ADDR: | ||
| _bf_chain_log_sock_addr(log); | ||
| break; | ||
| case BF_LOG_TYPE_PACKET_5_TUPLE: | ||
| _bf_chain_log_5_tuple(log); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| /* | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| */ | ||
|
|
||
| #include <linux/bpf.h> | ||
| #include <linux/if_ether.h> | ||
| #include <linux/in.h> | ||
| #include <linux/ip.h> | ||
| #include <linux/ipv6.h> | ||
| #include <linux/tcp.h> | ||
| #include <linux/udp.h> | ||
|
|
||
| #include <bpf/bpf_endian.h> | ||
| #include <bpf/bpf_helpers.h> | ||
| #include <stddef.h> | ||
|
|
||
| #include "cgen/runtime.h" | ||
|
|
||
| __u8 bf_pkt_5_tuple_log(struct bf_runtime *ctx, __u32 rule_id, __u32 verdict, | ||
| __u32 l3_l4_proto) | ||
| { | ||
| struct bf_log *log; | ||
| __u16 l3_proto = (__u16)(l3_l4_proto >> 16); | ||
| __u8 l4_proto = (__u8)l3_l4_proto; | ||
|
|
||
| log = bpf_ringbuf_reserve(ctx->log_map, sizeof(struct bf_log), 0); | ||
| if (!log) { | ||
| bpf_printk("failed to reserve %d bytes in ringbuf", | ||
| sizeof(struct bf_log)); | ||
| return 1; | ||
| } | ||
|
|
||
| __builtin_memset(log, 0, sizeof(*log)); | ||
|
|
||
| log->ts = bpf_ktime_get_ns(); | ||
| log->rule_id = rule_id; | ||
| log->verdict = verdict; | ||
| log->l3_proto = bpf_ntohs(l3_proto); | ||
| log->l4_proto = l4_proto; | ||
| log->log_type = BF_LOG_TYPE_PACKET_5_TUPLE; | ||
|
|
||
| if (l3_proto == bpf_htons(ETH_P_IP)) { | ||
| struct iphdr *ip4 = ctx->l3_hdr; | ||
|
|
||
| __builtin_memcpy(log->pkt_5_tuple.saddr, &ip4->saddr, | ||
| sizeof(ip4->saddr)); | ||
| __builtin_memcpy(log->pkt_5_tuple.daddr, &ip4->daddr, | ||
| sizeof(ip4->daddr)); | ||
| } else { | ||
| struct ipv6hdr *ip6 = ctx->l3_hdr; | ||
|
|
||
| __builtin_memcpy(log->pkt_5_tuple.saddr, &ip6->saddr, | ||
| sizeof(ip6->saddr)); | ||
| __builtin_memcpy(log->pkt_5_tuple.daddr, &ip6->daddr, | ||
| sizeof(ip6->daddr)); | ||
| } | ||
|
|
||
| if (l4_proto == IPPROTO_TCP) { | ||
| struct tcphdr *tcp = ctx->l4_hdr; | ||
|
|
||
| log->pkt_5_tuple.sport = bpf_ntohs(tcp->source); | ||
| log->pkt_5_tuple.dport = bpf_ntohs(tcp->dest); | ||
| } else { | ||
| struct udphdr *udp = ctx->l4_hdr; | ||
|
|
||
| log->pkt_5_tuple.sport = bpf_ntohs(udp->source); | ||
| log->pkt_5_tuple.dport = bpf_ntohs(udp->dest); | ||
| } | ||
|
|
||
| bpf_ringbuf_submit(log, 0); | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,11 @@ | |
|
|
||
| #include <linux/bpf.h> | ||
| #include <linux/bpf_common.h> | ||
| #include <linux/if_ether.h> | ||
| #include <linux/in.h> // NOLINT | ||
| #include <linux/limits.h> | ||
|
|
||
| #include <endian.h> | ||
| #include <errno.h> | ||
| #include <fcntl.h> | ||
| #include <limits.h> | ||
|
|
@@ -504,6 +507,76 @@ static int _bf_program_check_proto(struct bf_program *program, | |
| return 0; | ||
| } | ||
|
|
||
| static int _bf_program_generate_log(struct bf_program *program, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think once we are making a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alrighty, I will move the no-log early return, 5-tuple eligibility guards, rate-limit handling, and final |
||
| const struct bf_rule *rule) | ||
| { | ||
| _clean_bf_jmpctx_ struct bf_jmpctx l3_ctx = bf_jmpctx_default(); | ||
| _clean_bf_jmpctx_ struct bf_jmpctx l4_ctx = bf_jmpctx_default(); | ||
| _clean_bf_jmpctx_ struct bf_jmpctx null_ctx = bf_jmpctx_default(); | ||
| _clean_bf_jmpctx_ struct bf_jmpctx rate_ctx = bf_jmpctx_default(); | ||
|
|
||
| assert(program); | ||
| assert(rule); | ||
|
|
||
| if (!rule->log) | ||
| return 0; | ||
|
|
||
| if (rule->log == BF_FLAG(BF_LOG_OPT_5_TUPLE)) { | ||
| /* A 5-tuple is only complete for IPv4/IPv6 packets using TCP/UDP. | ||
| * Skip only the log action for other packets, leaving the rule's | ||
| * remaining actions and verdict unchanged. */ | ||
| EMIT(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, htobe16(ETH_P_IP), 2)); | ||
| EMIT(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, htobe16(ETH_P_IPV6), 1)); | ||
| l3_ctx = bf_jmpctx_get(program, BPF_JMP_A(0)); | ||
|
|
||
| EMIT(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_8, IPPROTO_TCP, 2)); | ||
| EMIT(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_8, IPPROTO_UDP, 1)); | ||
| l4_ctx = bf_jmpctx_get(program, BPF_JMP_A(0)); | ||
| } | ||
|
|
||
| if (rule->log_rate_ns) { | ||
| const struct bpf_insn rate_insn[2] = { | ||
| BPF_LD_IMM64(BPF_REG_1, rule->log_rate_ns), | ||
| }; | ||
|
|
||
| /* Rate-limited log: check last_log_ts in the state map before logging. | ||
| * | ||
| * R9 (callee-saved) holds the pointer to this rule's state entry | ||
| * across the bpf_ktime_get_ns() call. */ | ||
| EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_10, | ||
| BF_PROG_CTX_OFF(state_map))); | ||
|
|
||
| /* Skip the log if state_map is NULL. This shouldn't happen at runtime, | ||
| * but the verifier requires the NULL check. */ | ||
| null_ctx = | ||
| bf_jmpctx_get(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_9, 0, 0)); | ||
|
|
||
| if (rule->index > 0) { | ||
| EMIT(program, BPF_ALU64_IMM(BPF_ADD, BPF_REG_9, | ||
| (int)(rule->index * | ||
| sizeof(struct bf_rule_state)))); | ||
| } | ||
|
|
||
| EMIT(program, BPF_EMIT_CALL(BPF_FUNC_ktime_get_ns)); | ||
|
|
||
| EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_9, 0)); | ||
| EMIT(program, BPF_MOV64_REG(BPF_REG_2, BPF_REG_0)); | ||
| EMIT(program, BPF_ALU64_REG(BPF_SUB, BPF_REG_2, BPF_REG_1)); | ||
|
|
||
| // Load log_rate_ns as a 64-bit immediate into R1. | ||
| EMIT(program, rate_insn[0]); | ||
| EMIT(program, rate_insn[1]); | ||
|
|
||
| // Skip the log while delta is smaller than log_rate_ns. | ||
| rate_ctx = bf_jmpctx_get(program, | ||
| BPF_JMP_REG(BPF_JLT, BPF_REG_2, BPF_REG_1, 0)); | ||
|
|
||
| EMIT(program, BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_0, 0)); | ||
| } | ||
|
|
||
| return program->runtime.ops->gen_inline_log(program, rule); | ||
| } | ||
|
|
||
| static int _bf_program_generate_rule(struct bf_program *program, | ||
| struct bf_rule *rule) | ||
| { | ||
|
|
@@ -567,58 +640,9 @@ static int _bf_program_generate_rule(struct bf_program *program, | |
| } | ||
| } | ||
|
|
||
| if (rule->log && rule->log_rate_ns) { | ||
| // Rate-limited log: check last_log_ts in the state map before logging. | ||
| // | ||
| // R9 (callee-saved) holds the pointer to this rule's state entry | ||
| // across the bpf_ktime_get_ns() call. | ||
| EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_10, | ||
| BF_PROG_CTX_OFF(state_map))); | ||
| { | ||
| // Outer skip: state_map is NULL (shouldn't happen at runtime, | ||
| // but the verifier requires the NULL check). | ||
| _clean_bf_jmpctx_ struct bf_jmpctx null_ctx = | ||
| bf_jmpctx_get(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_9, 0, 0)); | ||
|
|
||
| if (rule->index > 0) { | ||
| EMIT(program, | ||
| BPF_ALU64_IMM( | ||
| BPF_ADD, BPF_REG_9, | ||
| (int)(rule->index * sizeof(struct bf_rule_state)))); | ||
| } | ||
|
|
||
| EMIT(program, BPF_EMIT_CALL(BPF_FUNC_ktime_get_ns)); | ||
|
|
||
| EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_9, 0)); | ||
| EMIT(program, BPF_MOV64_REG(BPF_REG_2, BPF_REG_0)); | ||
| EMIT(program, BPF_ALU64_REG(BPF_SUB, BPF_REG_2, BPF_REG_1)); | ||
|
|
||
| { | ||
| // Load log_rate_ns as a 64-bit immediate into R1. | ||
| const struct bpf_insn rate_insn[2] = { | ||
| BPF_LD_IMM64(BPF_REG_1, rule->log_rate_ns), | ||
| }; | ||
| EMIT(program, rate_insn[0]); | ||
| EMIT(program, rate_insn[1]); | ||
| } | ||
|
|
||
| { | ||
| // Inner skip: delta < log_rate_ns means still within window. | ||
| _clean_bf_jmpctx_ struct bf_jmpctx rate_ctx = bf_jmpctx_get( | ||
| program, BPF_JMP_REG(BPF_JLT, BPF_REG_2, BPF_REG_1, 0)); | ||
|
|
||
| EMIT(program, BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_0, 0)); | ||
|
|
||
| r = program->runtime.ops->gen_inline_log(program, rule); | ||
| if (r) | ||
| return r; | ||
| } | ||
| } | ||
| } else if (rule->log) { | ||
| r = program->runtime.ops->gen_inline_log(program, rule); | ||
| if (r) | ||
| return r; | ||
| } | ||
| r = _bf_program_generate_log(program, rule); | ||
| if (r) | ||
| return r; | ||
|
|
||
| if (rule->has_counters) { | ||
| EMIT(program, BPF_MOV64_REG(BPF_REG_1, BPF_REG_10)); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> "unsupported packets are not logged."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alright, will simplify the wording