Skip to content
Closed
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
21 changes: 18 additions & 3 deletions include/net/xdp_sock_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,23 @@ static inline struct xdp_buff *xsk_buff_get_frag(const struct xdp_buff *first)
return ret;
}

static inline void xsk_buff_del_tail(struct xdp_buff *tail)
static inline void xsk_buff_del_frag(struct xdp_buff *xdp)
{
struct xdp_buff_xsk *xskb = container_of(tail, struct xdp_buff_xsk, xdp);
struct xdp_buff_xsk *xskb = container_of(xdp, struct xdp_buff_xsk, xdp);

list_del(&xskb->list_node);
}

static inline struct xdp_buff *xsk_buff_get_head(struct xdp_buff *first)
{
struct xdp_buff_xsk *xskb = container_of(first, struct xdp_buff_xsk, xdp);
struct xdp_buff_xsk *frag;

frag = list_first_entry(&xskb->pool->xskb_list, struct xdp_buff_xsk,
list_node);
return &frag->xdp;
}

static inline struct xdp_buff *xsk_buff_get_tail(struct xdp_buff *first)
{
struct xdp_buff_xsk *xskb = container_of(first, struct xdp_buff_xsk, xdp);
Expand Down Expand Up @@ -389,8 +399,13 @@ static inline struct xdp_buff *xsk_buff_get_frag(const struct xdp_buff *first)
return NULL;
}

static inline void xsk_buff_del_tail(struct xdp_buff *tail)
static inline void xsk_buff_del_frag(struct xdp_buff *xdp)
{
}

static inline struct xdp_buff *xsk_buff_get_head(struct xdp_buff *first)
{
return NULL;
}

static inline struct xdp_buff *xsk_buff_get_tail(struct xdp_buff *first)
Expand Down
13 changes: 13 additions & 0 deletions kernel/bpf/verifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -12145,6 +12145,7 @@ enum special_kfunc_type {
KF_bpf_dynptr_from_skb,
KF_bpf_dynptr_from_xdp,
KF_bpf_dynptr_from_skb_meta,
KF_bpf_xdp_pull_data,
KF_bpf_dynptr_slice,
KF_bpf_dynptr_slice_rdwr,
KF_bpf_dynptr_clone,
Expand Down Expand Up @@ -12195,10 +12196,12 @@ BTF_ID(func, bpf_rbtree_right)
BTF_ID(func, bpf_dynptr_from_skb)
BTF_ID(func, bpf_dynptr_from_xdp)
BTF_ID(func, bpf_dynptr_from_skb_meta)
BTF_ID(func, bpf_xdp_pull_data)
#else
BTF_ID_UNUSED
BTF_ID_UNUSED
BTF_ID_UNUSED
BTF_ID_UNUSED
#endif
BTF_ID(func, bpf_dynptr_slice)
BTF_ID(func, bpf_dynptr_slice_rdwr)
Expand Down Expand Up @@ -12268,6 +12271,11 @@ static bool is_kfunc_bpf_preempt_enable(struct bpf_kfunc_call_arg_meta *meta)
return meta->func_id == special_kfunc_list[KF_bpf_preempt_enable];
}

static bool is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta)
{
return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data];
}

static enum kfunc_ptr_arg_type
get_kfunc_ptr_arg_type(struct bpf_verifier_env *env,
struct bpf_kfunc_call_arg_meta *meta,
Expand Down Expand Up @@ -13997,6 +14005,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
}
}

if (is_kfunc_pkt_changing(&meta))
clear_all_pkt_pointers(env);

nargs = btf_type_vlen(meta.func_proto);
args = (const struct btf_param *)(meta.func_proto + 1);
for (i = 0; i < nargs; i++) {
Expand Down Expand Up @@ -17714,6 +17725,8 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
*/
if (ret == 0 && is_kfunc_sleepable(&meta))
mark_subprog_might_sleep(env, t);
if (ret == 0 && is_kfunc_pkt_changing(&meta))
mark_subprog_changes_pkt_data(env, t);
}
return visit_func_call_insn(t, insns, env, insn->src_reg == BPF_PSEUDO_CALL);

