Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion felix/bpf-gpl/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Project Calico BPF dataplane build scripts.
# Copyright (c) 2020-2022 Tigera, Inc. All rights reserved.
# Copyright (c) 2020-2026 Tigera, Inc. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

# Disable implicit rules.
Expand Down Expand Up @@ -58,6 +58,7 @@ LD := llc
UT_C_FILES:=$(shell find ut -name '*.c')
UT_OBJS:=$(UT_C_FILES:.c=.o) $(shell ./list-ut-objs)
UT_OBJS+=ut/ip_parse_test_v6.o
UT_OBJS+=ut/tcp_rst_v6.o

XDP_MAP_HEADERS := jump.h
COMMON_MAP_HEADERS := counters.h ifstate.h perf_types.h profiling.h rule_counters.h qos.h ctlb_map.h $(XDP_MAP_HEADERS)
Expand Down Expand Up @@ -220,6 +221,10 @@ ut/ip_parse_test_v6.ll: ut/ip_parse_test.c
$(CC) $(UT_CFLAGS) $(CFLAGS) -DIPVER6 -c $< -o $@
ut/ip_parse_test_v6.o: ut/ip_parse_test_v6.ll
$(LINK)
ut/tcp_rst_v6.ll: ut/tcp_rst.c
$(CC) $(UT_CFLAGS) $(CFLAGS) -DIPVER6 -c $< -o $@
ut/tcp_rst_v6.o: ut/tcp_rst_v6.ll
$(LINK)

%_v4.ll: %.c %.d calculate-flags
$(COMPILE)
Expand Down
1 change: 1 addition & 0 deletions felix/bpf-gpl/conntrack_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum cali_ct_type {
#define CALI_CT_FLAG_SKIP_REDIR_PEER 0x4000 /* marks connections from a client which is excluded from redir */
#define CALI_CT_FLAG_SET_DSCP 0x8000 /* marks connections that needs to set DSCP */
#define CALI_CT_FLAG_MAGLEV 0X10000 /* marks Maglev connections. Allows packets of an existing to arrive via a different tunnel after failover. */
#define CALI_CT_FLAG_SEND_RESET 0x20000 /* marks connections where we should send a TCP RST on behalf of the workload */

struct calico_ct_leg {
__u64 bytes;
Expand Down
2 changes: 2 additions & 0 deletions felix/bpf-gpl/jump.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ enum cali_jump_index {
PROG_INDEX_NEW_FLOW,
PROG_INDEX_IP_FRAG,
PROG_INDEX_MAGLEV,
PROG_INDEX_TCP_RST,

PROG_INDEX_MAIN_DEBUG,
PROG_INDEX_POLICY_DEBUG,
Expand All @@ -107,6 +108,7 @@ enum cali_jump_index {
PROG_INDEX_NEW_FLOW_DEBUG,
PROG_INDEX_IP_FRAG_DEBUG,
PROG_INDEX_MAGLEV_DEBUG,
PROG_INDEX_TCP_RST_DEBUG,
};

#if CALI_F_XDP
Expand Down
45 changes: 44 additions & 1 deletion felix/bpf-gpl/tc.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Project Calico BPF dataplane programs.
// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved.
// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

#include <linux/types.h>
Expand Down Expand Up @@ -60,6 +60,9 @@

#ifndef IPVER6
#include "ip_v4_fragment.h"
#include "tcp4.h"
#else
#include "tcp6.h"
#endif

#define HAS_HOST_CONFLICT_PROG CALI_F_TO_HEP
Expand Down Expand Up @@ -446,6 +449,13 @@ static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx)
ctx->state->flags |= CALI_ST_SKIP_FIB;
}
CALI_DEBUG("CT Hit");
/* Check for TCP RST injection */
if (CALI_F_TO_HOST && ctx->state->ct_result.flags & CALI_CT_FLAG_SEND_RESET) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we remove this flag and set conntrack to rstseen? If for any reason it would be detected like a spurious RST, and there would be more traffic it would get reset. Idk if I am not overthinking. This would also send the the RST for any follow up packet, guess we may want it in case of RST packet drop.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I guess we should only send the packet to the client. Wht if we see a packet from the server - the now removed endpoint? I guess sending RST does not hurt.

Iirc we discussed that the same connection cannot be reused fast enough to cause a reasonable race, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyways this is to a workload which doesn't exist, so even if there is a spurious RST, its good to reset the connection. I think there cannot be a RST from the server, unless the same connection is reused. Am I missing something?

CALI_DEBUG("Sending TCP RST due to CT state");
ctx->state->ct_result.ifindex_fwd = CT_INVALID_IFINDEX;
CALI_JUMP_TO(ctx, PROG_INDEX_TCP_RST);
goto deny;
}

