root/tools/testing/selftests/bpf/progs/test_parse_tcp_hdr_opt_dynptr.c
// SPDX-License-Identifier: GPL-2.0

/* This logic is lifted from a real-world use case of packet parsing, used in
 * the open source library katran, a layer 4 load balancer.
 *
 * This test demonstrates how to parse packet contents using dynptrs. The
 * original code (parsing without dynptrs) can be found in test_parse_tcp_hdr_opt.c
 */

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/tcp.h>
#include <stdbool.h>
#include <linux/ipv6.h>
#include <linux/if_ether.h>
#include "test_tcp_hdr_options.h"
#include "bpf_kfuncs.h"

char _license[] SEC("license") = "GPL";

/* Kind number used for experiments */
const __u32 tcp_hdr_opt_kind_tpr = 0xFD;
/* Length of the tcp header option */
const __u32 tcp_hdr_opt_len_tpr = 6;
/* maximum number of header options to check to lookup server_id */
const __u32 tcp_hdr_opt_max_opt_checks = 15;

__u32 server_id;

static int parse_hdr_opt(struct bpf_dynptr *ptr, __u32 *off, __u8 *hdr_bytes_remaining,
                         __u32 *server_id)
{
        __u8 kind, hdr_len;
        __u8 buffer[sizeof(kind) + sizeof(hdr_len) + sizeof(*server_id)];
        __u8 *data;

        __builtin_memset(buffer, 0, sizeof(buffer));

        data = bpf_dynptr_slice(ptr, *off, buffer, sizeof(buffer));
        if (!data)
                return -1;

        kind = data[0];

        if (kind == TCPOPT_EOL)
                return -1;

        if (kind == TCPOPT_NOP) {
                *off += 1;
                *hdr_bytes_remaining -= 1;
                return 0;
        }

        if (*hdr_bytes_remaining < 2)
                return -1;

        hdr_len = data[1];
        if (hdr_len > *hdr_bytes_remaining)
                return -1;

        if (kind == tcp_hdr_opt_kind_tpr) {
                if (hdr_len != tcp_hdr_opt_len_tpr)
                        return -1;

                __builtin_memcpy(server_id, (__u32 *)(data + 2), sizeof(*server_id));
                return 1;
        }

        *off += hdr_len;
        *hdr_bytes_remaining -= hdr_len;
        return 0;
}

SEC("xdp")
int xdp_ingress_v6(struct xdp_md *xdp)
{
        __u8 buffer[sizeof(struct tcphdr)] = {};
        __u8 hdr_bytes_remaining;
        struct tcphdr *tcp_hdr;
        __u8 tcp_hdr_opt_len;
        int err = 0;
        __u32 off;

        struct bpf_dynptr ptr;

        bpf_dynptr_from_xdp(xdp, 0, &ptr);

        off = sizeof(struct ethhdr) + sizeof(struct ipv6hdr);

        tcp_hdr = bpf_dynptr_slice(&ptr, off, buffer, sizeof(buffer));
        if (!tcp_hdr)
                return XDP_DROP;

        tcp_hdr_opt_len = (tcp_hdr->doff * 4) - sizeof(struct tcphdr);
        if (tcp_hdr_opt_len < tcp_hdr_opt_len_tpr)
                return XDP_DROP;

        hdr_bytes_remaining = tcp_hdr_opt_len;

        off += sizeof(struct tcphdr);

        /* max number of bytes of options in tcp header is 40 bytes */
        for (int i = 0; i < tcp_hdr_opt_max_opt_checks; i++) {
                err = parse_hdr_opt(&ptr, &off, &hdr_bytes_remaining, &server_id);

                if (err || !hdr_bytes_remaining)
                        break;
        }

        if (!server_id)
                return XDP_DROP;

        return XDP_PASS;
}