Raw File
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright Authors of Cilium */
#include "common.h"
#include <bpf/ctx/skb.h>
#include "pktgen.h"

/*
 * Datapath configuration settings to setup tunneling with VXLan
 * and nodeport
 */
#define ENCAP_IFINDEX 1  /* Set dummy ifindex for tunnel device */
#define ENABLE_IPV4 1
#define ENABLE_IPV6 1
#define TUNNEL_MODE 1
#define ENABLE_NODEPORT 1
#define ENABLE_MASQUERADE_IPV4 1
#define ENABLE_MASQUERADE_IPV6 1

/*
 * Now include testing defaults
 */
#define ROUTER_IP
#undef ROUTER_IP
#include "node_config.h"

/*
 * Simulate sending traffic from pod_one on node_one directly to
 * node_two. Tests are written from the perspective of node_one,
 * allowing us access to nodeport_snat_fwd_ipv{4,6}.
 */
#define SRC_MAC mac_one
#define SRC_IPV4 v4_pod_one
#define SRC_IPV6 v6_pod_one
#define SRC_PORT tcp_src_one
#define DST_MAC mac_three
#define DST_IPV4 v4_node_two
#define DST_IPV6 v6_node_two
#define DST_PORT tcp_svc_one

/*
 * Include entrypoint into host stack
 */
#include "bpf_host.c"

/*
 * Include test helpers
 */
#include "lib/endpoint.h"
#include "lib/ipcache.h"
#include "lib/policy.h"
#include "lib/clear.h"

#define TO_NETDEV 0
struct {
	__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
	__uint(key_size, sizeof(__u32));
	__uint(max_entries, 1);
	__array(values, int());
} entry_call_map __section(".maps") = {
	.values = {
		[TO_NETDEV] = &cil_to_netdev,
	},
};

static __always_inline int
pktgen(struct __ctx_buff *ctx, bool v4)
{
	struct pktgen builder;
	struct tcphdr *l4;
	void *data;

	pktgen__init(&builder, ctx);

	if (v4)
		l4 = pktgen__push_ipv4_tcp_packet(&builder,
						  (__u8 *)SRC_MAC,
						  (__u8 *)DST_MAC,
						  SRC_IPV4, DST_IPV4,
						  SRC_PORT, DST_PORT);
	else
		l4 = pktgen__push_ipv6_tcp_packet(&builder,
						  (__u8 *)SRC_MAC, (__u8 *)DST_MAC,
						  (__u8 *)SRC_IPV6,
						  (__u8 *)DST_IPV6,
						  SRC_PORT, DST_PORT);

	if (!l4)
		return TEST_ERROR;

	data = pktgen__push_data(&builder, default_data, sizeof(default_data));
	if (!data)
		return TEST_ERROR;

	pktgen__finish(&builder);
	return 0;
}

/*
 * Setup scenario where pod with SRC_IP on node is sending traffic directly
 * to a remote node with the address DST_IP. In the case of tunnel mode where
 * flag_skip_tunnel=true, bpf masquerading should be skipped.
 */