if (ctx->state->ip_proto == IPPROTO_TCP && ct_result_is_syn(ctx->state->ct_result.rc)) {
CALI_DEBUG("Forcing policy on SYN");
Expand Down Expand Up @@ -1937,6 +1947,39 @@ int calico_tc_skb_icmp_inner_nat(struct __sk_buff *skb)
return TC_ACT_SHOT;
}

SEC("tc")
int calico_tc_skb_send_tcp_rst(struct __sk_buff *skb)
{
/* Initialise the context, which is stored on the stack, and the state, which
* we use to pass data from one program to the next via tail calls. */
DECLARE_TC_CTX(_ctx,
.skb = skb,
);
struct cali_tc_ctx *ctx = &_ctx;
int ret = 0;
#ifndef IPVER6
ret = tcp_v4_rst(ctx);
#else
ret = tcp_v6_rst(ctx);
Comment thread
sridhartigera marked this conversation as resolved.
#endif

CALI_DEBUG("Entering calico_tc_skb_send_tcp_rst");
if (ret) {
ctx->state->fwd.res = TC_ACT_SHOT;
} else {
fwd_fib_set(&ctx->state->fwd, true);
}

if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) {
deny_reason(ctx, CALI_REASON_SHORT);
CALI_DEBUG("Too short");
return TC_ACT_SHOT;
}

tc_state_fill_from_iphdr(ctx);
return forward_or_drop(ctx);
}


SEC("tc")
int calico_tc_skb_send_icmp_replies(struct __sk_buff *skb)
Expand Down
84 changes: 84 additions & 0 deletions felix/bpf-gpl/tcp4.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Project Calico BPF dataplane programs.
// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

#ifndef __CALI_TCP4_H__
#define __CALI_TCP4_H__

#include <linux/if_ether.h>
#include <linux/ip.h>

#include "bpf.h"
#include "log.h"
#include "skb.h"

static CALI_BPF_INLINE int tcp_v4_rst(struct cali_tc_ctx *ctx) {
if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) {
deny_reason(ctx, CALI_REASON_SHORT);
CALI_DEBUG("TCP reset : too short");
return -1;
}
struct iphdr ip_orig = *ip_hdr(ctx);
struct tcphdr th_orig = *tcp_hdr(ctx);
int original_len = ctx->skb->len;

/* Trim to minimum size */
__u32 len = skb_iphdr_offset(ctx) + IP_SIZE + TCP_SIZE /* max IP len */;
int err = bpf_skb_change_tail(ctx->skb, len, 0);
if (err) {
CALI_DEBUG("tcp reset reply: bpf_skb_change_tail (len=%d) failed (err=%d)", len, err);
return -1;
}

/* Revalidate all pointers */
if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) {
deny_reason(ctx, CALI_REASON_SHORT);
CALI_DEBUG("TCP reset : too short");
return -1;
}
ip_hdr(ctx)->version = 4;
ip_hdr(ctx)->ihl = 5;
ip_hdr(ctx)->tos = 0;
ip_hdr(ctx)->ttl = 64;
ip_hdr(ctx)->protocol = IPPROTO_TCP;
ip_hdr(ctx)->saddr = ip_orig.daddr;
ip_hdr(ctx)->daddr = ip_orig.saddr;
ip_hdr(ctx)->check = 0;
ip_hdr(ctx)->tot_len = bpf_htons(len - (CALI_F_L3_DEV ? 0 : ETH_SIZE));
ctx->ipheader_len = 20;