Expand Down
9 changes: 7 additions & 2 deletions net/bpf/test_run.c
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ static void *bpf_test_init(const union bpf_attr *kattr, u32 user_size,
void __user *data_in = u64_to_user_ptr(kattr->test.data_in);
void *data;

if (user_size < ETH_HLEN || user_size > PAGE_SIZE - headroom - tailroom)
if (user_size > PAGE_SIZE - headroom - tailroom)
return ERR_PTR(-EINVAL);

size = SKB_DATA_ALIGN(size);
Expand Down Expand Up @@ -1001,6 +1001,9 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
kattr->test.cpu || kattr->test.batch_size)
return -EINVAL;

if (size < ETH_HLEN)
return -EINVAL;

data = bpf_test_init(kattr, kattr->test.data_size_in,
size, NET_SKB_PAD + NET_IP_ALIGN,
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)));
Expand Down Expand Up @@ -1246,13 +1249,15 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr,

if (ctx) {
/* There can't be user provided data before the meta data */
if (ctx->data_meta || ctx->data_end != size ||
if (ctx->data_meta || ctx->data_end > size ||
ctx->data > ctx->data_end ||
unlikely(xdp_metalen_invalid(ctx->data)) ||
(do_live && (kattr->test.data_out || kattr->test.ctx_out)))
goto free_ctx;
/* Meta data is allocated from the headroom */
headroom -= ctx->data;

size = ctx->data_end;
}

max_data_sz = PAGE_SIZE - headroom - tailroom;
Expand Down
119 changes: 108 additions & 11 deletions net/core/filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -4153,34 +4153,44 @@ static int bpf_xdp_frags_increase_tail(struct xdp_buff *xdp, int offset)
return 0;
}

static void bpf_xdp_shrink_data_zc(struct xdp_buff *xdp, int shrink,
static void bpf_xdp_shrink_data_zc(struct xdp_buff *xdp, int shrink, bool tail,
enum xdp_mem_type mem_type, bool release)
{
struct xdp_buff *zc_frag = xsk_buff_get_tail(xdp);
struct xdp_buff *zc_frag = tail ? xsk_buff_get_tail(xdp) :
xsk_buff_get_head(xdp);

if (release) {
xsk_buff_del_tail(zc_frag);
xsk_buff_del_frag(zc_frag);
__xdp_return(0, mem_type, false, zc_frag);
} else {
zc_frag->data_end -= shrink;
if (tail)
zc_frag->data_end -= shrink;
else
zc_frag->data += shrink;
}
}

static bool bpf_xdp_shrink_data(struct xdp_buff *xdp, skb_frag_t *frag,
int shrink)
int shrink, bool tail)
{
enum xdp_mem_type mem_type = xdp->rxq->mem.type;
bool release = skb_frag_size(frag) == shrink;

if (mem_type == MEM_TYPE_XSK_BUFF_POOL) {
bpf_xdp_shrink_data_zc(xdp, shrink, mem_type, release);
bpf_xdp_shrink_data_zc(xdp, shrink, tail, mem_type, release);
goto out;
}

if (release)
__xdp_return(skb_frag_netmem(frag), mem_type, false, NULL);

out:
if (!release) {
if (!tail)
skb_frag_off_add(frag, shrink);
skb_frag_size_sub(frag, shrink);
}

return release;
}

Expand All @@ -4198,12 +4208,8 @@ static int bpf_xdp_frags_shrink_tail(struct xdp_buff *xdp, int offset)

len_free += shrink;
offset -= shrink;
if (bpf_xdp_shrink_data(xdp, frag, shrink)) {
if (bpf_xdp_shrink_data(xdp, frag, shrink, true))
n_frags_free++;
} else {
skb_frag_size_sub(frag, shrink);
break;
}
}
sinfo->nr_frags -= n_frags_free;
sinfo->xdp_frags_size -= len_free;
Expand Down Expand Up @@ -12210,6 +12216,96 @@ __bpf_kfunc int bpf_sock_ops_enable_tx_tstamp(struct bpf_sock_ops_kern *skops,
return 0;
}