static __always_inline int
setup(struct __ctx_buff *ctx, bool v4, bool flag_skip_tunnel)
{
	/*
	 * Reset maps before the test.
	 * Otherwise, leftover state from previous tests will have an impact,
	 * as the tests and checks assume we have a fresh state every time.
	 */
	clear_map(&METRICS_MAP);
	clear_map(&CT_MAP_TCP4);
	clear_map(&CT_MAP_TCP6);
	clear_map(get_cluster_snat_map_v4(0));
	clear_map(get_cluster_snat_map_v6(0));

	policy_add_egress_allow_all_entry();

	/*
	 * For this scenario, an endpoint for the source addresses needs
	 * to be created. Additionally, the destination's security identity in the ipcache
	 * must reflect that it is a remote node. This information is used by the SNAT stack to
	 * determine if SNAT needs to be performed for the given packet. See snat_v{4,6}_needs_masquerade.
	 */

	if (v4) {
		endpoint_v4_add_entry(SRC_IPV4, 0, 0, 0, 0, (__u8 *)SRC_MAC, (__u8 *)SRC_MAC);
		ipcache_v4_add_entry_with_flags(DST_IPV4,
						0, REMOTE_NODE_ID, 0,
						0, flag_skip_tunnel);
	} else {
		endpoint_v6_add_entry((union v6addr *)SRC_IPV6, 0, 0, 0, 0,
				      (__u8 *)SRC_MAC, (__u8 *)SRC_MAC);
		ipcache_v6_add_entry_with_flags((union v6addr *)DST_IPV6,
						0, REMOTE_NODE_ID, 0,
						0, flag_skip_tunnel);
	}

	tail_call_static(ctx, entry_call_map, TO_NETDEV);
	return TEST_ERROR;
}

static __always_inline int
check_ctx(const struct __ctx_buff *ctx, bool v4, bool snat)
{
	void *data;
	void *data_end;
	__u32 *status_code;
	struct ethhdr *l2;
	struct tcphdr *l4;
	__u8 *payload;

	struct metrics_value *entry = NULL;
	struct metrics_key key = {};

	test_init();

	data = (void *)(long)ctx->data;
	data_end = (void *)(long)ctx->data_end;

	if (data + sizeof(*status_code) > data_end)
		test_fatal("status code out of bounds");

	status_code = data;
	assert(*status_code == CTX_ACT_OK);

	key.reason = REASON_FORWARDED;
	key.dir = METRIC_EGRESS;

	entry = map_lookup_elem(&METRICS_MAP, &key);
	if (!entry)
		test_fatal("metrics entry not found")

	__u64 count = 1;

	assert_metrics_count(key, count);

	l2 = data + sizeof(*status_code);

	if ((void *)l2 + sizeof(struct ethhdr) > data_end)
		test_fatal("l2 out of bounds")

	if (v4 && l2->h_proto != bpf_htons(ETH_P_IP))
		test_fatal("l2 proto hasn't been set to ETH_P_IP")

	if (!v4 && l2->h_proto != bpf_htons(ETH_P_IPV6))
		test_fatal("l2 proto hasn't been set to ETH_P_IPV6")

	if (memcmp(l2->h_source, (__u8 *)SRC_MAC, ETH_ALEN) != 0)
		test_fatal("src mac was changed");

	if (memcmp(l2->h_dest, (__u8 *)DST_MAC, ETH_ALEN) != 0)
		test_fatal("dst mac has changed")

	if (v4) {
		struct iphdr *l3;

		l3 = (void *)l2 + sizeof(struct ethhdr);

		if ((void *)l3 + sizeof(struct iphdr) > data_end)
			test_fatal("l3 out of bounds");

		if (snat) {
			if (l3->saddr != IPV4_MASQUERADE)
				test_fatal("src IP was not snatted");
		} else {
			if (l3->saddr != SRC_IPV4)
				test_fatal("src IP was changed");
		}

		if (l3->daddr != DST_IPV4)
			test_fatal("dest IP was changed");

		l4 = (void *)l3 + sizeof(struct iphdr);
	} else {
		struct ipv6hdr *l3;
		union v6addr masq_addr;

		BPF_V6(masq_addr, IPV6_MASQUERADE);

		l3 = (void *)l2 + sizeof(struct ethhdr);

		if ((void *)l3 + sizeof(struct ipv6hdr) > data_end)
			test_fatal("l3 out of bounds");

		if (snat) {
			if (memcmp((__u8 *)&l3->saddr, (__u8 *)&masq_addr, 16) != 0)
				test_fatal("src IP was not snatted");
		} else {
			if (memcmp((__u8 *)&l3->saddr, (__u8 *)SRC_IPV6, 16) != 0)
				test_fatal("src IP was changed");
		}

		if (memcmp((__u8 *)&l3->daddr, (__u8 *)DST_IPV6, 16) != 0)
			test_fatal("dest IP was changed");

		l4 = (void *)l3 + sizeof(struct ipv6hdr);
	}

	if ((void *)l4 + sizeof(struct tcphdr) > data_end)
		test_fatal("l4 out of bounds");

	if (snat) {
		__be16 p = bpf_ntohs(l4->source);

		if (p < NODEPORT_PORT_MIN_NAT || p > NODEPORT_PORT_MAX_NAT)
			test_fatal("src port was not snatted");
	} else {
		if (l4->source != SRC_PORT)
			test_fatal("src port was changed");
	}

	if (l4->dest != DST_PORT)
		test_fatal("dest port was changed");

	payload = (void *)l4 + sizeof(struct tcphdr);
	if ((void *)payload + sizeof(default_data) > data_end)
		test_fatal("payload out of bounds");

	if (memcmp(payload, default_data, sizeof(default_data)) != 0)
		test_fatal("tcp payload was changed")

	test_finish();
}

