mirror of
https://github.com/ukui/kernel.git
synced 2026-03-09 10:07:04 -07:00
bpf: selftests: Tcp header options
This patch adds tests for the new bpf tcp header option feature. test_tcp_hdr_options.c: - It tests header option writing and parsing in 3WHS: regular connection establishment, fastopen, and syncookie. - In syncookie, the passive side's bpf prog is asking the active side to resend its bpf header option by specifying a RESEND bit in the outgoing SYNACK. handle_active_estab() and write_nodata_opt() has some details. - handle_passive_estab() has comments on fastopen. - It also has test for header writing and parsing in FIN packet. - Most of the tests is writing an experimental option 254 with magic 0xeB9F. - The no_exprm_estab() also tests writing a regular TCP option without any magic. test_misc_tcp_options.c: - It is an one directional test. Active side writes option and passive side parses option. The focus is to exercise the new helpers and API. - Testing the new helper: bpf_load_hdr_opt() and bpf_store_hdr_opt(). - Testing the bpf_getsockopt(TCP_BPF_SYN). - Negative tests for the above helpers. - Testing the sock_ops->skb_data. Signed-off-by: Martin KaFai Lau <kafai@fb.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/bpf/20200820190117.2886749-1-kafai@fb.com
This commit is contained in:
committed by
Alexei Starovoitov
parent
8085e1dc1f
commit
ad2f8eb009
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,325 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2020 Facebook */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/ipv6.h>
|
||||
#include <linux/tcp.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/types.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#define BPF_PROG_TEST_TCP_HDR_OPTIONS
|
||||
#include "test_tcp_hdr_options.h"
|
||||
|
||||
__u16 last_addr16_n = __bpf_htons(0xeB9F);
|
||||
__u16 active_lport_n = 0;
|
||||
__u16 active_lport_h = 0;
|
||||
__u16 passive_lport_n = 0;
|
||||
__u16 passive_lport_h = 0;
|
||||
|
||||
/* options received at passive side */
|
||||
unsigned int nr_pure_ack = 0;
|
||||
unsigned int nr_data = 0;
|
||||
unsigned int nr_syn = 0;
|
||||
unsigned int nr_fin = 0;
|
||||
|
||||
/* Check the header received from the active side */
|
||||
static int __check_active_hdr_in(struct bpf_sock_ops *skops, bool check_syn)
|
||||
{
|
||||
union {
|
||||
struct tcphdr th;
|
||||
struct ipv6hdr ip6;
|
||||
struct tcp_exprm_opt exprm_opt;
|
||||
struct tcp_opt reg_opt;
|
||||
__u8 data[100]; /* IPv6 (40) + Max TCP hdr (60) */
|
||||
} hdr = {};
|
||||
__u64 load_flags = check_syn ? BPF_LOAD_HDR_OPT_TCP_SYN : 0;
|
||||
struct tcphdr *pth;
|
||||
int ret;
|
||||
|
||||
hdr.reg_opt.kind = 0xB9;
|
||||
|
||||
/* The option is 4 bytes long instead of 2 bytes */
|
||||
ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, 2, load_flags);
|
||||
if (ret != -ENOSPC)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
/* Test searching magic with regular kind */
|
||||
hdr.reg_opt.len = 4;
|
||||
ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt),
|
||||
load_flags);
|
||||
if (ret != -EINVAL)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
hdr.reg_opt.len = 0;
|
||||
ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt),
|
||||
load_flags);
|
||||
if (ret != 4 || hdr.reg_opt.len != 4 || hdr.reg_opt.kind != 0xB9 ||
|
||||
hdr.reg_opt.data[0] != 0xfa || hdr.reg_opt.data[1] != 0xce)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
/* Test searching experimental option with invalid kind length */
|
||||
hdr.exprm_opt.kind = TCPOPT_EXP;
|
||||
hdr.exprm_opt.len = 5;
|
||||
hdr.exprm_opt.magic = 0;
|
||||
ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
|
||||
load_flags);
|
||||
if (ret != -EINVAL)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
/* Test searching experimental option with 0 magic value */
|
||||
hdr.exprm_opt.len = 4;
|
||||
ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
|
||||
load_flags);
|
||||
if (ret != -ENOMSG)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
hdr.exprm_opt.magic = __bpf_htons(0xeB9F);
|
||||
ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
|
||||
load_flags);
|
||||
if (ret != 4 || hdr.exprm_opt.len != 4 ||
|
||||
hdr.exprm_opt.kind != TCPOPT_EXP ||
|
||||
hdr.exprm_opt.magic != __bpf_htons(0xeB9F))
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
if (!check_syn)
|
||||
return CG_OK;
|
||||
|
||||
/* Test loading from skops->syn_skb if sk_state == TCP_NEW_SYN_RECV
|
||||
*
|
||||
* Test loading from tp->saved_syn for other sk_state.
|
||||
*/
|
||||
ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr.ip6,
|
||||
sizeof(hdr.ip6));
|
||||
if (ret != -ENOSPC)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
if (hdr.ip6.saddr.s6_addr16[7] != last_addr16_n ||
|
||||
hdr.ip6.daddr.s6_addr16[7] != last_addr16_n)
|
||||
RET_CG_ERR(0);
|
||||
|
||||
ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr, sizeof(hdr));
|
||||
if (ret < 0)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
pth = (struct tcphdr *)(&hdr.ip6 + 1);
|
||||
if (pth->dest != passive_lport_n || pth->source != active_lport_n)
|
||||
RET_CG_ERR(0);
|
||||
|
||||
ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN, &hdr, sizeof(hdr));
|
||||
if (ret < 0)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
if (hdr.th.dest != passive_lport_n || hdr.th.source != active_lport_n)
|
||||
RET_CG_ERR(0);
|
||||
|
||||
return CG_OK;
|
||||
}
|
||||
|
||||
static int check_active_syn_in(struct bpf_sock_ops *skops)
|
||||
{
|
||||
return __check_active_hdr_in(skops, true);
|
||||
}
|
||||
|
||||
static int check_active_hdr_in(struct bpf_sock_ops *skops)
|
||||
{
|
||||
struct tcphdr *th;
|
||||
|
||||
if (__check_active_hdr_in(skops, false) == CG_ERR)
|
||||
return CG_ERR;
|
||||
|
||||
th = skops->skb_data;
|
||||
if (th + 1 > skops->skb_data_end)
|
||||
RET_CG_ERR(0);
|
||||
|
||||
if (tcp_hdrlen(th) < skops->skb_len)
|
||||
nr_data++;
|
||||
|
||||
if (th->fin)
|
||||
nr_fin++;
|
||||
|
||||
if (th->ack && !th->fin && tcp_hdrlen(th) == skops->skb_len)
|
||||
nr_pure_ack++;
|
||||
|
||||
return CG_OK;
|
||||
}
|
||||
|
||||
static int active_opt_len(struct bpf_sock_ops *skops)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* Reserve more than enough to allow the -EEXIST test in
|
||||
* the write_active_opt().
|
||||
*/
|
||||
err = bpf_reserve_hdr_opt(skops, 12, 0);
|
||||
if (err)
|
||||
RET_CG_ERR(err);
|
||||
|
||||
return CG_OK;
|
||||
}
|
||||
|
||||
static int write_active_opt(struct bpf_sock_ops *skops)
|
||||
{
|
||||
struct tcp_exprm_opt exprm_opt = {};
|
||||
struct tcp_opt win_scale_opt = {};
|
||||
struct tcp_opt reg_opt = {};
|
||||
struct tcphdr *th;
|
||||
int err, ret;
|
||||
|
||||
exprm_opt.kind = TCPOPT_EXP;
|
||||
exprm_opt.len = 4;
|
||||
exprm_opt.magic = __bpf_htons(0xeB9F);
|
||||
|
||||
reg_opt.kind = 0xB9;
|
||||
reg_opt.len = 4;
|
||||
reg_opt.data[0] = 0xfa;
|
||||
reg_opt.data[1] = 0xce;
|
||||
|
||||
win_scale_opt.kind = TCPOPT_WINDOW;
|
||||
|
||||
err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
|
||||
if (err)
|
||||
RET_CG_ERR(err);
|
||||
|
||||
/* Store the same exprm option */
|
||||
err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
|
||||
if (err != -EEXIST)
|
||||
RET_CG_ERR(err);
|
||||
|
||||
err = bpf_store_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0);
|
||||
if (err)
|
||||
RET_CG_ERR(err);
|
||||
err = bpf_store_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0);
|
||||
if (err != -EEXIST)
|
||||
RET_CG_ERR(err);
|
||||
|
||||
/* Check the option has been written and can be searched */
|
||||
ret = bpf_load_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
|
||||
if (ret != 4 || exprm_opt.len != 4 || exprm_opt.kind != TCPOPT_EXP ||
|
||||
exprm_opt.magic != __bpf_htons(0xeB9F))
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
reg_opt.len = 0;
|
||||
ret = bpf_load_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0);
|
||||
if (ret != 4 || reg_opt.len != 4 || reg_opt.kind != 0xB9 ||
|
||||
reg_opt.data[0] != 0xfa || reg_opt.data[1] != 0xce)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
th = skops->skb_data;
|
||||
if (th + 1 > skops->skb_data_end)
|
||||
RET_CG_ERR(0);
|
||||
|
||||
if (th->syn) {
|
||||
active_lport_h = skops->local_port;
|
||||
active_lport_n = th->source;
|
||||
|
||||
/* Search the win scale option written by kernel
|
||||
* in the SYN packet.
|
||||
*/
|
||||
ret = bpf_load_hdr_opt(skops, &win_scale_opt,
|
||||
sizeof(win_scale_opt), 0);
|
||||
if (ret != 3 || win_scale_opt.len != 3 ||
|
||||
win_scale_opt.kind != TCPOPT_WINDOW)
|
||||
RET_CG_ERR(ret);
|
||||
|
||||
/* Write the win scale option that kernel
|
||||
* has already written.
|
||||
*/
|
||||
err = bpf_store_hdr_opt(skops, &win_scale_opt,
|
||||
sizeof(win_scale_opt), 0);
|
||||
if (err != -EEXIST)
|
||||
RET_CG_ERR(err);
|
||||
}
|
||||
|
||||
return CG_OK;
|
||||
}
|
||||
|
||||
static int handle_hdr_opt_len(struct bpf_sock_ops *skops)
|
||||
{
|
||||
__u8 tcp_flags = skops_tcp_flags(skops);
|
||||
|
||||
if ((tcp_flags & TCPHDR_SYNACK) == TCPHDR_SYNACK)
|
||||
/* Check the SYN from bpf_sock_ops_kern->syn_skb */
|
||||
return check_active_syn_in(skops);
|
||||
|
||||
/* Passive side should have cleared the write hdr cb by now */
|
||||
if (skops->local_port == passive_lport_h)
|
||||
RET_CG_ERR(0);
|
||||
|
||||
return active_opt_len(skops);
|
||||
}
|
||||
|
||||
static int handle_write_hdr_opt(struct bpf_sock_ops *skops)
|
||||
{
|
||||
if (skops->local_port == passive_lport_h)
|
||||
RET_CG_ERR(0);
|
||||
|
||||
return write_active_opt(skops);
|
||||
}
|
||||
|
||||
static int handle_parse_hdr(struct bpf_sock_ops *skops)
|
||||
{
|
||||
/* Passive side is not writing any non-standard/unknown
|
||||
* option, so the active side should never be called.
|
||||
*/
|
||||
if (skops->local_port == active_lport_h)
|
||||
RET_CG_ERR(0);
|
||||
|
||||
return check_active_hdr_in(skops);
|
||||
}
|
||||
|
||||
static int handle_passive_estab(struct bpf_sock_ops *skops)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* No more write hdr cb */
|
||||
bpf_sock_ops_cb_flags_set(skops,
|
||||
skops->bpf_sock_ops_cb_flags &
|
||||
~BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG);
|
||||
|
||||
/* Recheck the SYN but check the tp->saved_syn this time */
|
||||
err = check_active_syn_in(skops);
|
||||
if (err == CG_ERR)
|
||||
return err;
|
||||
|
||||
nr_syn++;
|
||||
|
||||
/* The ack has header option written by the active side also */
|
||||
return check_active_hdr_in(skops);
|
||||
}
|
||||
|
||||
SEC("sockops/misc_estab")
|
||||
int misc_estab(struct bpf_sock_ops *skops)
|
||||
{
|
||||
int true_val = 1;
|
||||
|
||||
switch (skops->op) {
|
||||
case BPF_SOCK_OPS_TCP_LISTEN_CB:
|
||||
passive_lport_h = skops->local_port;
|
||||
passive_lport_n = __bpf_htons(passive_lport_h);
|
||||
bpf_setsockopt(skops, SOL_TCP, TCP_SAVE_SYN,
|
||||
&true_val, sizeof(true_val));
|
||||
set_hdr_cb_flags(skops);
|
||||
break;
|
||||
case BPF_SOCK_OPS_TCP_CONNECT_CB:
|
||||
set_hdr_cb_flags(skops);
|
||||
break;
|
||||
case BPF_SOCK_OPS_PARSE_HDR_OPT_CB:
|
||||
return handle_parse_hdr(skops);
|
||||
case BPF_SOCK_OPS_HDR_OPT_LEN_CB:
|
||||
return handle_hdr_opt_len(skops);
|
||||
case BPF_SOCK_OPS_WRITE_HDR_OPT_CB:
|
||||
return handle_write_hdr_opt(skops);
|
||||
case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
|
||||
return handle_passive_estab(skops);
|
||||
}
|
||||
|
||||
return CG_OK;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (c) 2020 Facebook */
|
||||
|
||||
#ifndef _TEST_TCP_HDR_OPTIONS_H
|
||||
#define _TEST_TCP_HDR_OPTIONS_H
|
||||
|
||||
struct bpf_test_option {
|
||||
__u8 flags;
|
||||
__u8 max_delack_ms;
|
||||
__u8 rand;
|
||||
} __attribute__((packed));
|
||||
|
||||
enum {
|
||||
OPTION_RESEND,
|
||||
OPTION_MAX_DELACK_MS,
|
||||
OPTION_RAND,
|
||||
__NR_OPTION_FLAGS,
|
||||
};
|
||||
|
||||
#define OPTION_F_RESEND (1 << OPTION_RESEND)
|
||||
#define OPTION_F_MAX_DELACK_MS (1 << OPTION_MAX_DELACK_MS)
|
||||
#define OPTION_F_RAND (1 << OPTION_RAND)
|
||||
#define OPTION_MASK ((1 << __NR_OPTION_FLAGS) - 1)
|
||||
|
||||
#define TEST_OPTION_FLAGS(flags, option) (1 & ((flags) >> (option)))
|
||||
#define SET_OPTION_FLAGS(flags, option) ((flags) |= (1 << (option)))
|
||||
|
||||
/* Store in bpf_sk_storage */
|
||||
struct hdr_stg {
|
||||
bool active;
|
||||
bool resend_syn; /* active side only */
|
||||
bool syncookie; /* passive side only */
|
||||
bool fastopen; /* passive side only */
|
||||
};
|
||||
|
||||
struct linum_err {
|
||||
unsigned int linum;
|
||||
int err;
|
||||
};
|
||||
|
||||
#define TCPHDR_FIN 0x01
|
||||
#define TCPHDR_SYN 0x02
|
||||
#define TCPHDR_RST 0x04
|
||||
#define TCPHDR_PSH 0x08
|
||||
#define TCPHDR_ACK 0x10
|
||||
#define TCPHDR_URG 0x20
|
||||
#define TCPHDR_ECE 0x40
|
||||
#define TCPHDR_CWR 0x80
|
||||
#define TCPHDR_SYNACK (TCPHDR_SYN | TCPHDR_ACK)
|
||||
|
||||
#define TCPOPT_EOL 0
|
||||
#define TCPOPT_NOP 1
|
||||
#define TCPOPT_WINDOW 3
|
||||
#define TCPOPT_EXP 254
|
||||
|
||||
#define TCP_BPF_EXPOPT_BASE_LEN 4
|
||||
#define MAX_TCP_HDR_LEN 60
|
||||
#define MAX_TCP_OPTION_SPACE 40
|
||||
|
||||
#ifdef BPF_PROG_TEST_TCP_HDR_OPTIONS
|
||||
|
||||
#define CG_OK 1
|
||||
#define CG_ERR 0
|
||||
|
||||
#ifndef SOL_TCP
|
||||
#define SOL_TCP 6
|
||||
#endif
|
||||
|
||||
struct tcp_exprm_opt {
|
||||
__u8 kind;
|
||||
__u8 len;
|
||||
__u16 magic;
|
||||
union {
|
||||
__u8 data[4];
|
||||
__u32 data32;
|
||||
};
|
||||
} __attribute__((packed));
|
||||
|
||||
struct tcp_opt {
|
||||
__u8 kind;
|
||||
__u8 len;
|
||||
union {
|
||||
__u8 data[4];
|
||||
__u32 data32;
|
||||
};
|
||||
} __attribute__((packed));
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 2);
|
||||
__type(key, int);
|
||||
__type(value, struct linum_err);
|
||||
} lport_linum_map SEC(".maps");
|
||||
|
||||
static inline unsigned int tcp_hdrlen(const struct tcphdr *th)
|
||||
{
|
||||
return th->doff << 2;
|
||||
}
|
||||
|
||||
static inline __u8 skops_tcp_flags(const struct bpf_sock_ops *skops)
|
||||
{
|
||||
return skops->skb_tcp_flags;
|
||||
}
|
||||
|
||||
static inline void clear_hdr_cb_flags(struct bpf_sock_ops *skops)
|
||||
{
|
||||
bpf_sock_ops_cb_flags_set(skops,
|
||||
skops->bpf_sock_ops_cb_flags &
|
||||
~(BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG |
|
||||
BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG));
|
||||
}
|
||||
|
||||
static inline void set_hdr_cb_flags(struct bpf_sock_ops *skops)
|
||||
{
|
||||
bpf_sock_ops_cb_flags_set(skops,
|
||||
skops->bpf_sock_ops_cb_flags |
|
||||
BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG |
|
||||
BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG);
|
||||
}
|
||||
static inline void
|
||||
clear_parse_all_hdr_cb_flags(struct bpf_sock_ops *skops)
|
||||
{
|
||||
bpf_sock_ops_cb_flags_set(skops,
|
||||
skops->bpf_sock_ops_cb_flags &
|
||||
~BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG);
|
||||
}
|
||||
|
||||
static inline void
|
||||
set_parse_all_hdr_cb_flags(struct bpf_sock_ops *skops)
|
||||
{
|
||||
bpf_sock_ops_cb_flags_set(skops,
|
||||
skops->bpf_sock_ops_cb_flags |
|
||||
BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG);
|
||||
}
|
||||
|
||||
#define RET_CG_ERR(__err) ({ \
|
||||
struct linum_err __linum_err; \
|
||||
int __lport; \
|
||||
\
|
||||
__linum_err.linum = __LINE__; \
|
||||
__linum_err.err = __err; \
|
||||
__lport = skops->local_port; \
|
||||
bpf_map_update_elem(&lport_linum_map, &__lport, &__linum_err, BPF_NOEXIST); \
|
||||
clear_hdr_cb_flags(skops); \
|
||||
clear_parse_all_hdr_cb_flags(skops); \
|
||||
return CG_ERR; \
|
||||
})
|
||||
|
||||
#endif /* BPF_PROG_TEST_TCP_HDR_OPTIONS */
|
||||
|
||||
#endif /* _TEST_TCP_HDR_OPTIONS_H */
|
||||
Reference in New Issue
Block a user