/**
* bpf_xdp_pull_data() - Pull in non-linear xdp data.
* @x: &xdp_md associated with the XDP buffer
* @len: length of data to be made directly accessible in the linear part
*
* Pull in data in case the XDP buffer associated with @x is non-linear and
* not all @len are in the linear data area.
*
* Direct packet access allows reading and writing linear XDP data through
* packet pointers (i.e., &xdp_md->data + offsets). The amount of data which
* ends up in the linear part of the xdp_buff depends on the NIC and its
* configuration. When a frag-capable XDP program wants to directly access
* headers that may be in the non-linear area, call this kfunc to make sure
* the data is available in the linear area. Alternatively, use dynptr or
* bpf_xdp_{load,store}_bytes() to access data without pulling.
*
* This kfunc can also be used with bpf_xdp_adjust_head() to decapsulate
* headers in the non-linear data area.
*
* A call to this kfunc may reduce headroom. If there is not enough tailroom
* in the linear data area, metadata and data will be shifted down.
*
* A call to this kfunc is susceptible to change the buffer geometry.
* Therefore, at load time, all checks on pointers previously done by the
* verifier are invalidated and must be performed again, if the kfunc is used
* in combination with direct packet access.
*
* Return:
* * %0 - success
* * %-EINVAL - invalid len
*/
__bpf_kfunc int bpf_xdp_pull_data(struct xdp_md *x, u32 len)
{
struct xdp_buff *xdp = (struct xdp_buff *)x;
struct skb_shared_info *sinfo = xdp_get_shared_info_from_buff(xdp);
int i, delta, shift, headroom, tailroom, n_frags_free = 0;
void *data_hard_end = xdp_data_hard_end(xdp);
int data_len = xdp->data_end - xdp->data;
void *start;

if (len <= data_len)
return 0;

if (unlikely(len > xdp_get_buff_len(xdp)))
return -EINVAL;

start = xdp_data_meta_unsupported(xdp) ? xdp->data : xdp->data_meta;

headroom = start - xdp->data_hard_start - sizeof(struct xdp_frame);
tailroom = data_hard_end - xdp->data_end;

delta = len - data_len;
if (unlikely(delta > tailroom + headroom))
return -EINVAL;

shift = delta - tailroom;
if (shift > 0) {
memmove(start - shift, start, xdp->data_end - start);

xdp->data_meta -= shift;
xdp->data -= shift;
xdp->data_end -= shift;
}

for (i = 0; i < sinfo->nr_frags && delta; i++) {
skb_frag_t *frag = &sinfo->frags[i];
u32 shrink = min_t(u32, delta, skb_frag_size(frag));

memcpy(xdp->data_end, skb_frag_address(frag), shrink);

xdp->data_end += shrink;
sinfo->xdp_frags_size -= shrink;
delta -= shrink;
if (bpf_xdp_shrink_data(xdp, frag, shrink, false))
n_frags_free++;
}

if (unlikely(n_frags_free)) {
memmove(sinfo->frags, sinfo->frags + n_frags_free,
(sinfo->nr_frags - n_frags_free) * sizeof(skb_frag_t));

sinfo->nr_frags -= n_frags_free;

if (!sinfo->nr_frags)
xdp_buff_clear_frags_flag(xdp);
}

return 0;
}

__bpf_kfunc_end_defs();

int bpf_dynptr_from_skb_rdonly(struct __sk_buff *skb, u64 flags,
Expand Down Expand Up @@ -12237,6 +12333,7 @@ BTF_KFUNCS_END(bpf_kfunc_check_set_skb_meta)

BTF_KFUNCS_START(bpf_kfunc_check_set_xdp)
BTF_ID_FLAGS(func, bpf_dynptr_from_xdp)
BTF_ID_FLAGS(func, bpf_xdp_pull_data)
BTF_KFUNCS_END(bpf_kfunc_check_set_xdp)

BTF_KFUNCS_START(bpf_kfunc_check_set_sock_addr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ void test_xdp_context_test_run(void)
/* Meta data must be 255 bytes or smaller */
test_xdp_context_error(prog_fd, opts, 0, 256, sizeof(data), 0, 0, 0);

/* Total size of data must match data_end - data_meta */
test_xdp_context_error(prog_fd, opts, 0, sizeof(__u32),
sizeof(data) - 1, 0, 0, 0);
/* Total size of data must be data_end - data_meta or larger */
test_xdp_context_error(prog_fd, opts, 0, sizeof(__u32),
sizeof(data) + 1, 0, 0, 0);

Expand Down
Loading
Loading