PKTGEN("tc", "01_ipv4_nodeport_ingress_no_flags")
int ipv4_nodeport_ingress_no_flags_pktgen(struct __ctx_buff *ctx)
{
	return pktgen(ctx, true);
}

SETUP("tc", "01_ipv4_nodeport_ingress_no_flags")
int ipv4_nodeport_ingress_no_flags_setup(struct __ctx_buff *ctx)
{
	return setup(ctx, true, false);
}

CHECK("tc", "01_ipv4_nodeport_ingress_no_flags")
int ipv4_nodeport_ingress_no_flags_check(__maybe_unused const struct __ctx_buff *ctx)
{
	return check_ctx(ctx, true, true);
}

PKTGEN("tc", "02_ipv4_nodeport_ingress_skip_tunnel")
int ipv4_nodeport_ingress_skip_tunnel_pktgen(struct __ctx_buff *ctx)
{
	return pktgen(ctx, true);
}

SETUP("tc", "02_ipv4_nodeport_ingress_skip_tunnel")
int ipv4_nodeport_ingress_skip_tunnel_setup(struct __ctx_buff *ctx)
{
	return setup(ctx, true, true);
}

CHECK("tc", "02_ipv4_nodeport_ingress_skip_tunnel")
int ipv4_nodeport_ingress_skip_tunnel_check(__maybe_unused const struct __ctx_buff *ctx)
{
	return check_ctx(ctx, true, false);
}

PKTGEN("tc", "03_ipv6_nodeport_ingress_no_flags")
int ipv6_nodeport_ingress_no_flags_pktgen(struct __ctx_buff *ctx)
{
	return pktgen(ctx, false);
}

SETUP("tc", "03_ipv6_nodeport_ingress_no_flags")
int ipv6_nodeport_ingress_no_flags_setup(struct __ctx_buff *ctx)
{
	return setup(ctx, false, false);
}

CHECK("tc", "03_ipv6_nodeport_ingress_no_flags")
int ipv6_nodeport_ingress_no_flags_check(__maybe_unused const struct __ctx_buff *ctx)
{
	return check_ctx(ctx, false, true);
}

PKTGEN("tc", "04_ipv6_nodeport_ingress_skip_tunnel")
int ipv6_nodeport_ingress_skip_tunnel_pktgen(struct __ctx_buff *ctx)
{
	return pktgen(ctx, false);
}

SETUP("tc", "04_ipv6_nodeport_ingress_skip_tunnel")
int ipv6_nodeport_ingress_skip_tunnel_setup(struct __ctx_buff *ctx)
{
	return setup(ctx, false, true);
}

CHECK("tc", "04_ipv6_nodeport_ingress_skip_tunnel")
int ipv6_nodeport_ingress_skip_tunnel_check(__maybe_unused const struct __ctx_buff *ctx)
{
	return check_ctx(ctx, false, false);
}
back to top