struct tcphdr *th = ((void *)ip_hdr(ctx)) + IP_SIZE;
__builtin_memset(th, 0, sizeof(struct tcphdr));
th->source = th_orig.dest;
th->dest = th_orig.source;
th->rst = 1;
th->doff = sizeof(struct tcphdr) / 4;
th->seq = 0;

if (th_orig.ack) {
th->seq = th_orig.ack_seq;
} else {
th->ack_seq = bpf_htonl(bpf_ntohl(th_orig.seq) + th_orig.syn + th_orig.fin +
original_len - (th_orig.doff << 2));
th->ack = 1;
}
Comment thread
sridhartigera marked this conversation as resolved.
th->check = 0;

__wsum ip_csum = bpf_csum_diff(0, 0, ctx->ip_header, sizeof(struct iphdr), 0);
__wsum tcp_csum = bpf_csum_diff(0, 0, (__u32 *)th, len - sizeof(struct iphdr) - skb_iphdr_offset(ctx), 0);
if (bpf_l3_csum_replace(ctx->skb,
skb_iphdr_offset(ctx) + offsetof(struct iphdr, check), 0, ip_csum, 0)) {
CALI_DEBUG("TCP reset v4 reply: set ip csum failed");
return -1;
}

Comment thread
sridhartigera marked this conversation as resolved.
err = bpf_l4_csum_replace(ctx->skb, skb_l4hdr_offset(ctx) +
offsetof(struct tcphdr, check), 0, tcp_csum, BPF_F_PSEUDO_HDR);
if (err) {
CALI_DEBUG("TCP reset v4 reply: set tcp csum failed %d", err);
return -1;
}
Comment thread
sridhartigera marked this conversation as resolved.
return 0;
}

#endif /* __CALI_TCP4_H__ */
76 changes: 76 additions & 0 deletions felix/bpf-gpl/tcp6.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Project Calico BPF dataplane programs.
// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

#ifndef __CALI_TCP6_H__
#define __CALI_TCP6_H__

#include <linux/if_ether.h>
#include <linux/ip.h>

#include "bpf.h"
#include "log.h"
#include "skb.h"

static CALI_BPF_INLINE int tcp_v6_rst(struct cali_tc_ctx *ctx) {
if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) {
deny_reason(ctx, CALI_REASON_SHORT);
CALI_DEBUG("TCP reset : too short");
return -1;
}

ipv6_addr_t orig_src, orig_dst;
ipv6hdr_ip_to_ipv6_addr_t(&orig_src, &ip_hdr(ctx)->saddr);
ipv6hdr_ip_to_ipv6_addr_t(&orig_dst, &ip_hdr(ctx)->daddr);
struct tcphdr th_orig = *tcp_hdr(ctx);
int original_len = ctx->skb->len;

/* Trim to minimum size */
__u32 len = skb_iphdr_offset(ctx) + IP_SIZE + TCP_SIZE /* max IP len */;
int err = bpf_skb_change_tail(ctx->skb, len, 0);
if (err) {
CALI_DEBUG("tcp reset reply: bpf_skb_change_tail (len=%d) failed (err=%d)", len, err);
return -1;
}

/* Revalidate all pointers */
if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) {
deny_reason(ctx, CALI_REASON_SHORT);
CALI_DEBUG("TCP reset : too short");
return -1;
}
ip_hdr(ctx)->version = 6;
ip_hdr(ctx)->hop_limit = 255;
ip_hdr(ctx)->nexthdr = IPPROTO_TCP;
ipv6_addr_t_to_ipv6hdr_ip(&ip_hdr(ctx)->daddr, &orig_src);
ipv6_addr_t_to_ipv6hdr_ip(&ip_hdr(ctx)->saddr, &orig_dst);
ip_hdr(ctx)->payload_len = bpf_htons(TCP_SIZE);
ctx->ipheader_len = IP_SIZE;

struct tcphdr *th = ((void *)ip_hdr(ctx)) + IP_SIZE;
__builtin_memset(th, 0, TCP_SIZE);
th->source = th_orig.dest;
th->dest = th_orig.source;
th->rst = 1;
th->doff = sizeof(struct tcphdr) / 4;
th->seq = 0;

