github.com/cilium/cilium@v1.16.2/bpf/tests/tc_nodeport_l3_dev.c (about)

     1  // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
     2  /* Copyright Authors of Cilium */
     3  
     4  #include "common.h"
     5  
     6  #include <bpf/ctx/skb.h>
     7  #include <bpf/helpers_skb.h>
     8  #include "pktgen.h"
     9  
    10  #define ETH_HLEN		0
    11  #define SECCTX_FROM_IPCACHE	1
    12  #define ENABLE_HOST_ROUTING
    13  #define ENABLE_IPV4
    14  #define ENABLE_IPV6
    15  
    16  #define TEST_IP_LOCAL		v4_pod_one
    17  #define TEST_IP_REMOTE		v4_pod_two
    18  #define TEST_IPV6_LOCAL		v6_pod_one
    19  #define TEST_IPV6_REMOTE	v6_pod_two
    20  #define TEST_LXC_ID_LOCAL	233
    21  
    22  /* We wanted to tail call handle_policy from bpf_lxc, but at present it's
    23   * impossible to #include both bpf_host.c and bpf_lxc.c at the same time.
    24   * Therefore, we created a stud, mock_hanle_policy, to simply check if the
    25   * our skb reaches there.
    26   */
    27  __section("mock-handle-policy")
    28  int mock_handle_policy(struct __ctx_buff *ctx __maybe_unused)
    29  {
    30  	return TC_ACT_REDIRECT;
    31  }
    32  
    33  struct {
    34  	__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
    35  	__uint(key_size, sizeof(__u32));
    36  	__uint(max_entries, 256);
    37  	__array(values, int());
    38  } mock_policy_call_map __section(".maps") = {
    39  	.values = {
    40  		[TEST_LXC_ID_LOCAL] = &mock_handle_policy,
    41  	},
    42  };
    43  
    44  #define tail_call_dynamic mock_tail_call_dynamic
    45  static __always_inline __maybe_unused void
    46  mock_tail_call_dynamic(struct __ctx_buff *ctx __maybe_unused,
    47  		       const void *map __maybe_unused, __u32 slot __maybe_unused)
    48  {
    49  	tail_call(ctx, &mock_policy_call_map, slot);
    50  }
    51  
    52  static volatile const __u8 *ep_mac = mac_one;
    53  static volatile const __u8 *node_mac = mac_two;
    54  
    55  #include "bpf_host.c"
    56  
    57  #include "lib/endpoint.h"
    58  
    59  struct {
    60  	__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
    61  	__uint(key_size, sizeof(__u32));
    62  	__uint(max_entries, 2);
    63  	__array(values, int());
    64  } entry_call_map __section(".maps") = {
    65  	.values = {
    66  		[0] = &cil_from_netdev,
    67  	},
    68  };
    69  
    70  PKTGEN("tc", "ipv4_l3_to_l2_fast_redirect")
    71  int ipv4_l3_to_l2_fast_redirect_pktgen(struct __ctx_buff *ctx)
    72  {
    73  	struct pktgen builder;
    74  	struct tcphdr *l4;
    75  	void *data;
    76  
    77  	/* Init packet builder */
    78  	pktgen__init(&builder, ctx);
    79  
    80  	/* We are building an L3 skb which doesn't have L2 header, so in theory
    81  	 * we need to skip L2 header and set ctx->protocol = bpf_ntohs(ETH_P_IP),
    82  	 * but bpf verifier doesn't allow us to do so, and kernel also doesn't
    83  	 * handle an L3 skb properly (see https://elixir.bootlin.com/linux/v6.2.1/source/net/bpf/test_run.c#L1156).
    84  	 * Therefore we workaround the issue by pushing L2 header in the PKTGEN
    85  	 * and stripping it in the SETUP.
    86  	 */
    87  
    88  	l4 = pktgen__push_ipv4_tcp_packet(&builder,
    89  					  (__u8 *)node_mac, (__u8 *)ep_mac,
    90  					  TEST_IP_REMOTE, TEST_IP_LOCAL,
    91  					  tcp_src_one, tcp_svc_one);
    92  	if (!l4)
    93  		return TEST_ERROR;
    94  
    95  	data = pktgen__push_data(&builder, default_data, sizeof(default_data));
    96  
    97  	if (!data)
    98  		return TEST_ERROR;
    99  
   100  	pktgen__finish(&builder);
   101  
   102  	return 0;
   103  }
   104  
   105  SETUP("tc", "ipv4_l3_to_l2_fast_redirect")
   106  int ipv4_l3_to_l2_fast_redirect_setup(struct __ctx_buff *ctx)
   107  {
   108  	void *data = (void *)(long)ctx->data;
   109  	void *data_end = (void *)(long)ctx->data_end;
   110  	__u64 flags = BPF_F_ADJ_ROOM_FIXED_GSO;
   111  
   112  	endpoint_v4_add_entry(TEST_IP_LOCAL, 0, TEST_LXC_ID_LOCAL, 0, 0,
   113  			      (__u8 *)ep_mac, (__u8 *)node_mac);
   114  
   115  	/* As commented in PKTGEN, now we strip the L2 header. Bpf helper
   116  	 * skb_adjust_room will use L2 header to overwrite L3 header, so we play
   117  	 * a trick to memcpy(ethhdr, iphdr, ETH_HLEN) ahead of skb_adjust_room
   118  	 * so as to guarantee L3 header keeps intact.
   119  	 */
   120  	if ((void *)data + __ETH_HLEN + __ETH_HLEN <= data_end)
   121  		memcpy(data, data + __ETH_HLEN, __ETH_HLEN);
   122  
   123  	skb_adjust_room(ctx, -__ETH_HLEN, BPF_ADJ_ROOM_MAC, flags);
   124  
   125  	tail_call_static(ctx, entry_call_map, 0);
   126  	return TEST_ERROR;
   127  }
   128  
   129  CHECK("tc", "ipv4_l3_to_l2_fast_redirect")
   130  int ipv4_l3_to_l2_fast_redirect_check(__maybe_unused const struct __ctx_buff *ctx)
   131  {
   132  	void *data;
   133  	void *data_end;
   134  	__u32 *status_code;
   135  	struct ethhdr *l2;
   136  	struct iphdr *l3;
   137  	struct tcphdr *l4;
   138  	__u8 *payload;
   139  
   140  	test_init();
   141  
   142  	data = (void *)(long)ctx->data;
   143  	data_end = (void *)(long)ctx->data_end;
   144  
   145  	if (data + sizeof(__u32) > data_end)
   146  		test_fatal("status code out of bounds");
   147  
   148  	status_code = data;
   149  
   150  	assert(*status_code == TC_ACT_REDIRECT);
   151  
   152  	l2 = data + sizeof(__u32);
   153  
   154  	if ((void *)l2 + sizeof(struct ethhdr) > data_end)
   155  		test_fatal("l2 out of bounds");
   156  
   157  	if (l2->h_proto != bpf_htons(ETH_P_IP))
   158  		test_fatal("l2 proto hasn't been set to ETH_P_IP");
   159  
   160  	if (memcmp(l2->h_source, (__u8 *)node_mac, ETH_ALEN) != 0)
   161  		test_fatal("src mac hasn't been set to router's mac");
   162  
   163  	if (memcmp(l2->h_dest, (__u8 *)ep_mac, ETH_ALEN) != 0)
   164  		test_fatal("dest mac hasn't been set to ep's mac");
   165  
   166  	l3 = data + sizeof(__u32) + sizeof(struct ethhdr);
   167  
   168  	if ((void *)l3 + sizeof(struct iphdr) > data_end)
   169  		test_fatal("l3 out of bounds");
   170  
   171  	if (l3->saddr != TEST_IP_REMOTE)
   172  		test_fatal("src IP was changed");
   173  
   174  	if (l3->daddr != TEST_IP_LOCAL)
   175  		test_fatal("dest IP was changed");
   176  
   177  	if (l3->check != bpf_htons(0xfa68))
   178  		test_fatal("L3 checksum is invalid: %d", bpf_htons(l3->check));
   179  
   180  	l4 = (void *)l3 + sizeof(struct iphdr);
   181  
   182  	if ((void *)l4 + sizeof(struct tcphdr) > data_end)
   183  		test_fatal("l4 out of bounds");
   184  
   185  	if (l4->source != tcp_src_one)
   186  		test_fatal("src TCP port was changed");
   187  
   188  	if (l4->dest != tcp_svc_one)
   189  		test_fatal("dst TCP port was changed");
   190  
   191  	payload = (void *)l4 + sizeof(struct tcphdr);
   192  	if ((void *)payload + sizeof(default_data) > data_end)
   193  		test_fatal("paylaod out of bounds\n");
   194  
   195  	if (memcmp(payload, default_data, sizeof(default_data)) != 0)
   196  		test_fatal("tcp payload was changed");
   197  
   198  	test_finish();
   199  }
   200  
   201  PKTGEN("tc", "ipv6_l3_to_l2_fast_redirect")
   202  int ipv6_l3_to_l2_fast_redirect_pktgen(struct __ctx_buff *ctx)
   203  {
   204  	struct pktgen builder;
   205  	struct tcphdr *l4;
   206  	void *data;
   207  
   208  	/* Init packet builder */
   209  	pktgen__init(&builder, ctx);
   210  
   211  	/* We are building an L3 skb which doesn't have L2 header, so in theory
   212  	 * we need to skip L2 header and set ctx->protocol = bpf_ntohs(ETH_P_IP),
   213  	 * but bpf verifier doesn't allow us to do so, and kernel also doesn't
   214  	 * handle an L3 skb properly (see https://elixir.bootlin.com/linux/v6.2.1/source/net/bpf/test_run.c#L1156).
   215  	 * Therefore we workaround the issue by pushing L2 header in the PKTGEN
   216  	 * and stripping it in the SETUP.
   217  	 */
   218  	l4 = pktgen__push_ipv6_tcp_packet(&builder,
   219  					  (__u8 *)node_mac, (__u8 *)ep_mac,
   220  					  (__u8 *)TEST_IPV6_REMOTE,
   221  					  (__u8 *)TEST_IPV6_LOCAL,
   222  					  tcp_src_one, tcp_svc_one);
   223  	if (!l4)
   224  		return TEST_ERROR;
   225  
   226  	data = pktgen__push_data(&builder, default_data, sizeof(default_data));
   227  
   228  	if (!data)
   229  		return TEST_ERROR;
   230  
   231  	pktgen__finish(&builder);
   232  
   233  	return 0;
   234  }
   235  
   236  SETUP("tc", "ipv6_l3_to_l2_fast_redirect")
   237  int ipv6_l3_to_l2_fast_redirect_setup(struct __ctx_buff *ctx)
   238  {
   239  	void *data = (void *)(long)ctx->data;
   240  	void *data_end = (void *)(long)ctx->data_end;
   241  	__u64 flags = BPF_F_ADJ_ROOM_FIXED_GSO;
   242  
   243  	endpoint_v6_add_entry((union v6addr *)TEST_IPV6_LOCAL, 0, TEST_LXC_ID_LOCAL, 0, 0,
   244  			      (__u8 *)ep_mac, (__u8 *)node_mac);
   245  
   246  	/* As commented in PKTGEN, now we strip the L2 header. Bpf helper
   247  	 * skb_adjust_room will use L2 header to overwrite L3 header, so we play
   248  	 * a trick to memcpy(ethhdr, ipv6hdr, ETH_HLEN) ahead of skb_adjust_room
   249  	 * so as to guarantee L3 header keeps intact.
   250  	 */
   251  	if ((void *)data + __ETH_HLEN + __ETH_HLEN <= data_end)
   252  		memcpy(data, data + __ETH_HLEN, __ETH_HLEN);
   253  
   254  	skb_adjust_room(ctx, -__ETH_HLEN, BPF_ADJ_ROOM_MAC, flags);
   255  
   256  	tail_call_static(ctx, entry_call_map, 0);
   257  	return TEST_ERROR;
   258  }
   259  
   260  CHECK("tc", "ipv6_l3_to_l2_fast_redirect")
   261  int ipv6_l3_to_l2_fast_redirect_check(__maybe_unused const struct __ctx_buff *ctx)
   262  {
   263  	void *data;
   264  	void *data_end;
   265  	__u32 *status_code;
   266  	struct ethhdr *l2;
   267  	struct ipv6hdr *l3;
   268  	struct tcphdr *l4;
   269  	__u8 *payload;
   270  
   271  	test_init();
   272  
   273  	data = (void *)(long)ctx->data;
   274  	data_end = (void *)(long)ctx->data_end;
   275  
   276  	if (data + sizeof(__u32) > data_end)
   277  		test_fatal("status code out of bounds");
   278  
   279  	status_code = data;
   280  
   281  	assert(*status_code == TC_ACT_REDIRECT);
   282  
   283  	l2 = data + sizeof(__u32);
   284  
   285  	if ((void *)l2 + sizeof(struct ethhdr) > data_end)
   286  		test_fatal("l2 out of bounds");
   287  
   288  	if (l2->h_proto != bpf_htons(ETH_P_IPV6))
   289  		test_fatal("l2 proto hasn't been set to ETH_P_IPV6");
   290  
   291  	if (memcmp(l2->h_source, (__u8 *)node_mac, ETH_ALEN) != 0)
   292  		test_fatal("src mac hasn't been set to router's mac");
   293  
   294  	if (memcmp(l2->h_dest, (__u8 *)ep_mac, ETH_ALEN) != 0)
   295  		test_fatal("dest mac hasn't been set to ep's mac");
   296  
   297  	l3 = data + sizeof(__u32) + sizeof(struct ethhdr);
   298  
   299  	if ((void *)l3 + sizeof(struct ipv6hdr) > data_end)
   300  		test_fatal("l3 out of bounds");
   301  
   302  	if (memcmp((__u8 *)&l3->saddr, (__u8 *)TEST_IPV6_REMOTE, 16) != 0)
   303  		test_fatal("src IP was changed");
   304  
   305  	if (memcmp((__u8 *)&l3->daddr, (__u8 *)TEST_IPV6_LOCAL, 16) != 0)
   306  		test_fatal("dest IP was changed");
   307  
   308  	l4 = (void *)l3 + sizeof(struct ipv6hdr);
   309  
   310  	if ((void *)l4 + sizeof(struct tcphdr) > data_end)
   311  		test_fatal("l4 out of bounds");
   312  
   313  	if (l4->source != tcp_src_one)
   314  		test_fatal("src TCP port was changed");
   315  
   316  	if (l4->dest != tcp_svc_one)
   317  		test_fatal("dst TCP port was changed");
   318  
   319  	payload = (void *)l4 + sizeof(struct tcphdr);
   320  	if ((void *)payload + sizeof(default_data) > data_end)
   321  		test_fatal("paylaod out of bounds\n");
   322  
   323  	if (memcmp(payload, default_data, sizeof(default_data)) != 0)
   324  		test_fatal("tcp payload was changed");
   325  
   326  	test_finish();
   327  }