if (th_orig.ack) {
th->seq = th_orig.ack_seq;
} else {
th->ack_seq = bpf_htonl(bpf_ntohl(th_orig.seq) + th_orig.syn + th_orig.fin +
original_len - (th_orig.doff << 2));
Comment on lines +54 to +62
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TCP flags not properly initialized. The code sets some TCP header fields but doesn't explicitly zero all flags. Fields like syn, fin, psh, urg, window, and urg_ptr should be explicitly set to 0 to ensure we're not sending garbage values. Consider using memset or explicitly setting all TCP header fields to known values.

Suggested change
th->rst = 1;
th->doff = sizeof(struct tcphdr) / 4;
th->seq = 0;
if (th_orig.ack) {
th->seq = th_orig.ack_seq;
} else {
th->ack_seq = bpf_htonl(bpf_ntohl(th_orig.seq) + th_orig.syn + th_orig.fin +
original_len - (th_orig.doff << 2));
/* Initialize all TCP flags and related fields to known values. */
th->fin = 0;
th->syn = 0;
th->rst = 0;
th->psh = 0;
th->ack = 0;
th->urg = 0;
th->ece = 0;
th->cwr = 0;
th->res1 = 0;
th->res2 = 0;
th->window = 0;
th->urg_ptr = 0;
th->seq = 0;
th->ack_seq = 0;
th->rst = 1;
th->doff = sizeof(struct tcphdr) / 4;
if (th_orig.ack) {
th->seq = th_orig.ack_seq;
} else {
th->ack_seq = bpf_htonl(bpf_ntohl(th_orig.seq) + th_orig.syn + th_orig.fin +
original_len - (th_orig.doff << 2));

Copilot uses AI. Check for mistakes.
th->ack = 1;
}
th->check = 0;

__wsum tcp_csum = bpf_csum_diff(0, 0, (__u32 *)th, len - sizeof(struct ipv6hdr) - skb_iphdr_offset(ctx), 0);
if (bpf_l4_csum_replace(ctx->skb, skb_l4hdr_offset(ctx) +
offsetof(struct tcphdr, check), 0, tcp_csum, BPF_F_PSEUDO_HDR)) {
CALI_DEBUG("TCP reset v6 reply: set tcp csum failed");
return -1;
}
Comment thread
sridhartigera marked this conversation as resolved.
return 0;
}

#endif /* __CALI_TCP6_H__ */
69 changes: 69 additions & 0 deletions felix/bpf-gpl/ut/tcp_rst.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Project Calico BPF dataplane programs.
// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

#include "ut.h"
#include "bpf.h"
#include "nat.h"
#ifndef IPVER6
#include "tcp4.h"
#else
#include "tcp6.h"
#endif
#include "parsing.h"
#include "jump.h"

const volatile struct cali_tc_preamble_globals __globals;

static CALI_BPF_INLINE int calico_unittest_entry (struct __sk_buff *skb)
{
volatile struct cali_tc_globals *globals = state_get_globals_tc();

if (!globals) {
return TC_ACT_SHOT;
}

/* Set the globals for the rest of the prog chain. */
#ifndef IPVER6
globals->data = __globals.v4;
#else
globals->data = __globals.v6;
#endif
DECLARE_TC_CTX(_ctx,
.skb = skb,
.ipheader_len = IP_SIZE,
);
struct cali_tc_ctx *ctx = &_ctx;
if (!ctx->counters) {
CALI_DEBUG("Counters map lookup failed: DROP\n");
return TC_ACT_SHOT;
}
int ret = PARSING_OK;
#ifdef IPVER6
ret = PARSING_OK_V6;
#endif
if (parse_packet_ip(ctx) != ret) {
return TC_ACT_UNSPEC;
}

tc_state_fill_from_iphdr(ctx);

switch (tc_state_fill_from_nexthdr(ctx, true)) {
case PARSING_ERROR:
goto deny;
case PARSING_ALLOW_WITHOUT_ENFORCING_POLICY:
goto allow;
}
#ifndef IPVER6
return tcp_v4_rst(ctx);
#else
return tcp_v6_rst(ctx);
#endif

allow:
return TC_ACT_UNSPEC;

deny:
return TC_ACT_SHOT;
}

Loading