forked from Minki/linux
Merge git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next
Daniel Borkmann says: ==================== pull-request: bpf-next 2018-10-16 The following pull-request contains BPF updates for your *net-next* tree. The main changes are: 1) Convert BPF sockmap and kTLS to both use a new sk_msg API and enable sk_msg BPF integration for the latter, from Daniel and John. 2) Enable BPF syscall side to indicate for maps that they do not support a map lookup operation as opposed to just missing key, from Prashant. 3) Add bpftool map create command which after map creation pins the map into bpf fs for further processing, from Jakub. 4) Add bpftool support for attaching programs to maps allowing sock_map and sock_hash to be used from bpftool, from John. 5) Improve syscall BPF map update/delete path for map-in-map types to wait a RCU grace period for pending references to complete, from Daniel. 6) Couple of follow-up fixes for the BPF socket lookup to get it enabled also when IPv6 is compiled as a module, from Joe. 7) Fix a generic-XDP bug to handle the case when the Ethernet header was mangled and thus update skb's protocol and data, from Jesper. 8) Add a missing BTF header length check between header copies from user space, from Wenwen. 9) Minor fixups in libbpf to use __u32 instead u32 types and include proper perf_event.h uapi header instead of perf internal one, from Yonghong. 10) Allow to pass user-defined flags through EXTRA_CFLAGS and EXTRA_LDFLAGS to bpftool's build, from Jiri. 11) BPF kselftest tweaks to add LWTUNNEL to config fragment and to install with_addr.sh script from flow dissector selftest, from Anders. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
e85679511e
10
MAINTAINERS
10
MAINTAINERS
@ -8193,6 +8193,16 @@ S: Maintained
|
||||
F: net/l3mdev
|
||||
F: include/net/l3mdev.h
|
||||
|
||||
L7 BPF FRAMEWORK
|
||||
M: John Fastabend <john.fastabend@gmail.com>
|
||||
M: Daniel Borkmann <daniel@iogearbox.net>
|
||||
L: netdev@vger.kernel.org
|
||||
S: Maintained
|
||||
F: include/linux/skmsg.h
|
||||
F: net/core/skmsg.c
|
||||
F: net/core/sock_map.c
|
||||
F: net/ipv4/tcp_bpf.c
|
||||
|
||||
LANTIQ / INTEL Ethernet drivers
|
||||
M: Hauke Mehrtens <hauke@hauke-m.de>
|
||||
L: netdev@vger.kernel.org
|
||||
|
@ -737,33 +737,18 @@ static inline void bpf_map_offload_map_free(struct bpf_map *map)
|
||||
}
|
||||
#endif /* CONFIG_NET && CONFIG_BPF_SYSCALL */
|
||||
|
||||
#if defined(CONFIG_STREAM_PARSER) && defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_INET)
|
||||
struct sock *__sock_map_lookup_elem(struct bpf_map *map, u32 key);
|
||||
struct sock *__sock_hash_lookup_elem(struct bpf_map *map, void *key);
|
||||
int sock_map_prog(struct bpf_map *map, struct bpf_prog *prog, u32 type);
|
||||
int sockmap_get_from_fd(const union bpf_attr *attr, int type,
|
||||
struct bpf_prog *prog);
|
||||
#if defined(CONFIG_BPF_STREAM_PARSER)
|
||||
int sock_map_prog_update(struct bpf_map *map, struct bpf_prog *prog, u32 which);
|
||||
int sock_map_get_from_fd(const union bpf_attr *attr, struct bpf_prog *prog);
|
||||
#else
|
||||
static inline struct sock *__sock_map_lookup_elem(struct bpf_map *map, u32 key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline struct sock *__sock_hash_lookup_elem(struct bpf_map *map,
|
||||
void *key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int sock_map_prog(struct bpf_map *map,
|
||||
struct bpf_prog *prog,
|
||||
u32 type)
|
||||
static inline int sock_map_prog_update(struct bpf_map *map,
|
||||
struct bpf_prog *prog, u32 which)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int sockmap_get_from_fd(const union bpf_attr *attr, int type,
|
||||
struct bpf_prog *prog)
|
||||
static inline int sock_map_get_from_fd(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -839,6 +824,10 @@ extern const struct bpf_func_proto bpf_get_stack_proto;
|
||||
extern const struct bpf_func_proto bpf_sock_map_update_proto;
|
||||
extern const struct bpf_func_proto bpf_sock_hash_update_proto;
|
||||
extern const struct bpf_func_proto bpf_get_current_cgroup_id_proto;
|
||||
extern const struct bpf_func_proto bpf_msg_redirect_hash_proto;
|
||||
extern const struct bpf_func_proto bpf_msg_redirect_map_proto;
|
||||
extern const struct bpf_func_proto bpf_sk_redirect_hash_proto;
|
||||
extern const struct bpf_func_proto bpf_sk_redirect_map_proto;
|
||||
|
||||
extern const struct bpf_func_proto bpf_get_local_storage_proto;
|
||||
|
||||
|
@ -57,7 +57,7 @@ BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY_OF_MAPS, array_of_maps_map_ops)
|
||||
BPF_MAP_TYPE(BPF_MAP_TYPE_HASH_OF_MAPS, htab_of_maps_map_ops)
|
||||
#ifdef CONFIG_NET
|
||||
BPF_MAP_TYPE(BPF_MAP_TYPE_DEVMAP, dev_map_ops)
|
||||
#if defined(CONFIG_STREAM_PARSER) && defined(CONFIG_INET)
|
||||
#if defined(CONFIG_BPF_STREAM_PARSER)
|
||||
BPF_MAP_TYPE(BPF_MAP_TYPE_SOCKMAP, sock_map_ops)
|
||||
BPF_MAP_TYPE(BPF_MAP_TYPE_SOCKHASH, sock_hash_ops)
|
||||
#endif
|
||||
|
@ -520,24 +520,6 @@ struct bpf_skb_data_end {
|
||||
void *data_end;
|
||||
};
|
||||
|
||||
struct sk_msg_buff {
|
||||
void *data;
|
||||
void *data_end;
|
||||
__u32 apply_bytes;
|
||||
__u32 cork_bytes;
|
||||
int sg_copybreak;
|
||||
int sg_start;
|
||||
int sg_curr;
|
||||
int sg_end;
|
||||
struct scatterlist sg_data[MAX_SKB_FRAGS];
|
||||
bool sg_copy[MAX_SKB_FRAGS];
|
||||
__u32 flags;
|
||||
struct sock *sk_redir;
|
||||
struct sock *sk;
|
||||
struct sk_buff *skb;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct bpf_redirect_info {
|
||||
u32 ifindex;
|
||||
u32 flags;
|
||||
@ -833,9 +815,6 @@ void xdp_do_flush_map(void);
|
||||
|
||||
void bpf_warn_invalid_xdp_action(u32 act);
|
||||
|
||||
struct sock *do_sk_redirect_map(struct sk_buff *skb);
|
||||
struct sock *do_msg_redirect_map(struct sk_msg_buff *md);
|
||||
|
||||
#ifdef CONFIG_INET
|
||||
struct sock *bpf_run_sk_reuseport(struct sock_reuseport *reuse, struct sock *sk,
|
||||
struct bpf_prog *prog, struct sk_buff *skb,
|
||||
|
410
include/linux/skmsg.h
Normal file
410
include/linux/skmsg.h
Normal file
@ -0,0 +1,410 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (c) 2017 - 2018 Covalent IO, Inc. http://covalent.io */
|
||||
|
||||
#ifndef _LINUX_SKMSG_H
|
||||
#define _LINUX_SKMSG_H
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/skbuff.h>
|
||||
|
||||
#include <net/sock.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/strparser.h>
|
||||
|
||||
#define MAX_MSG_FRAGS MAX_SKB_FRAGS
|
||||
|
||||
enum __sk_action {
|
||||
__SK_DROP = 0,
|
||||
__SK_PASS,
|
||||
__SK_REDIRECT,
|
||||
__SK_NONE,
|
||||
};
|
||||
|
||||
struct sk_msg_sg {
|
||||
u32 start;
|
||||
u32 curr;
|
||||
u32 end;
|
||||
u32 size;
|
||||
u32 copybreak;
|
||||
bool copy[MAX_MSG_FRAGS];
|
||||
/* The extra element is used for chaining the front and sections when
|
||||
* the list becomes partitioned (e.g. end < start). The crypto APIs
|
||||
* require the chaining.
|
||||
*/
|
||||
struct scatterlist data[MAX_MSG_FRAGS + 1];
|
||||
};
|
||||
|
||||
struct sk_msg {
|
||||
struct sk_msg_sg sg;
|
||||
void *data;
|
||||
void *data_end;
|
||||
u32 apply_bytes;
|
||||
u32 cork_bytes;
|
||||
u32 flags;
|
||||
struct sk_buff *skb;
|
||||
struct sock *sk_redir;
|
||||
struct sock *sk;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct sk_psock_progs {
|
||||
struct bpf_prog *msg_parser;
|
||||
struct bpf_prog *skb_parser;
|
||||
struct bpf_prog *skb_verdict;
|
||||
};
|
||||
|
||||
enum sk_psock_state_bits {
|
||||
SK_PSOCK_TX_ENABLED,
|
||||
};
|
||||
|
||||
struct sk_psock_link {
|
||||
struct list_head list;
|
||||
struct bpf_map *map;
|
||||
void *link_raw;
|
||||
};
|
||||
|
||||
struct sk_psock_parser {
|
||||
struct strparser strp;
|
||||
bool enabled;
|
||||
void (*saved_data_ready)(struct sock *sk);
|
||||
};
|
||||
|
||||
struct sk_psock_work_state {
|
||||
struct sk_buff *skb;
|
||||
u32 len;
|
||||
u32 off;
|
||||
};
|
||||
|
||||
struct sk_psock {
|
||||
struct sock *sk;
|
||||
struct sock *sk_redir;
|
||||
u32 apply_bytes;
|
||||
u32 cork_bytes;
|
||||
u32 eval;
|
||||
struct sk_msg *cork;
|
||||
struct sk_psock_progs progs;
|
||||
struct sk_psock_parser parser;
|
||||
struct sk_buff_head ingress_skb;
|
||||
struct list_head ingress_msg;
|
||||
unsigned long state;
|
||||
struct list_head link;
|
||||
spinlock_t link_lock;
|
||||
refcount_t refcnt;
|
||||
void (*saved_unhash)(struct sock *sk);
|
||||
void (*saved_close)(struct sock *sk, long timeout);
|
||||
void (*saved_write_space)(struct sock *sk);
|
||||
struct proto *sk_proto;
|
||||
struct sk_psock_work_state work_state;
|
||||
struct work_struct work;
|
||||
union {
|
||||
struct rcu_head rcu;
|
||||
struct work_struct gc;
|
||||
};
|
||||
};
|
||||
|
||||
int sk_msg_alloc(struct sock *sk, struct sk_msg *msg, int len,
|
||||
int elem_first_coalesce);
|
||||
int sk_msg_clone(struct sock *sk, struct sk_msg *dst, struct sk_msg *src,
|
||||
u32 off, u32 len);
|
||||
void sk_msg_trim(struct sock *sk, struct sk_msg *msg, int len);
|
||||
int sk_msg_free(struct sock *sk, struct sk_msg *msg);
|
||||
int sk_msg_free_nocharge(struct sock *sk, struct sk_msg *msg);
|
||||
void sk_msg_free_partial(struct sock *sk, struct sk_msg *msg, u32 bytes);
|
||||
void sk_msg_free_partial_nocharge(struct sock *sk, struct sk_msg *msg,
|
||||
u32 bytes);
|
||||
|
||||
void sk_msg_return(struct sock *sk, struct sk_msg *msg, int bytes);
|
||||
void sk_msg_return_zero(struct sock *sk, struct sk_msg *msg, int bytes);
|
||||
|
||||
int sk_msg_zerocopy_from_iter(struct sock *sk, struct iov_iter *from,
|
||||
struct sk_msg *msg, u32 bytes);
|
||||
int sk_msg_memcopy_from_iter(struct sock *sk, struct iov_iter *from,
|
||||
struct sk_msg *msg, u32 bytes);
|
||||
|
||||
static inline void sk_msg_check_to_free(struct sk_msg *msg, u32 i, u32 bytes)
|
||||
{
|
||||
WARN_ON(i == msg->sg.end && bytes);
|
||||
}
|
||||
|
||||
static inline void sk_msg_apply_bytes(struct sk_psock *psock, u32 bytes)
|
||||
{
|
||||
if (psock->apply_bytes) {
|
||||
if (psock->apply_bytes < bytes)
|
||||
psock->apply_bytes = 0;
|
||||
else
|
||||
psock->apply_bytes -= bytes;
|
||||
}
|
||||
}
|
||||
|
||||
#define sk_msg_iter_var_prev(var) \
|
||||
do { \
|
||||
if (var == 0) \
|
||||
var = MAX_MSG_FRAGS - 1; \
|
||||
else \
|
||||
var--; \
|
||||
} while (0)
|
||||
|
||||
#define sk_msg_iter_var_next(var) \
|
||||
do { \
|
||||
var++; \
|
||||
if (var == MAX_MSG_FRAGS) \
|
||||
var = 0; \
|
||||
} while (0)
|
||||
|
||||
#define sk_msg_iter_prev(msg, which) \
|
||||
sk_msg_iter_var_prev(msg->sg.which)
|
||||
|
||||
#define sk_msg_iter_next(msg, which) \
|
||||
sk_msg_iter_var_next(msg->sg.which)
|
||||
|
||||
static inline void sk_msg_clear_meta(struct sk_msg *msg)
|
||||
{
|
||||
memset(&msg->sg, 0, offsetofend(struct sk_msg_sg, copy));
|
||||
}
|
||||
|
||||
static inline void sk_msg_init(struct sk_msg *msg)
|
||||
{
|
||||
BUILD_BUG_ON(ARRAY_SIZE(msg->sg.data) - 1 != MAX_MSG_FRAGS);
|
||||
memset(msg, 0, sizeof(*msg));
|
||||
sg_init_marker(msg->sg.data, MAX_MSG_FRAGS);
|
||||
}
|
||||
|
||||
static inline void sk_msg_xfer(struct sk_msg *dst, struct sk_msg *src,
|
||||
int which, u32 size)
|
||||
{
|
||||
dst->sg.data[which] = src->sg.data[which];
|
||||
dst->sg.data[which].length = size;
|
||||
src->sg.data[which].length -= size;
|
||||
src->sg.data[which].offset += size;
|
||||
}
|
||||
|
||||
static inline void sk_msg_xfer_full(struct sk_msg *dst, struct sk_msg *src)
|
||||
{
|
||||
memcpy(dst, src, sizeof(*src));
|
||||
sk_msg_init(src);
|
||||
}
|
||||
|
||||
static inline u32 sk_msg_elem_used(const struct sk_msg *msg)
|
||||
{
|
||||
return msg->sg.end >= msg->sg.start ?
|
||||
msg->sg.end - msg->sg.start :
|
||||
msg->sg.end + (MAX_MSG_FRAGS - msg->sg.start);
|
||||
}
|
||||
|
||||
static inline bool sk_msg_full(const struct sk_msg *msg)
|
||||
{
|
||||
return (msg->sg.end == msg->sg.start) && msg->sg.size;
|
||||
}
|
||||
|
||||
static inline struct scatterlist *sk_msg_elem(struct sk_msg *msg, int which)
|
||||
{
|
||||
return &msg->sg.data[which];
|
||||
}
|
||||
|
||||
static inline struct page *sk_msg_page(struct sk_msg *msg, int which)
|
||||
{
|
||||
return sg_page(sk_msg_elem(msg, which));
|
||||
}
|
||||
|
||||
static inline bool sk_msg_to_ingress(const struct sk_msg *msg)
|
||||
{
|
||||
return msg->flags & BPF_F_INGRESS;
|
||||
}
|
||||
|
||||
static inline void sk_msg_compute_data_pointers(struct sk_msg *msg)
|
||||
{
|
||||
struct scatterlist *sge = sk_msg_elem(msg, msg->sg.start);
|
||||
|
||||
if (msg->sg.copy[msg->sg.start]) {
|
||||
msg->data = NULL;
|
||||
msg->data_end = NULL;
|
||||
} else {
|
||||
msg->data = sg_virt(sge);
|
||||
msg->data_end = msg->data + sge->length;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sk_msg_page_add(struct sk_msg *msg, struct page *page,
|
||||
u32 len, u32 offset)
|
||||
{
|
||||
struct scatterlist *sge;
|
||||
|
||||
get_page(page);
|
||||
sge = sk_msg_elem(msg, msg->sg.end);
|
||||
sg_set_page(sge, page, len, offset);
|
||||
sg_unmark_end(sge);
|
||||
|
||||
msg->sg.copy[msg->sg.end] = true;
|
||||
msg->sg.size += len;
|
||||
sk_msg_iter_next(msg, end);
|
||||
}
|
||||
|
||||
static inline void sk_msg_sg_copy(struct sk_msg *msg, u32 i, bool copy_state)
|
||||
{
|
||||
do {
|
||||
msg->sg.copy[i] = copy_state;
|
||||
sk_msg_iter_var_next(i);
|
||||
if (i == msg->sg.end)
|
||||
break;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static inline void sk_msg_sg_copy_set(struct sk_msg *msg, u32 start)
|
||||
{
|
||||
sk_msg_sg_copy(msg, start, true);
|
||||
}
|
||||
|
||||
static inline void sk_msg_sg_copy_clear(struct sk_msg *msg, u32 start)
|
||||
{
|
||||
sk_msg_sg_copy(msg, start, false);
|
||||
}
|
||||
|
||||
static inline struct sk_psock *sk_psock(const struct sock *sk)
|
||||
{
|
||||
return rcu_dereference_sk_user_data(sk);
|
||||
}
|
||||
|
||||
static inline bool sk_has_psock(struct sock *sk)
|
||||
{
|
||||
return sk_psock(sk) != NULL && sk->sk_prot->recvmsg == tcp_bpf_recvmsg;
|
||||
}
|
||||
|
||||
static inline void sk_psock_queue_msg(struct sk_psock *psock,
|
||||
struct sk_msg *msg)
|
||||
{
|
||||
list_add_tail(&msg->list, &psock->ingress_msg);
|
||||
}
|
||||
|
||||
static inline bool sk_psock_queue_empty(const struct sk_psock *psock)
|
||||
{
|
||||
return psock ? list_empty(&psock->ingress_msg) : true;
|
||||
}
|
||||
|
||||
static inline void sk_psock_report_error(struct sk_psock *psock, int err)
|
||||
{
|
||||
struct sock *sk = psock->sk;
|
||||
|
||||
sk->sk_err = err;
|
||||
sk->sk_error_report(sk);
|
||||
}
|
||||
|
||||
struct sk_psock *sk_psock_init(struct sock *sk, int node);
|
||||
|
||||
int sk_psock_init_strp(struct sock *sk, struct sk_psock *psock);
|
||||
void sk_psock_start_strp(struct sock *sk, struct sk_psock *psock);
|
||||
void sk_psock_stop_strp(struct sock *sk, struct sk_psock *psock);
|
||||
|
||||
int sk_psock_msg_verdict(struct sock *sk, struct sk_psock *psock,
|
||||
struct sk_msg *msg);
|
||||
|
||||
static inline struct sk_psock_link *sk_psock_init_link(void)
|
||||
{
|
||||
return kzalloc(sizeof(struct sk_psock_link),
|
||||
GFP_ATOMIC | __GFP_NOWARN);
|
||||
}
|
||||
|
||||
static inline void sk_psock_free_link(struct sk_psock_link *link)
|
||||
{
|
||||
kfree(link);
|
||||
}
|
||||
|
||||
struct sk_psock_link *sk_psock_link_pop(struct sk_psock *psock);
|
||||
#if defined(CONFIG_BPF_STREAM_PARSER)
|
||||
void sk_psock_unlink(struct sock *sk, struct sk_psock_link *link);
|
||||
#else
|
||||
static inline void sk_psock_unlink(struct sock *sk,
|
||||
struct sk_psock_link *link)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void __sk_psock_purge_ingress_msg(struct sk_psock *psock);
|
||||
|
||||
static inline void sk_psock_cork_free(struct sk_psock *psock)
|
||||
{
|
||||
if (psock->cork) {
|
||||
sk_msg_free(psock->sk, psock->cork);
|
||||
kfree(psock->cork);
|
||||
psock->cork = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sk_psock_update_proto(struct sock *sk,
|
||||
struct sk_psock *psock,
|
||||
struct proto *ops)
|
||||
{
|
||||
psock->saved_unhash = sk->sk_prot->unhash;
|
||||
psock->saved_close = sk->sk_prot->close;
|
||||
psock->saved_write_space = sk->sk_write_space;
|
||||
|
||||
psock->sk_proto = sk->sk_prot;
|
||||
sk->sk_prot = ops;
|
||||
}
|
||||
|
||||
static inline void sk_psock_restore_proto(struct sock *sk,
|
||||
struct sk_psock *psock)
|
||||
{
|
||||
if (psock->sk_proto) {
|
||||
sk->sk_prot = psock->sk_proto;
|
||||
psock->sk_proto = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sk_psock_set_state(struct sk_psock *psock,
|
||||
enum sk_psock_state_bits bit)
|
||||
{
|
||||
set_bit(bit, &psock->state);
|
||||
}
|
||||
|
||||
static inline void sk_psock_clear_state(struct sk_psock *psock,
|
||||
enum sk_psock_state_bits bit)
|
||||
{
|
||||
clear_bit(bit, &psock->state);
|
||||
}
|
||||
|
||||
static inline bool sk_psock_test_state(const struct sk_psock *psock,
|
||||
enum sk_psock_state_bits bit)
|
||||
{
|
||||
return test_bit(bit, &psock->state);
|
||||
}
|
||||
|
||||
static inline struct sk_psock *sk_psock_get(struct sock *sk)
|
||||
{
|
||||
struct sk_psock *psock;
|
||||
|
||||
rcu_read_lock();
|
||||
psock = sk_psock(sk);
|
||||
if (psock && !refcount_inc_not_zero(&psock->refcnt))
|
||||
psock = NULL;
|
||||
rcu_read_unlock();
|
||||
return psock;
|
||||
}
|
||||
|
||||
void sk_psock_stop(struct sock *sk, struct sk_psock *psock);
|
||||
void sk_psock_destroy(struct rcu_head *rcu);
|
||||
void sk_psock_drop(struct sock *sk, struct sk_psock *psock);
|
||||
|
||||
static inline void sk_psock_put(struct sock *sk, struct sk_psock *psock)
|
||||
{
|
||||
if (refcount_dec_and_test(&psock->refcnt))
|
||||
sk_psock_drop(sk, psock);
|
||||
}
|
||||
|
||||
static inline void psock_set_prog(struct bpf_prog **pprog,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
prog = xchg(pprog, prog);
|
||||
if (prog)
|
||||
bpf_prog_put(prog);
|
||||
}
|
||||
|
||||
static inline void psock_progs_drop(struct sk_psock_progs *progs)
|
||||
{
|
||||
psock_set_prog(&progs->msg_parser, NULL);
|
||||
psock_set_prog(&progs->skb_parser, NULL);
|
||||
psock_set_prog(&progs->skb_verdict, NULL);
|
||||
}
|
||||
|
||||
#endif /* _LINUX_SKMSG_H */
|
@ -265,6 +265,11 @@ extern const struct ipv6_stub *ipv6_stub __read_mostly;
|
||||
struct ipv6_bpf_stub {
|
||||
int (*inet6_bind)(struct sock *sk, struct sockaddr *uaddr, int addr_len,
|
||||
bool force_bind_address_no_port, bool with_lock);
|
||||
struct sock *(*udp6_lib_lookup)(struct net *net,
|
||||
const struct in6_addr *saddr, __be16 sport,
|
||||
const struct in6_addr *daddr, __be16 dport,
|
||||
int dif, int sdif, struct udp_table *tbl,
|
||||
struct sk_buff *skb);
|
||||
};
|
||||
extern const struct ipv6_bpf_stub *ipv6_bpf_stub __read_mostly;
|
||||
|
||||
|
@ -2214,10 +2214,6 @@ static inline struct page_frag *sk_page_frag(struct sock *sk)
|
||||
|
||||
bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag);
|
||||
|
||||
int sk_alloc_sg(struct sock *sk, int len, struct scatterlist *sg,
|
||||
int sg_start, int *sg_curr, unsigned int *sg_size,
|
||||
int first_coalesce);
|
||||
|
||||
/*
|
||||
* Default write policy as shown to user space via poll/select/SIGIO
|
||||
*/
|
||||
|
@ -858,6 +858,21 @@ static inline void bpf_compute_data_end_sk_skb(struct sk_buff *skb)
|
||||
TCP_SKB_CB(skb)->bpf.data_end = skb->data + skb_headlen(skb);
|
||||
}
|
||||
|
||||
static inline bool tcp_skb_bpf_ingress(const struct sk_buff *skb)
|
||||
{
|
||||
return TCP_SKB_CB(skb)->bpf.flags & BPF_F_INGRESS;
|
||||
}
|
||||
|
||||
static inline struct sock *tcp_skb_bpf_redirect_fetch(struct sk_buff *skb)
|
||||
{
|
||||
return TCP_SKB_CB(skb)->bpf.sk_redir;
|
||||
}
|
||||
|
||||
static inline void tcp_skb_bpf_redirect_clear(struct sk_buff *skb)
|
||||
{
|
||||
TCP_SKB_CB(skb)->bpf.sk_redir = NULL;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
/* This is the variant of inet6_iif() that must be used by TCP,
|
||||
* as TCP moves IP6CB into a different location in skb->cb[]
|
||||
@ -2057,7 +2072,6 @@ struct tcp_ulp_ops {
|
||||
int tcp_register_ulp(struct tcp_ulp_ops *type);
|
||||
void tcp_unregister_ulp(struct tcp_ulp_ops *type);
|
||||
int tcp_set_ulp(struct sock *sk, const char *name);
|
||||
int tcp_set_ulp_id(struct sock *sk, const int ulp);
|
||||
void tcp_get_available_ulp(char *buf, size_t len);
|
||||
void tcp_cleanup_ulp(struct sock *sk);
|
||||
|
||||
@ -2065,6 +2079,18 @@ void tcp_cleanup_ulp(struct sock *sk);
|
||||
__MODULE_INFO(alias, alias_userspace, name); \
|
||||
__MODULE_INFO(alias, alias_tcp_ulp, "tcp-ulp-" name)
|
||||
|
||||
struct sk_msg;
|
||||
struct sk_psock;
|
||||
|
||||
int tcp_bpf_init(struct sock *sk);
|
||||
void tcp_bpf_reinit(struct sock *sk);
|
||||
int tcp_bpf_sendmsg_redir(struct sock *sk, struct sk_msg *msg, u32 bytes,
|
||||
int flags);
|
||||
int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
|
||||
int nonblock, int flags, int *addr_len);
|
||||
int __tcp_bpf_recvmsg(struct sock *sk, struct sk_psock *psock,
|
||||
struct msghdr *msg, int len);
|
||||
|
||||
/* Call BPF_SOCK_OPS program that returns an int. If the return value
|
||||
* is < 0, then the BPF op failed (for example if the loaded BPF
|
||||
* program does not support the chosen operation or there is no BPF
|
||||
|
@ -39,6 +39,8 @@
|
||||
#include <linux/crypto.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/tcp.h>
|
||||
#include <linux/skmsg.h>
|
||||
|
||||
#include <net/tcp.h>
|
||||
#include <net/strparser.h>
|
||||
#include <crypto/aead.h>
|
||||
@ -103,15 +105,13 @@ struct tls_rec {
|
||||
int tx_flags;
|
||||
int inplace_crypto;
|
||||
|
||||
/* AAD | sg_plaintext_data | sg_tag */
|
||||
struct scatterlist sg_plaintext_data[MAX_SKB_FRAGS + 1];
|
||||
/* AAD | sg_encrypted_data (data contain overhead for hdr&iv&tag) */
|
||||
struct scatterlist sg_encrypted_data[MAX_SKB_FRAGS + 1];
|
||||
struct sk_msg msg_plaintext;
|
||||
struct sk_msg msg_encrypted;
|
||||
|
||||
unsigned int sg_plaintext_size;
|
||||
unsigned int sg_encrypted_size;
|
||||
int sg_plaintext_num_elem;
|
||||
int sg_encrypted_num_elem;
|
||||
/* AAD | msg_plaintext.sg.data | sg_tag */
|
||||
struct scatterlist sg_aead_in[2];
|
||||
/* AAD | msg_encrypted.sg.data (data contains overhead for hdr & iv & tag) */
|
||||
struct scatterlist sg_aead_out[2];
|
||||
|
||||
char aad_space[TLS_AAD_SPACE_SIZE];
|
||||
struct aead_request aead_req;
|
||||
@ -142,8 +142,7 @@ struct tls_sw_context_rx {
|
||||
|
||||
struct strparser strp;
|
||||
void (*saved_data_ready)(struct sock *sk);
|
||||
unsigned int (*sk_poll)(struct file *file, struct socket *sock,
|
||||
struct poll_table_struct *wait);
|
||||
|
||||
struct sk_buff *recv_pkt;
|
||||
u8 control;
|
||||
bool decrypted;
|
||||
@ -223,8 +222,8 @@ struct tls_context {
|
||||
|
||||
unsigned long flags;
|
||||
bool in_tcp_sendpages;
|
||||
bool pending_open_record_frags;
|
||||
|
||||
u16 pending_open_record_frags;
|
||||
int (*push_pending_record)(struct sock *sk, int flags);
|
||||
|
||||
void (*sk_write_space)(struct sock *sk);
|
||||
@ -272,8 +271,7 @@ void tls_sw_free_resources_rx(struct sock *sk);
|
||||
void tls_sw_release_resources_rx(struct sock *sk);
|
||||
int tls_sw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
|
||||
int nonblock, int flags, int *addr_len);
|
||||
unsigned int tls_sw_poll(struct file *file, struct socket *sock,
|
||||
struct poll_table_struct *wait);
|
||||
bool tls_sw_stream_read(const struct sock *sk);
|
||||
ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,
|
||||
struct pipe_inode_info *pipe,
|
||||
size_t len, unsigned int flags);
|
||||
|
@ -13,11 +13,6 @@ ifeq ($(CONFIG_XDP_SOCKETS),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += xskmap.o
|
||||
endif
|
||||
obj-$(CONFIG_BPF_SYSCALL) += offload.o
|
||||
ifeq ($(CONFIG_STREAM_PARSER),y)
|
||||
ifeq ($(CONFIG_INET),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += sockmap.o
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
ifeq ($(CONFIG_PERF_EVENTS),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += stackmap.o
|
||||
|
@ -449,7 +449,7 @@ static void fd_array_map_free(struct bpf_map *map)
|
||||
|
||||
static void *fd_array_map_lookup_elem(struct bpf_map *map, void *key)
|
||||
{
|
||||
return NULL;
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
|
||||
/* only called from syscall */
|
||||
|
@ -2114,6 +2114,9 @@ static int btf_parse_hdr(struct btf_verifier_env *env, void __user *btf_data,
|
||||
|
||||
hdr = &btf->hdr;
|
||||
|
||||
if (hdr->hdr_len != hdr_len)
|
||||
return -EINVAL;
|
||||
|
||||
btf_verifier_log_hdr(env, btf_data_size);
|
||||
|
||||
if (hdr->magic != BTF_MAGIC) {
|
||||
|
@ -1792,8 +1792,6 @@ const struct bpf_func_proto bpf_ktime_get_ns_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_current_pid_tgid_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_current_uid_gid_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_current_comm_proto __weak;
|
||||
const struct bpf_func_proto bpf_sock_map_update_proto __weak;
|
||||
const struct bpf_func_proto bpf_sock_hash_update_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_current_cgroup_id_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_local_storage_proto __weak;
|
||||
|
||||
|
2629
kernel/bpf/sockmap.c
2629
kernel/bpf/sockmap.c
File diff suppressed because it is too large
Load Diff
@ -505,7 +505,7 @@ const struct bpf_func_proto bpf_get_stack_proto = {
|
||||
/* Called from eBPF program */
|
||||
static void *stack_map_lookup_elem(struct bpf_map *map, void *key)
|
||||
{
|
||||
return NULL;
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
|
||||
/* Called from syscall */
|
||||
|
@ -719,10 +719,15 @@ static int map_lookup_elem(union bpf_attr *attr)
|
||||
} else {
|
||||
rcu_read_lock();
|
||||
ptr = map->ops->map_lookup_elem(map, key);
|
||||
if (ptr)
|
||||
if (IS_ERR(ptr)) {
|
||||
err = PTR_ERR(ptr);
|
||||
} else if (!ptr) {
|
||||
err = -ENOENT;
|
||||
} else {
|
||||
err = 0;
|
||||
memcpy(value, ptr, value_size);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
err = ptr ? 0 : -ENOENT;
|
||||
}
|
||||
|
||||
if (err)
|
||||
@ -743,6 +748,17 @@ err_put:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void maybe_wait_bpf_programs(struct bpf_map *map)
|
||||
{
|
||||
/* Wait for any running BPF programs to complete so that
|
||||
* userspace, when we return to it, knows that all programs
|
||||
* that could be running use the new map value.
|
||||
*/
|
||||
if (map->map_type == BPF_MAP_TYPE_HASH_OF_MAPS ||
|
||||
map->map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS)
|
||||
synchronize_rcu();
|
||||
}
|
||||
|
||||
#define BPF_MAP_UPDATE_ELEM_LAST_FIELD flags
|
||||
|
||||
static int map_update_elem(union bpf_attr *attr)
|
||||
@ -837,6 +853,7 @@ static int map_update_elem(union bpf_attr *attr)
|
||||
}
|
||||
__this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
maybe_wait_bpf_programs(map);
|
||||
out:
|
||||
free_value:
|
||||
kfree(value);
|
||||
@ -889,6 +906,7 @@ static int map_delete_elem(union bpf_attr *attr)
|
||||
rcu_read_unlock();
|
||||
__this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
maybe_wait_bpf_programs(map);
|
||||
out:
|
||||
kfree(key);
|
||||
err_put:
|
||||
@ -1646,7 +1664,7 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
||||
switch (ptype) {
|
||||
case BPF_PROG_TYPE_SK_SKB:
|
||||
case BPF_PROG_TYPE_SK_MSG:
|
||||
ret = sockmap_get_from_fd(attr, ptype, prog);
|
||||
ret = sock_map_get_from_fd(attr, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_LIRC_MODE2:
|
||||
ret = lirc_prog_attach(attr, prog);
|
||||
@ -1700,10 +1718,10 @@ static int bpf_prog_detach(const union bpf_attr *attr)
|
||||
ptype = BPF_PROG_TYPE_CGROUP_DEVICE;
|
||||
break;
|
||||
case BPF_SK_MSG_VERDICT:
|
||||
return sockmap_get_from_fd(attr, BPF_PROG_TYPE_SK_MSG, NULL);
|
||||
return sock_map_get_from_fd(attr, NULL);
|
||||
case BPF_SK_SKB_STREAM_PARSER:
|
||||
case BPF_SK_SKB_STREAM_VERDICT:
|
||||
return sockmap_get_from_fd(attr, BPF_PROG_TYPE_SK_SKB, NULL);
|
||||
return sock_map_get_from_fd(attr, NULL);
|
||||
case BPF_LIRC_MODE2:
|
||||
return lirc_prog_detach(attr);
|
||||
case BPF_FLOW_DISSECTOR:
|
||||
|
@ -154,7 +154,7 @@ void __xsk_map_flush(struct bpf_map *map)
|
||||
|
||||
static void *xsk_map_lookup_elem(struct bpf_map *map, void *key)
|
||||
{
|
||||
return NULL;
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
|
||||
static int xsk_map_update_elem(struct bpf_map *map, void *key, void *value,
|
||||
|
11
net/Kconfig
11
net/Kconfig
@ -300,8 +300,11 @@ config BPF_JIT
|
||||
|
||||
config BPF_STREAM_PARSER
|
||||
bool "enable BPF STREAM_PARSER"
|
||||
depends on INET
|
||||
depends on BPF_SYSCALL
|
||||
depends on CGROUP_BPF
|
||||
select STREAM_PARSER
|
||||
select NET_SOCK_MSG
|
||||
---help---
|
||||
Enabling this allows a stream parser to be used with
|
||||
BPF_MAP_TYPE_SOCKMAP.
|
||||
@ -413,6 +416,14 @@ config GRO_CELLS
|
||||
config SOCK_VALIDATE_XMIT
|
||||
bool
|
||||
|
||||
config NET_SOCK_MSG
|
||||
bool
|
||||
default n
|
||||
help
|
||||
The NET_SOCK_MSG provides a framework for plain sockets (e.g. TCP) or
|
||||
ULPs (upper layer modules, e.g. TLS) to process L7 application data
|
||||
with the help of BPF programs.
|
||||
|
||||
config NET_DEVLINK
|
||||
tristate "Network physical/parent device Netlink interface"
|
||||
help
|
||||
|
@ -16,6 +16,7 @@ obj-y += dev.o ethtool.o dev_addr_lists.o dst.o netevent.o \
|
||||
obj-y += net-sysfs.o
|
||||
obj-$(CONFIG_PAGE_POOL) += page_pool.o
|
||||
obj-$(CONFIG_PROC_FS) += net-procfs.o
|
||||
obj-$(CONFIG_NET_SOCK_MSG) += skmsg.o
|
||||
obj-$(CONFIG_NET_PKTGEN) += pktgen.o
|
||||
obj-$(CONFIG_NETPOLL) += netpoll.o
|
||||
obj-$(CONFIG_FIB_RULES) += fib_rules.o
|
||||
@ -27,6 +28,7 @@ obj-$(CONFIG_CGROUP_NET_PRIO) += netprio_cgroup.o
|
||||
obj-$(CONFIG_CGROUP_NET_CLASSID) += netclassid_cgroup.o
|
||||
obj-$(CONFIG_LWTUNNEL) += lwtunnel.o
|
||||
obj-$(CONFIG_LWTUNNEL_BPF) += lwt_bpf.o
|
||||
obj-$(CONFIG_BPF_STREAM_PARSER) += sock_map.o
|
||||
obj-$(CONFIG_DST_CACHE) += dst_cache.o
|
||||
obj-$(CONFIG_HWBM) += hwbm.o
|
||||
obj-$(CONFIG_NET_DEVLINK) += devlink.o
|
||||
|
@ -4291,6 +4291,9 @@ static u32 netif_receive_generic_xdp(struct sk_buff *skb,
|
||||
struct netdev_rx_queue *rxqueue;
|
||||
void *orig_data, *orig_data_end;
|
||||
u32 metalen, act = XDP_DROP;
|
||||
__be16 orig_eth_type;
|
||||
struct ethhdr *eth;
|
||||
bool orig_bcast;
|
||||
int hlen, off;
|
||||
u32 mac_len;
|
||||
|
||||
@ -4331,6 +4334,9 @@ static u32 netif_receive_generic_xdp(struct sk_buff *skb,
|
||||
xdp->data_hard_start = skb->data - skb_headroom(skb);
|
||||
orig_data_end = xdp->data_end;
|
||||
orig_data = xdp->data;
|
||||
eth = (struct ethhdr *)xdp->data;
|
||||
orig_bcast = is_multicast_ether_addr_64bits(eth->h_dest);
|
||||
orig_eth_type = eth->h_proto;
|
||||
|
||||
rxqueue = netif_get_rxqueue(skb);
|
||||
xdp->rxq = &rxqueue->xdp_rxq;
|
||||
@ -4354,6 +4360,14 @@ static u32 netif_receive_generic_xdp(struct sk_buff *skb,
|
||||
|
||||
}
|
||||
|
||||
/* check if XDP changed eth hdr such SKB needs update */
|
||||
eth = (struct ethhdr *)xdp->data;
|
||||
if ((orig_eth_type != eth->h_proto) ||
|
||||
(orig_bcast != is_multicast_ether_addr_64bits(eth->h_dest))) {
|
||||
__skb_push(skb, ETH_HLEN);
|
||||
skb->protocol = eth_type_trans(skb, skb->dev);
|
||||
}
|
||||
|
||||
switch (act) {
|
||||
case XDP_REDIRECT:
|
||||
case XDP_TX:
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <net/protocol.h>
|
||||
#include <net/netlink.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/skmsg.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/flow_dissector.h>
|
||||
#include <linux/errno.h>
|
||||
@ -2142,123 +2143,7 @@ static const struct bpf_func_proto bpf_redirect_proto = {
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_4(bpf_sk_redirect_hash, struct sk_buff *, skb,
|
||||
struct bpf_map *, map, void *, key, u64, flags)
|
||||
{
|
||||
struct tcp_skb_cb *tcb = TCP_SKB_CB(skb);
|
||||
|
||||
/* If user passes invalid input drop the packet. */
|
||||
if (unlikely(flags & ~(BPF_F_INGRESS)))
|
||||
return SK_DROP;
|
||||
|
||||
tcb->bpf.flags = flags;
|
||||
tcb->bpf.sk_redir = __sock_hash_lookup_elem(map, key);
|
||||
if (!tcb->bpf.sk_redir)
|
||||
return SK_DROP;
|
||||
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_sk_redirect_hash_proto = {
|
||||
.func = bpf_sk_redirect_hash,
|
||||
.gpl_only = false,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_CTX,
|
||||
.arg2_type = ARG_CONST_MAP_PTR,
|
||||
.arg3_type = ARG_PTR_TO_MAP_KEY,
|
||||
.arg4_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_4(bpf_sk_redirect_map, struct sk_buff *, skb,
|
||||
struct bpf_map *, map, u32, key, u64, flags)
|
||||
{
|
||||
struct tcp_skb_cb *tcb = TCP_SKB_CB(skb);
|
||||
|
||||
/* If user passes invalid input drop the packet. */
|
||||
if (unlikely(flags & ~(BPF_F_INGRESS)))
|
||||
return SK_DROP;
|
||||
|
||||
tcb->bpf.flags = flags;
|
||||
tcb->bpf.sk_redir = __sock_map_lookup_elem(map, key);
|
||||
if (!tcb->bpf.sk_redir)
|
||||
return SK_DROP;
|
||||
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
struct sock *do_sk_redirect_map(struct sk_buff *skb)
|
||||
{
|
||||
struct tcp_skb_cb *tcb = TCP_SKB_CB(skb);
|
||||
|
||||
return tcb->bpf.sk_redir;
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_sk_redirect_map_proto = {
|
||||
.func = bpf_sk_redirect_map,
|
||||
.gpl_only = false,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_CTX,
|
||||
.arg2_type = ARG_CONST_MAP_PTR,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
.arg4_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_4(bpf_msg_redirect_hash, struct sk_msg_buff *, msg,
|
||||
struct bpf_map *, map, void *, key, u64, flags)
|
||||
{
|
||||
/* If user passes invalid input drop the packet. */
|
||||
if (unlikely(flags & ~(BPF_F_INGRESS)))
|
||||
return SK_DROP;
|
||||
|
||||
msg->flags = flags;
|
||||
msg->sk_redir = __sock_hash_lookup_elem(map, key);
|
||||
if (!msg->sk_redir)
|
||||
return SK_DROP;
|
||||
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_msg_redirect_hash_proto = {
|
||||
.func = bpf_msg_redirect_hash,
|
||||
.gpl_only = false,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_CTX,
|
||||
.arg2_type = ARG_CONST_MAP_PTR,
|
||||
.arg3_type = ARG_PTR_TO_MAP_KEY,
|
||||
.arg4_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_4(bpf_msg_redirect_map, struct sk_msg_buff *, msg,
|
||||
struct bpf_map *, map, u32, key, u64, flags)
|
||||
{
|
||||
/* If user passes invalid input drop the packet. */
|
||||
if (unlikely(flags & ~(BPF_F_INGRESS)))
|
||||
return SK_DROP;
|
||||
|
||||
msg->flags = flags;
|
||||
msg->sk_redir = __sock_map_lookup_elem(map, key);
|
||||
if (!msg->sk_redir)
|
||||
return SK_DROP;
|
||||
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
struct sock *do_msg_redirect_map(struct sk_msg_buff *msg)
|
||||
{
|
||||
return msg->sk_redir;
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_msg_redirect_map_proto = {
|
||||
.func = bpf_msg_redirect_map,
|
||||
.gpl_only = false,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_CTX,
|
||||
.arg2_type = ARG_CONST_MAP_PTR,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
.arg4_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_2(bpf_msg_apply_bytes, struct sk_msg_buff *, msg, u32, bytes)
|
||||
BPF_CALL_2(bpf_msg_apply_bytes, struct sk_msg *, msg, u32, bytes)
|
||||
{
|
||||
msg->apply_bytes = bytes;
|
||||
return 0;
|
||||
@ -2272,7 +2157,7 @@ static const struct bpf_func_proto bpf_msg_apply_bytes_proto = {
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_2(bpf_msg_cork_bytes, struct sk_msg_buff *, msg, u32, bytes)
|
||||
BPF_CALL_2(bpf_msg_cork_bytes, struct sk_msg *, msg, u32, bytes)
|
||||
{
|
||||
msg->cork_bytes = bytes;
|
||||
return 0;
|
||||
@ -2286,45 +2171,37 @@ static const struct bpf_func_proto bpf_msg_cork_bytes_proto = {
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
#define sk_msg_iter_var(var) \
|
||||
do { \
|
||||
var++; \
|
||||
if (var == MAX_SKB_FRAGS) \
|
||||
var = 0; \
|
||||
} while (0)
|
||||
|
||||
BPF_CALL_4(bpf_msg_pull_data,
|
||||
struct sk_msg_buff *, msg, u32, start, u32, end, u64, flags)
|
||||
BPF_CALL_4(bpf_msg_pull_data, struct sk_msg *, msg, u32, start,
|
||||
u32, end, u64, flags)
|
||||
{
|
||||
unsigned int len = 0, offset = 0, copy = 0, poffset = 0;
|
||||
int bytes = end - start, bytes_sg_total;
|
||||
struct scatterlist *sg = msg->sg_data;
|
||||
int first_sg, last_sg, i, shift;
|
||||
unsigned char *p, *to, *from;
|
||||
u32 len = 0, offset = 0, copy = 0, poffset = 0, bytes = end - start;
|
||||
u32 first_sge, last_sge, i, shift, bytes_sg_total;
|
||||
struct scatterlist *sge;
|
||||
u8 *raw, *to, *from;
|
||||
struct page *page;
|
||||
|
||||
if (unlikely(flags || end <= start))
|
||||
return -EINVAL;
|
||||
|
||||
/* First find the starting scatterlist element */
|
||||
i = msg->sg_start;
|
||||
i = msg->sg.start;
|
||||
do {
|
||||
len = sg[i].length;
|
||||
len = sk_msg_elem(msg, i)->length;
|
||||
if (start < offset + len)
|
||||
break;
|
||||
offset += len;
|
||||
sk_msg_iter_var(i);
|
||||
} while (i != msg->sg_end);
|
||||
sk_msg_iter_var_next(i);
|
||||
} while (i != msg->sg.end);
|
||||
|
||||
if (unlikely(start >= offset + len))
|
||||
return -EINVAL;
|
||||
|
||||
first_sg = i;
|
||||
first_sge = i;
|
||||
/* The start may point into the sg element so we need to also
|
||||
* account for the headroom.
|
||||
*/
|
||||
bytes_sg_total = start - offset + bytes;
|
||||
if (!msg->sg_copy[i] && bytes_sg_total <= len)
|
||||
if (!msg->sg.copy[i] && bytes_sg_total <= len)
|
||||
goto out;
|
||||
|
||||
/* At this point we need to linearize multiple scatterlist
|
||||
@ -2338,12 +2215,12 @@ BPF_CALL_4(bpf_msg_pull_data,
|
||||
* will copy the entire sg entry.
|
||||
*/
|
||||
do {
|
||||
copy += sg[i].length;
|
||||
sk_msg_iter_var(i);
|
||||
copy += sk_msg_elem(msg, i)->length;
|
||||
sk_msg_iter_var_next(i);
|
||||
if (bytes_sg_total <= copy)
|
||||
break;
|
||||
} while (i != msg->sg_end);
|
||||
last_sg = i;
|
||||
} while (i != msg->sg.end);
|
||||
last_sge = i;
|
||||
|
||||
if (unlikely(bytes_sg_total > copy))
|
||||
return -EINVAL;
|
||||
@ -2352,63 +2229,61 @@ BPF_CALL_4(bpf_msg_pull_data,
|
||||
get_order(copy));
|
||||
if (unlikely(!page))
|
||||
return -ENOMEM;
|
||||
p = page_address(page);
|
||||
|
||||
i = first_sg;
|
||||
raw = page_address(page);
|
||||
i = first_sge;
|
||||
do {
|
||||
from = sg_virt(&sg[i]);
|
||||
len = sg[i].length;
|
||||
to = p + poffset;
|
||||
sge = sk_msg_elem(msg, i);
|
||||
from = sg_virt(sge);
|
||||
len = sge->length;
|
||||
to = raw + poffset;
|
||||
|
||||
memcpy(to, from, len);
|
||||
poffset += len;
|
||||
sg[i].length = 0;
|
||||
put_page(sg_page(&sg[i]));
|
||||
sge->length = 0;
|
||||
put_page(sg_page(sge));
|
||||
|
||||
sk_msg_iter_var(i);
|
||||
} while (i != last_sg);
|
||||
sk_msg_iter_var_next(i);
|
||||
} while (i != last_sge);
|
||||
|
||||
sg[first_sg].length = copy;
|
||||
sg_set_page(&sg[first_sg], page, copy, 0);
|
||||
sg_set_page(&msg->sg.data[first_sge], page, copy, 0);
|
||||
|
||||
/* To repair sg ring we need to shift entries. If we only
|
||||
* had a single entry though we can just replace it and
|
||||
* be done. Otherwise walk the ring and shift the entries.
|
||||
*/
|
||||
WARN_ON_ONCE(last_sg == first_sg);
|
||||
shift = last_sg > first_sg ?
|
||||
last_sg - first_sg - 1 :
|
||||
MAX_SKB_FRAGS - first_sg + last_sg - 1;
|
||||
WARN_ON_ONCE(last_sge == first_sge);
|
||||
shift = last_sge > first_sge ?
|
||||
last_sge - first_sge - 1 :
|
||||
MAX_SKB_FRAGS - first_sge + last_sge - 1;
|
||||
if (!shift)
|
||||
goto out;
|
||||
|
||||
i = first_sg;
|
||||
sk_msg_iter_var(i);
|
||||
i = first_sge;
|
||||
sk_msg_iter_var_next(i);
|
||||
do {
|
||||
int move_from;
|
||||
u32 move_from;
|
||||
|
||||
if (i + shift >= MAX_SKB_FRAGS)
|
||||
move_from = i + shift - MAX_SKB_FRAGS;
|
||||
if (i + shift >= MAX_MSG_FRAGS)
|
||||
move_from = i + shift - MAX_MSG_FRAGS;
|
||||
else
|
||||
move_from = i + shift;
|
||||
|
||||
if (move_from == msg->sg_end)
|
||||
if (move_from == msg->sg.end)
|
||||
break;
|
||||
|
||||
sg[i] = sg[move_from];
|
||||
sg[move_from].length = 0;
|
||||
sg[move_from].page_link = 0;
|
||||
sg[move_from].offset = 0;
|
||||
|
||||
sk_msg_iter_var(i);
|
||||
msg->sg.data[i] = msg->sg.data[move_from];
|
||||
msg->sg.data[move_from].length = 0;
|
||||
msg->sg.data[move_from].page_link = 0;
|
||||
msg->sg.data[move_from].offset = 0;
|
||||
sk_msg_iter_var_next(i);
|
||||
} while (1);
|
||||
msg->sg_end -= shift;
|
||||
if (msg->sg_end < 0)
|
||||
msg->sg_end += MAX_SKB_FRAGS;
|
||||
out:
|
||||
msg->data = sg_virt(&sg[first_sg]) + start - offset;
|
||||
msg->data_end = msg->data + bytes;
|
||||
|
||||
msg->sg.end = msg->sg.end - shift > msg->sg.end ?
|
||||
msg->sg.end - shift + MAX_MSG_FRAGS :
|
||||
msg->sg.end - shift;
|
||||
out:
|
||||
msg->data = sg_virt(&msg->sg.data[first_sge]) + start - offset;
|
||||
msg->data_end = msg->data + bytes;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -4821,9 +4696,12 @@ static const struct bpf_func_proto bpf_lwt_seg6_adjust_srh_proto = {
|
||||
static struct sock *sk_lookup(struct net *net, struct bpf_sock_tuple *tuple,
|
||||
struct sk_buff *skb, u8 family, u8 proto)
|
||||
{
|
||||
int dif = skb->dev->ifindex;
|
||||
bool refcounted = false;
|
||||
struct sock *sk = NULL;
|
||||
int dif = 0;
|
||||
|
||||
if (skb->dev)
|
||||
dif = skb->dev->ifindex;
|
||||
|
||||
if (family == AF_INET) {
|
||||
__be32 src4 = tuple->ipv4.saddr;
|
||||
@ -4839,21 +4717,24 @@ static struct sock *sk_lookup(struct net *net, struct bpf_sock_tuple *tuple,
|
||||
sk = __udp4_lib_lookup(net, src4, tuple->ipv4.sport,
|
||||
dst4, tuple->ipv4.dport,
|
||||
dif, sdif, &udp_table, skb);
|
||||
#if IS_REACHABLE(CONFIG_IPV6)
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
} else {
|
||||
struct in6_addr *src6 = (struct in6_addr *)&tuple->ipv6.saddr;
|
||||
struct in6_addr *dst6 = (struct in6_addr *)&tuple->ipv6.daddr;
|
||||
u16 hnum = ntohs(tuple->ipv6.dport);
|
||||
int sdif = inet6_sdif(skb);
|
||||
|
||||
if (proto == IPPROTO_TCP)
|
||||
sk = __inet6_lookup(net, &tcp_hashinfo, skb, 0,
|
||||
src6, tuple->ipv6.sport,
|
||||
dst6, tuple->ipv6.dport,
|
||||
dst6, hnum,
|
||||
dif, sdif, &refcounted);
|
||||
else
|
||||
sk = __udp6_lib_lookup(net, src6, tuple->ipv6.sport,
|
||||
dst6, tuple->ipv6.dport,
|
||||
dif, sdif, &udp_table, skb);
|
||||
else if (likely(ipv6_bpf_stub))
|
||||
sk = ipv6_bpf_stub->udp6_lib_lookup(net,
|
||||
src6, tuple->ipv6.sport,
|
||||
dst6, hnum,
|
||||
dif, sdif,
|
||||
&udp_table, skb);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -5200,6 +5081,9 @@ xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
}
|
||||
}
|
||||
|
||||
const struct bpf_func_proto bpf_sock_map_update_proto __weak;
|
||||
const struct bpf_func_proto bpf_sock_hash_update_proto __weak;
|
||||
|
||||
static const struct bpf_func_proto *
|
||||
sock_ops_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
{
|
||||
@ -5223,6 +5107,9 @@ sock_ops_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
}
|
||||
}
|
||||
|
||||
const struct bpf_func_proto bpf_msg_redirect_map_proto __weak;
|
||||
const struct bpf_func_proto bpf_msg_redirect_hash_proto __weak;
|
||||
|
||||
static const struct bpf_func_proto *
|
||||
sk_msg_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
{
|
||||
@ -5244,6 +5131,9 @@ sk_msg_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
}
|
||||
}
|
||||
|
||||
const struct bpf_func_proto bpf_sk_redirect_map_proto __weak;
|
||||
const struct bpf_func_proto bpf_sk_redirect_hash_proto __weak;
|
||||
|
||||
static const struct bpf_func_proto *
|
||||
sk_skb_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
{
|
||||
@ -6998,22 +6888,22 @@ static u32 sk_msg_convert_ctx_access(enum bpf_access_type type,
|
||||
|
||||
switch (si->off) {
|
||||
case offsetof(struct sk_msg_md, data):
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_msg_buff, data),
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_msg, data),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, data));
|
||||
offsetof(struct sk_msg, data));
|
||||
break;
|
||||
case offsetof(struct sk_msg_md, data_end):
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_msg_buff, data_end),
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_msg, data_end),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, data_end));
|
||||
offsetof(struct sk_msg, data_end));
|
||||
break;
|
||||
case offsetof(struct sk_msg_md, family):
|
||||
BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_family) != 2);
|
||||
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
||||
struct sk_msg_buff, sk),
|
||||
struct sk_msg, sk),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, sk));
|
||||
offsetof(struct sk_msg, sk));
|
||||
*insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
|
||||
offsetof(struct sock_common, skc_family));
|
||||
break;
|
||||
@ -7022,9 +6912,9 @@ static u32 sk_msg_convert_ctx_access(enum bpf_access_type type,
|
||||
BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_daddr) != 4);
|
||||
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
||||
struct sk_msg_buff, sk),
|
||||
struct sk_msg, sk),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, sk));
|
||||
offsetof(struct sk_msg, sk));
|
||||
*insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
|
||||
offsetof(struct sock_common, skc_daddr));
|
||||
break;
|
||||
@ -7034,9 +6924,9 @@ static u32 sk_msg_convert_ctx_access(enum bpf_access_type type,
|
||||
skc_rcv_saddr) != 4);
|
||||
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
||||
struct sk_msg_buff, sk),
|
||||
struct sk_msg, sk),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, sk));
|
||||
offsetof(struct sk_msg, sk));
|
||||
*insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
|
||||
offsetof(struct sock_common,
|
||||
skc_rcv_saddr));
|
||||
@ -7051,9 +6941,9 @@ static u32 sk_msg_convert_ctx_access(enum bpf_access_type type,
|
||||
off = si->off;
|
||||
off -= offsetof(struct sk_msg_md, remote_ip6[0]);
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
||||
struct sk_msg_buff, sk),
|
||||
struct sk_msg, sk),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, sk));
|
||||
offsetof(struct sk_msg, sk));
|
||||
*insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
|
||||
offsetof(struct sock_common,
|
||||
skc_v6_daddr.s6_addr32[0]) +
|
||||
@ -7072,9 +6962,9 @@ static u32 sk_msg_convert_ctx_access(enum bpf_access_type type,
|
||||
off = si->off;
|
||||
off -= offsetof(struct sk_msg_md, local_ip6[0]);
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
||||
struct sk_msg_buff, sk),
|
||||
struct sk_msg, sk),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, sk));
|
||||
offsetof(struct sk_msg, sk));
|
||||
*insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
|
||||
offsetof(struct sock_common,
|
||||
skc_v6_rcv_saddr.s6_addr32[0]) +
|
||||
@ -7088,9 +6978,9 @@ static u32 sk_msg_convert_ctx_access(enum bpf_access_type type,
|
||||
BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_dport) != 2);
|
||||
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
||||
struct sk_msg_buff, sk),
|
||||
struct sk_msg, sk),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, sk));
|
||||
offsetof(struct sk_msg, sk));
|
||||
*insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
|
||||
offsetof(struct sock_common, skc_dport));
|
||||
#ifndef __BIG_ENDIAN_BITFIELD
|
||||
@ -7102,9 +6992,9 @@ static u32 sk_msg_convert_ctx_access(enum bpf_access_type type,
|
||||
BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_num) != 2);
|
||||
|
||||
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
||||
struct sk_msg_buff, sk),
|
||||
struct sk_msg, sk),
|
||||
si->dst_reg, si->src_reg,
|
||||
offsetof(struct sk_msg_buff, sk));
|
||||
offsetof(struct sk_msg, sk));
|
||||
*insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
|
||||
offsetof(struct sock_common, skc_num));
|
||||
break;
|
||||
|
802
net/core/skmsg.c
Normal file
802
net/core/skmsg.c
Normal file
@ -0,0 +1,802 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2017 - 2018 Covalent IO, Inc. http://covalent.io */
|
||||
|
||||
#include <linux/skmsg.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
#include <net/sock.h>
|
||||
#include <net/tcp.h>
|
||||
|
||||
static bool sk_msg_try_coalesce_ok(struct sk_msg *msg, int elem_first_coalesce)
|
||||
{
|
||||
if (msg->sg.end > msg->sg.start &&
|
||||
elem_first_coalesce < msg->sg.end)
|
||||
return true;
|
||||
|
||||
if (msg->sg.end < msg->sg.start &&
|
||||
(elem_first_coalesce > msg->sg.start ||
|
||||
elem_first_coalesce < msg->sg.end))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int sk_msg_alloc(struct sock *sk, struct sk_msg *msg, int len,
|
||||
int elem_first_coalesce)
|
||||
{
|
||||
struct page_frag *pfrag = sk_page_frag(sk);
|
||||
int ret = 0;
|
||||
|
||||
len -= msg->sg.size;
|
||||
while (len > 0) {
|
||||
struct scatterlist *sge;
|
||||
u32 orig_offset;
|
||||
int use, i;
|
||||
|
||||
if (!sk_page_frag_refill(sk, pfrag))
|
||||
return -ENOMEM;
|
||||
|
||||
orig_offset = pfrag->offset;
|
||||
use = min_t(int, len, pfrag->size - orig_offset);
|
||||
if (!sk_wmem_schedule(sk, use))
|
||||
return -ENOMEM;
|
||||
|
||||
i = msg->sg.end;
|
||||
sk_msg_iter_var_prev(i);
|
||||
sge = &msg->sg.data[i];
|
||||
|
||||
if (sk_msg_try_coalesce_ok(msg, elem_first_coalesce) &&
|
||||
sg_page(sge) == pfrag->page &&
|
||||
sge->offset + sge->length == orig_offset) {
|
||||
sge->length += use;
|
||||
} else {
|
||||
if (sk_msg_full(msg)) {
|
||||
ret = -ENOSPC;
|
||||
break;
|
||||
}
|
||||
|
||||
sge = &msg->sg.data[msg->sg.end];
|
||||
sg_unmark_end(sge);
|
||||
sg_set_page(sge, pfrag->page, use, orig_offset);
|
||||
get_page(pfrag->page);
|
||||
sk_msg_iter_next(msg, end);
|
||||
}
|
||||
|
||||
sk_mem_charge(sk, use);
|
||||
msg->sg.size += use;
|
||||
pfrag->offset += use;
|
||||
len -= use;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_alloc);
|
||||
|
||||
int sk_msg_clone(struct sock *sk, struct sk_msg *dst, struct sk_msg *src,
|
||||
u32 off, u32 len)
|
||||
{
|
||||
int i = src->sg.start;
|
||||
struct scatterlist *sge = sk_msg_elem(src, i);
|
||||
u32 sge_len, sge_off;
|
||||
|
||||
if (sk_msg_full(dst))
|
||||
return -ENOSPC;
|
||||
|
||||
while (off) {
|
||||
if (sge->length > off)
|
||||
break;
|
||||
off -= sge->length;
|
||||
sk_msg_iter_var_next(i);
|
||||
if (i == src->sg.end && off)
|
||||
return -ENOSPC;
|
||||
sge = sk_msg_elem(src, i);
|
||||
}
|
||||
|
||||
while (len) {
|
||||
sge_len = sge->length - off;
|
||||
sge_off = sge->offset + off;
|
||||
if (sge_len > len)
|
||||
sge_len = len;
|
||||
off = 0;
|
||||
len -= sge_len;
|
||||
sk_msg_page_add(dst, sg_page(sge), sge_len, sge_off);
|
||||
sk_mem_charge(sk, sge_len);
|
||||
sk_msg_iter_var_next(i);
|
||||
if (i == src->sg.end && len)
|
||||
return -ENOSPC;
|
||||
sge = sk_msg_elem(src, i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_clone);
|
||||
|
||||
void sk_msg_return_zero(struct sock *sk, struct sk_msg *msg, int bytes)
|
||||
{
|
||||
int i = msg->sg.start;
|
||||
|
||||
do {
|
||||
struct scatterlist *sge = sk_msg_elem(msg, i);
|
||||
|
||||
if (bytes < sge->length) {
|
||||
sge->length -= bytes;
|
||||
sge->offset += bytes;
|
||||
sk_mem_uncharge(sk, bytes);
|
||||
break;
|
||||
}
|
||||
|
||||
sk_mem_uncharge(sk, sge->length);
|
||||
bytes -= sge->length;
|
||||
sge->length = 0;
|
||||
sge->offset = 0;
|
||||
sk_msg_iter_var_next(i);
|
||||
} while (bytes && i != msg->sg.end);
|
||||
msg->sg.start = i;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_return_zero);
|
||||
|
||||
void sk_msg_return(struct sock *sk, struct sk_msg *msg, int bytes)
|
||||
{
|
||||
int i = msg->sg.start;
|
||||
|
||||
do {
|
||||
struct scatterlist *sge = &msg->sg.data[i];
|
||||
int uncharge = (bytes < sge->length) ? bytes : sge->length;
|
||||
|
||||
sk_mem_uncharge(sk, uncharge);
|
||||
bytes -= uncharge;
|
||||
sk_msg_iter_var_next(i);
|
||||
} while (i != msg->sg.end);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_return);
|
||||
|
||||
static int sk_msg_free_elem(struct sock *sk, struct sk_msg *msg, u32 i,
|
||||
bool charge)
|
||||
{
|
||||
struct scatterlist *sge = sk_msg_elem(msg, i);
|
||||
u32 len = sge->length;
|
||||
|
||||
if (charge)
|
||||
sk_mem_uncharge(sk, len);
|
||||
if (!msg->skb)
|
||||
put_page(sg_page(sge));
|
||||
memset(sge, 0, sizeof(*sge));
|
||||
return len;
|
||||
}
|
||||
|
||||
static int __sk_msg_free(struct sock *sk, struct sk_msg *msg, u32 i,
|
||||
bool charge)
|
||||
{
|
||||
struct scatterlist *sge = sk_msg_elem(msg, i);
|
||||
int freed = 0;
|
||||
|
||||
while (msg->sg.size) {
|
||||
msg->sg.size -= sge->length;
|
||||
freed += sk_msg_free_elem(sk, msg, i, charge);
|
||||
sk_msg_iter_var_next(i);
|
||||
sk_msg_check_to_free(msg, i, msg->sg.size);
|
||||
sge = sk_msg_elem(msg, i);
|
||||
}
|
||||
if (msg->skb)
|
||||
consume_skb(msg->skb);
|
||||
sk_msg_init(msg);
|
||||
return freed;
|
||||
}
|
||||
|
||||
int sk_msg_free_nocharge(struct sock *sk, struct sk_msg *msg)
|
||||
{
|
||||
return __sk_msg_free(sk, msg, msg->sg.start, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_free_nocharge);
|
||||
|
||||
int sk_msg_free(struct sock *sk, struct sk_msg *msg)
|
||||
{
|
||||
return __sk_msg_free(sk, msg, msg->sg.start, true);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_free);
|
||||
|
||||
static void __sk_msg_free_partial(struct sock *sk, struct sk_msg *msg,
|
||||
u32 bytes, bool charge)
|
||||
{
|
||||
struct scatterlist *sge;
|
||||
u32 i = msg->sg.start;
|
||||
|
||||
while (bytes) {
|
||||
sge = sk_msg_elem(msg, i);
|
||||
if (!sge->length)
|
||||
break;
|
||||
if (bytes < sge->length) {
|
||||
if (charge)
|
||||
sk_mem_uncharge(sk, bytes);
|
||||
sge->length -= bytes;
|
||||
sge->offset += bytes;
|
||||
msg->sg.size -= bytes;
|
||||
break;
|
||||
}
|
||||
|
||||
msg->sg.size -= sge->length;
|
||||
bytes -= sge->length;
|
||||
sk_msg_free_elem(sk, msg, i, charge);
|
||||
sk_msg_iter_var_next(i);
|
||||
sk_msg_check_to_free(msg, i, bytes);
|
||||
}
|
||||
msg->sg.start = i;
|
||||
}
|
||||
|
||||
void sk_msg_free_partial(struct sock *sk, struct sk_msg *msg, u32 bytes)
|
||||
{
|
||||
__sk_msg_free_partial(sk, msg, bytes, true);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_free_partial);
|
||||
|
||||
void sk_msg_free_partial_nocharge(struct sock *sk, struct sk_msg *msg,
|
||||
u32 bytes)
|
||||
{
|
||||
__sk_msg_free_partial(sk, msg, bytes, false);
|
||||
}
|
||||
|
||||
void sk_msg_trim(struct sock *sk, struct sk_msg *msg, int len)
|
||||
{
|
||||
int trim = msg->sg.size - len;
|
||||
u32 i = msg->sg.end;
|
||||
|
||||
if (trim <= 0) {
|
||||
WARN_ON(trim < 0);
|
||||
return;
|
||||
}
|
||||
|
||||
sk_msg_iter_var_prev(i);
|
||||
msg->sg.size = len;
|
||||
while (msg->sg.data[i].length &&
|
||||
trim >= msg->sg.data[i].length) {
|
||||
trim -= msg->sg.data[i].length;
|
||||
sk_msg_free_elem(sk, msg, i, true);
|
||||
sk_msg_iter_var_prev(i);
|
||||
if (!trim)
|
||||
goto out;
|
||||
}
|
||||
|
||||
msg->sg.data[i].length -= trim;
|
||||
sk_mem_uncharge(sk, trim);
|
||||
out:
|
||||
/* If we trim data before curr pointer update copybreak and current
|
||||
* so that any future copy operations start at new copy location.
|
||||
* However trimed data that has not yet been used in a copy op
|
||||
* does not require an update.
|
||||
*/
|
||||
if (msg->sg.curr >= i) {
|
||||
msg->sg.curr = i;
|
||||
msg->sg.copybreak = msg->sg.data[i].length;
|
||||
}
|
||||
sk_msg_iter_var_next(i);
|
||||
msg->sg.end = i;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_trim);
|
||||
|
||||
int sk_msg_zerocopy_from_iter(struct sock *sk, struct iov_iter *from,
|
||||
struct sk_msg *msg, u32 bytes)
|
||||
{
|
||||
int i, maxpages, ret = 0, num_elems = sk_msg_elem_used(msg);
|
||||
const int to_max_pages = MAX_MSG_FRAGS;
|
||||
struct page *pages[MAX_MSG_FRAGS];
|
||||
ssize_t orig, copied, use, offset;
|
||||
|
||||
orig = msg->sg.size;
|
||||
while (bytes > 0) {
|
||||
i = 0;
|
||||
maxpages = to_max_pages - num_elems;
|
||||
if (maxpages == 0) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
copied = iov_iter_get_pages(from, pages, bytes, maxpages,
|
||||
&offset);
|
||||
if (copied <= 0) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
iov_iter_advance(from, copied);
|
||||
bytes -= copied;
|
||||
msg->sg.size += copied;
|
||||
|
||||
while (copied) {
|
||||
use = min_t(int, copied, PAGE_SIZE - offset);
|
||||
sg_set_page(&msg->sg.data[msg->sg.end],
|
||||
pages[i], use, offset);
|
||||
sg_unmark_end(&msg->sg.data[msg->sg.end]);
|
||||
sk_mem_charge(sk, use);
|
||||
|
||||
offset = 0;
|
||||
copied -= use;
|
||||
sk_msg_iter_next(msg, end);
|
||||
num_elems++;
|
||||
i++;
|
||||
}
|
||||
/* When zerocopy is mixed with sk_msg_*copy* operations we
|
||||
* may have a copybreak set in this case clear and prefer
|
||||
* zerocopy remainder when possible.
|
||||
*/
|
||||
msg->sg.copybreak = 0;
|
||||
msg->sg.curr = msg->sg.end;
|
||||
}
|
||||
out:
|
||||
/* Revert iov_iter updates, msg will need to use 'trim' later if it
|
||||
* also needs to be cleared.
|
||||
*/
|
||||
if (ret)
|
||||
iov_iter_revert(from, msg->sg.size - orig);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_zerocopy_from_iter);
|
||||
|
||||
int sk_msg_memcopy_from_iter(struct sock *sk, struct iov_iter *from,
|
||||
struct sk_msg *msg, u32 bytes)
|
||||
{
|
||||
int ret = -ENOSPC, i = msg->sg.curr;
|
||||
struct scatterlist *sge;
|
||||
u32 copy, buf_size;
|
||||
void *to;
|
||||
|
||||
do {
|
||||
sge = sk_msg_elem(msg, i);
|
||||
/* This is possible if a trim operation shrunk the buffer */
|
||||
if (msg->sg.copybreak >= sge->length) {
|
||||
msg->sg.copybreak = 0;
|
||||
sk_msg_iter_var_next(i);
|
||||
if (i == msg->sg.end)
|
||||
break;
|
||||
sge = sk_msg_elem(msg, i);
|
||||
}
|
||||
|
||||
buf_size = sge->length - msg->sg.copybreak;
|
||||
copy = (buf_size > bytes) ? bytes : buf_size;
|
||||
to = sg_virt(sge) + msg->sg.copybreak;
|
||||
msg->sg.copybreak += copy;
|
||||
if (sk->sk_route_caps & NETIF_F_NOCACHE_COPY)
|
||||
ret = copy_from_iter_nocache(to, copy, from);
|
||||
else
|
||||
ret = copy_from_iter(to, copy, from);
|
||||
if (ret != copy) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
bytes -= copy;
|
||||
if (!bytes)
|
||||
break;
|
||||
msg->sg.copybreak = 0;
|
||||
sk_msg_iter_var_next(i);
|
||||
} while (i != msg->sg.end);
|
||||
out:
|
||||
msg->sg.curr = i;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_msg_memcopy_from_iter);
|
||||
|
||||
static int sk_psock_skb_ingress(struct sk_psock *psock, struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk = psock->sk;
|
||||
int copied = 0, num_sge;
|
||||
struct sk_msg *msg;
|
||||
|
||||
msg = kzalloc(sizeof(*msg), __GFP_NOWARN | GFP_ATOMIC);
|
||||
if (unlikely(!msg))
|
||||
return -EAGAIN;
|
||||
if (!sk_rmem_schedule(sk, skb, skb->len)) {
|
||||
kfree(msg);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
sk_msg_init(msg);
|
||||
num_sge = skb_to_sgvec(skb, msg->sg.data, 0, skb->len);
|
||||
if (unlikely(num_sge < 0)) {
|
||||
kfree(msg);
|
||||
return num_sge;
|
||||
}
|
||||
|
||||
sk_mem_charge(sk, skb->len);
|
||||
copied = skb->len;
|
||||
msg->sg.start = 0;
|
||||
msg->sg.end = num_sge == MAX_MSG_FRAGS ? 0 : num_sge;
|
||||
msg->skb = skb;
|
||||
|
||||
sk_psock_queue_msg(psock, msg);
|
||||
sk->sk_data_ready(sk);
|
||||
return copied;
|
||||
}
|
||||
|
||||
static int sk_psock_handle_skb(struct sk_psock *psock, struct sk_buff *skb,
|
||||
u32 off, u32 len, bool ingress)
|
||||
{
|
||||
if (ingress)
|
||||
return sk_psock_skb_ingress(psock, skb);
|
||||
else
|
||||
return skb_send_sock_locked(psock->sk, skb, off, len);
|
||||
}
|
||||
|
||||
static void sk_psock_backlog(struct work_struct *work)
|
||||
{
|
||||
struct sk_psock *psock = container_of(work, struct sk_psock, work);
|
||||
struct sk_psock_work_state *state = &psock->work_state;
|
||||
struct sk_buff *skb;
|
||||
bool ingress;
|
||||
u32 len, off;
|
||||
int ret;
|
||||
|
||||
/* Lock sock to avoid losing sk_socket during loop. */
|
||||
lock_sock(psock->sk);
|
||||
if (state->skb) {
|
||||
skb = state->skb;
|
||||
len = state->len;
|
||||
off = state->off;
|
||||
state->skb = NULL;
|
||||
goto start;
|
||||
}
|
||||
|
||||
while ((skb = skb_dequeue(&psock->ingress_skb))) {
|
||||
len = skb->len;
|
||||
off = 0;
|
||||
start:
|
||||
ingress = tcp_skb_bpf_ingress(skb);
|
||||
do {
|
||||
ret = -EIO;
|
||||
if (likely(psock->sk->sk_socket))
|
||||
ret = sk_psock_handle_skb(psock, skb, off,
|
||||
len, ingress);
|
||||
if (ret <= 0) {
|
||||
if (ret == -EAGAIN) {
|
||||
state->skb = skb;
|
||||
state->len = len;
|
||||
state->off = off;
|
||||
goto end;
|
||||
}
|
||||
/* Hard errors break pipe and stop xmit. */
|
||||
sk_psock_report_error(psock, ret ? -ret : EPIPE);
|
||||
sk_psock_clear_state(psock, SK_PSOCK_TX_ENABLED);
|
||||
kfree_skb(skb);
|
||||
goto end;
|
||||
}
|
||||
off += ret;
|
||||
len -= ret;
|
||||
} while (len);
|
||||
|
||||
if (!ingress)
|
||||
kfree_skb(skb);
|
||||
}
|
||||
end:
|
||||
release_sock(psock->sk);
|
||||
}
|
||||
|
||||
struct sk_psock *sk_psock_init(struct sock *sk, int node)
|
||||
{
|
||||
struct sk_psock *psock = kzalloc_node(sizeof(*psock),
|
||||
GFP_ATOMIC | __GFP_NOWARN,
|
||||
node);
|
||||
if (!psock)
|
||||
return NULL;
|
||||
|
||||
psock->sk = sk;
|
||||
psock->eval = __SK_NONE;
|
||||
|
||||
INIT_LIST_HEAD(&psock->link);
|
||||
spin_lock_init(&psock->link_lock);
|
||||
|
||||
INIT_WORK(&psock->work, sk_psock_backlog);
|
||||
INIT_LIST_HEAD(&psock->ingress_msg);
|
||||
skb_queue_head_init(&psock->ingress_skb);
|
||||
|
||||
sk_psock_set_state(psock, SK_PSOCK_TX_ENABLED);
|
||||
refcount_set(&psock->refcnt, 1);
|
||||
|
||||
rcu_assign_sk_user_data(sk, psock);
|
||||
sock_hold(sk);
|
||||
|
||||
return psock;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_psock_init);
|
||||
|
||||
struct sk_psock_link *sk_psock_link_pop(struct sk_psock *psock)
|
||||
{
|
||||
struct sk_psock_link *link;
|
||||
|
||||
spin_lock_bh(&psock->link_lock);
|
||||
link = list_first_entry_or_null(&psock->link, struct sk_psock_link,
|
||||
list);
|
||||
if (link)
|
||||
list_del(&link->list);
|
||||
spin_unlock_bh(&psock->link_lock);
|
||||
return link;
|
||||
}
|
||||
|
||||
void __sk_psock_purge_ingress_msg(struct sk_psock *psock)
|
||||
{
|
||||
struct sk_msg *msg, *tmp;
|
||||
|
||||
list_for_each_entry_safe(msg, tmp, &psock->ingress_msg, list) {
|
||||
list_del(&msg->list);
|
||||
sk_msg_free(psock->sk, msg);
|
||||
kfree(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void sk_psock_zap_ingress(struct sk_psock *psock)
|
||||
{
|
||||
__skb_queue_purge(&psock->ingress_skb);
|
||||
__sk_psock_purge_ingress_msg(psock);
|
||||
}
|
||||
|
||||
static void sk_psock_link_destroy(struct sk_psock *psock)
|
||||
{
|
||||
struct sk_psock_link *link, *tmp;
|
||||
|
||||
list_for_each_entry_safe(link, tmp, &psock->link, list) {
|
||||
list_del(&link->list);
|
||||
sk_psock_free_link(link);
|
||||
}
|
||||
}
|
||||
|
||||
static void sk_psock_destroy_deferred(struct work_struct *gc)
|
||||
{
|
||||
struct sk_psock *psock = container_of(gc, struct sk_psock, gc);
|
||||
|
||||
/* No sk_callback_lock since already detached. */
|
||||
if (psock->parser.enabled)
|
||||
strp_done(&psock->parser.strp);
|
||||
|
||||
cancel_work_sync(&psock->work);
|
||||
|
||||
psock_progs_drop(&psock->progs);
|
||||
|
||||
sk_psock_link_destroy(psock);
|
||||
sk_psock_cork_free(psock);
|
||||
sk_psock_zap_ingress(psock);
|
||||
|
||||
if (psock->sk_redir)
|
||||
sock_put(psock->sk_redir);
|
||||
sock_put(psock->sk);
|
||||
kfree(psock);
|
||||
}
|
||||
|
||||
void sk_psock_destroy(struct rcu_head *rcu)
|
||||
{
|
||||
struct sk_psock *psock = container_of(rcu, struct sk_psock, rcu);
|
||||
|
||||
INIT_WORK(&psock->gc, sk_psock_destroy_deferred);
|
||||
schedule_work(&psock->gc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_psock_destroy);
|
||||
|
||||
void sk_psock_drop(struct sock *sk, struct sk_psock *psock)
|
||||
{
|
||||
rcu_assign_sk_user_data(sk, NULL);
|
||||
sk_psock_cork_free(psock);
|
||||
sk_psock_restore_proto(sk, psock);
|
||||
|
||||
write_lock_bh(&sk->sk_callback_lock);
|
||||
if (psock->progs.skb_parser)
|
||||
sk_psock_stop_strp(sk, psock);
|
||||
write_unlock_bh(&sk->sk_callback_lock);
|
||||
sk_psock_clear_state(psock, SK_PSOCK_TX_ENABLED);
|
||||
|
||||
call_rcu_sched(&psock->rcu, sk_psock_destroy);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_psock_drop);
|
||||
|
||||
static int sk_psock_map_verd(int verdict, bool redir)
|
||||
{
|
||||
switch (verdict) {
|
||||
case SK_PASS:
|
||||
return redir ? __SK_REDIRECT : __SK_PASS;
|
||||
case SK_DROP:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return __SK_DROP;
|
||||
}
|
||||
|
||||
int sk_psock_msg_verdict(struct sock *sk, struct sk_psock *psock,
|
||||
struct sk_msg *msg)
|
||||
{
|
||||
struct bpf_prog *prog;
|
||||
int ret;
|
||||
|
||||
preempt_disable();
|
||||
rcu_read_lock();
|
||||
prog = READ_ONCE(psock->progs.msg_parser);
|
||||
if (unlikely(!prog)) {
|
||||
ret = __SK_PASS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
sk_msg_compute_data_pointers(msg);
|
||||
msg->sk = sk;
|
||||
ret = BPF_PROG_RUN(prog, msg);
|
||||
ret = sk_psock_map_verd(ret, msg->sk_redir);
|
||||
psock->apply_bytes = msg->apply_bytes;
|
||||
if (ret == __SK_REDIRECT) {
|
||||
if (psock->sk_redir)
|
||||
sock_put(psock->sk_redir);
|
||||
psock->sk_redir = msg->sk_redir;
|
||||
if (!psock->sk_redir) {
|
||||
ret = __SK_DROP;
|
||||
goto out;
|
||||
}
|
||||
sock_hold(psock->sk_redir);
|
||||
}
|
||||
out:
|
||||
rcu_read_unlock();
|
||||
preempt_enable();
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sk_psock_msg_verdict);
|
||||
|
||||
static int sk_psock_bpf_run(struct sk_psock *psock, struct bpf_prog *prog,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
skb->sk = psock->sk;
|
||||
bpf_compute_data_end_sk_skb(skb);
|
||||
preempt_disable();
|
||||
ret = BPF_PROG_RUN(prog, skb);
|
||||
preempt_enable();
|
||||
/* strparser clones the skb before handing it to a upper layer,
|
||||
* meaning skb_orphan has been called. We NULL sk on the way out
|
||||
* to ensure we don't trigger a BUG_ON() in skb/sk operations
|
||||
* later and because we are not charging the memory of this skb
|
||||
* to any socket yet.
|
||||
*/
|
||||
skb->sk = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sk_psock *sk_psock_from_strp(struct strparser *strp)
|
||||
{
|
||||
struct sk_psock_parser *parser;
|
||||
|
||||
parser = container_of(strp, struct sk_psock_parser, strp);
|
||||
return container_of(parser, struct sk_psock, parser);
|
||||
}
|
||||
|
||||
static void sk_psock_verdict_apply(struct sk_psock *psock,
|
||||
struct sk_buff *skb, int verdict)
|
||||
{
|
||||
struct sk_psock *psock_other;
|
||||
struct sock *sk_other;
|
||||
bool ingress;
|
||||
|
||||
switch (verdict) {
|
||||
case __SK_REDIRECT:
|
||||
sk_other = tcp_skb_bpf_redirect_fetch(skb);
|
||||
if (unlikely(!sk_other))
|
||||
goto out_free;
|
||||
psock_other = sk_psock(sk_other);
|
||||
if (!psock_other || sock_flag(sk_other, SOCK_DEAD) ||
|
||||
!sk_psock_test_state(psock_other, SK_PSOCK_TX_ENABLED))
|
||||
goto out_free;
|
||||
ingress = tcp_skb_bpf_ingress(skb);
|
||||
if ((!ingress && sock_writeable(sk_other)) ||
|
||||
(ingress &&
|
||||
atomic_read(&sk_other->sk_rmem_alloc) <=
|
||||
sk_other->sk_rcvbuf)) {
|
||||
if (!ingress)
|
||||
skb_set_owner_w(skb, sk_other);
|
||||
skb_queue_tail(&psock_other->ingress_skb, skb);
|
||||
schedule_work(&psock_other->work);
|
||||
break;
|
||||
}
|
||||
/* fall-through */
|
||||
case __SK_DROP:
|
||||
/* fall-through */
|
||||
default:
|
||||
out_free:
|
||||
kfree_skb(skb);
|
||||
}
|
||||
}
|
||||
|
||||
static void sk_psock_strp_read(struct strparser *strp, struct sk_buff *skb)
|
||||
{
|
||||
struct sk_psock *psock = sk_psock_from_strp(strp);
|
||||
struct bpf_prog *prog;
|
||||
int ret = __SK_DROP;
|
||||
|
||||
rcu_read_lock();
|
||||
prog = READ_ONCE(psock->progs.skb_verdict);
|
||||
if (likely(prog)) {
|
||||
skb_orphan(skb);
|
||||
tcp_skb_bpf_redirect_clear(skb);
|
||||
ret = sk_psock_bpf_run(psock, prog, skb);
|
||||
ret = sk_psock_map_verd(ret, tcp_skb_bpf_redirect_fetch(skb));
|
||||
}
|
||||
rcu_read_unlock();
|
||||
sk_psock_verdict_apply(psock, skb, ret);
|
||||
}
|
||||
|
||||
static int sk_psock_strp_read_done(struct strparser *strp, int err)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
static int sk_psock_strp_parse(struct strparser *strp, struct sk_buff *skb)
|
||||
{
|
||||
struct sk_psock *psock = sk_psock_from_strp(strp);
|
||||
struct bpf_prog *prog;
|
||||
int ret = skb->len;
|
||||
|
||||
rcu_read_lock();
|
||||
prog = READ_ONCE(psock->progs.skb_parser);
|
||||
if (likely(prog))
|
||||
ret = sk_psock_bpf_run(psock, prog, skb);
|
||||
rcu_read_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Called with socket lock held. */
|
||||
static void sk_psock_data_ready(struct sock *sk)
|
||||
{
|
||||
struct sk_psock *psock;
|
||||
|
||||
rcu_read_lock();
|
||||
psock = sk_psock(sk);
|
||||
if (likely(psock)) {
|
||||
write_lock_bh(&sk->sk_callback_lock);
|
||||
strp_data_ready(&psock->parser.strp);
|
||||
write_unlock_bh(&sk->sk_callback_lock);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
static void sk_psock_write_space(struct sock *sk)
|
||||
{
|
||||
struct sk_psock *psock;
|
||||
void (*write_space)(struct sock *sk);
|
||||
|
||||
rcu_read_lock();
|
||||
psock = sk_psock(sk);
|
||||
if (likely(psock && sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)))
|
||||
schedule_work(&psock->work);
|
||||
write_space = psock->saved_write_space;
|
||||
rcu_read_unlock();
|
||||
write_space(sk);
|
||||
}
|
||||
|
||||
int sk_psock_init_strp(struct sock *sk, struct sk_psock *psock)
|
||||
{
|
||||
static const struct strp_callbacks cb = {
|
||||
.rcv_msg = sk_psock_strp_read,
|
||||
.read_sock_done = sk_psock_strp_read_done,
|
||||
.parse_msg = sk_psock_strp_parse,
|
||||
};
|
||||
|
||||
psock->parser.enabled = false;
|
||||
return strp_init(&psock->parser.strp, sk, &cb);
|
||||
}
|
||||
|
||||
void sk_psock_start_strp(struct sock *sk, struct sk_psock *psock)
|
||||
{
|
||||
struct sk_psock_parser *parser = &psock->parser;
|
||||
|
||||
if (parser->enabled)
|
||||
return;
|
||||
|
||||
parser->saved_data_ready = sk->sk_data_ready;
|
||||
sk->sk_data_ready = sk_psock_data_ready;
|
||||
sk->sk_write_space = sk_psock_write_space;
|
||||
parser->enabled = true;
|
||||
}
|
||||
|
||||
void sk_psock_stop_strp(struct sock *sk, struct sk_psock *psock)
|
||||
{
|
||||
struct sk_psock_parser *parser = &psock->parser;
|
||||
|
||||
if (!parser->enabled)
|
||||
return;
|
||||
|
||||
sk->sk_data_ready = parser->saved_data_ready;
|
||||
parser->saved_data_ready = NULL;
|
||||
strp_stop(&parser->strp);
|
||||
parser->enabled = false;
|
||||
}
|
@ -2239,67 +2239,6 @@ bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag)
|
||||
}
|
||||
EXPORT_SYMBOL(sk_page_frag_refill);
|
||||
|
||||
int sk_alloc_sg(struct sock *sk, int len, struct scatterlist *sg,
|
||||
int sg_start, int *sg_curr_index, unsigned int *sg_curr_size,
|
||||
int first_coalesce)
|
||||
{
|
||||
int sg_curr = *sg_curr_index, use = 0, rc = 0;
|
||||
unsigned int size = *sg_curr_size;
|
||||
struct page_frag *pfrag;
|
||||
struct scatterlist *sge;
|
||||
|
||||
len -= size;
|
||||
pfrag = sk_page_frag(sk);
|
||||
|
||||
while (len > 0) {
|
||||
unsigned int orig_offset;
|
||||
|
||||
if (!sk_page_frag_refill(sk, pfrag)) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
use = min_t(int, len, pfrag->size - pfrag->offset);
|
||||
|
||||
if (!sk_wmem_schedule(sk, use)) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
sk_mem_charge(sk, use);
|
||||
size += use;
|
||||
orig_offset = pfrag->offset;
|
||||
pfrag->offset += use;
|
||||
|
||||
sge = sg + sg_curr - 1;
|
||||
if (sg_curr > first_coalesce && sg_page(sge) == pfrag->page &&
|
||||
sge->offset + sge->length == orig_offset) {
|
||||
sge->length += use;
|
||||
} else {
|
||||
sge = sg + sg_curr;
|
||||
sg_unmark_end(sge);
|
||||
sg_set_page(sge, pfrag->page, use, orig_offset);
|
||||
get_page(pfrag->page);
|
||||
sg_curr++;
|
||||
|
||||
if (sg_curr == MAX_SKB_FRAGS)
|
||||
sg_curr = 0;
|
||||
|
||||
if (sg_curr == sg_start) {
|
||||
rc = -ENOSPC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
len -= use;
|
||||
}
|
||||
out:
|
||||
*sg_curr_size = size;
|
||||
*sg_curr_index = sg_curr;
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(sk_alloc_sg);
|
||||
|
||||
static void __lock_sock(struct sock *sk)
|
||||
__releases(&sk->sk_lock.slock)
|
||||
__acquires(&sk->sk_lock.slock)
|
||||
|
1002
net/core/sock_map.c
Normal file
1002
net/core/sock_map.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -63,6 +63,7 @@ obj-$(CONFIG_TCP_CONG_SCALABLE) += tcp_scalable.o
|
||||
obj-$(CONFIG_TCP_CONG_LP) += tcp_lp.o
|
||||
obj-$(CONFIG_TCP_CONG_YEAH) += tcp_yeah.o
|
||||
obj-$(CONFIG_TCP_CONG_ILLINOIS) += tcp_illinois.o
|
||||
obj-$(CONFIG_NET_SOCK_MSG) += tcp_bpf.o
|
||||
obj-$(CONFIG_NETLABEL) += cipso_ipv4.o
|
||||
|
||||
obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \
|
||||
|
655
net/ipv4/tcp_bpf.c
Normal file
655
net/ipv4/tcp_bpf.c
Normal file
@ -0,0 +1,655 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2017 - 2018 Covalent IO, Inc. http://covalent.io */
|
||||
|
||||
#include <linux/skmsg.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include <net/inet_common.h>
|
||||
|
||||
static bool tcp_bpf_stream_read(const struct sock *sk)
|
||||
{
|
||||
struct sk_psock *psock;
|
||||
bool empty = true;
|
||||
|
||||
rcu_read_lock();
|
||||
psock = sk_psock(sk);
|
||||
if (likely(psock))
|
||||
empty = list_empty(&psock->ingress_msg);
|
||||
rcu_read_unlock();
|
||||
return !empty;
|
||||
}
|
||||
|
||||
static int tcp_bpf_wait_data(struct sock *sk, struct sk_psock *psock,
|
||||
int flags, long timeo, int *err)
|
||||
{
|
||||
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
||||
int ret;
|
||||
|
||||
add_wait_queue(sk_sleep(sk), &wait);
|
||||
sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk);
|
||||
ret = sk_wait_event(sk, &timeo,
|
||||
!list_empty(&psock->ingress_msg) ||
|
||||
!skb_queue_empty(&sk->sk_receive_queue), &wait);
|
||||
sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk);
|
||||
remove_wait_queue(sk_sleep(sk), &wait);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __tcp_bpf_recvmsg(struct sock *sk, struct sk_psock *psock,
|
||||
struct msghdr *msg, int len)
|
||||
{
|
||||
struct iov_iter *iter = &msg->msg_iter;
|
||||
int i, ret, copied = 0;
|
||||
|
||||
while (copied != len) {
|
||||
struct scatterlist *sge;
|
||||
struct sk_msg *msg_rx;
|
||||
|
||||
msg_rx = list_first_entry_or_null(&psock->ingress_msg,
|
||||
struct sk_msg, list);
|
||||
if (unlikely(!msg_rx))
|
||||
break;
|
||||
|
||||
i = msg_rx->sg.start;
|
||||
do {
|
||||
struct page *page;
|
||||
int copy;
|
||||
|
||||
sge = sk_msg_elem(msg_rx, i);
|
||||
copy = sge->length;
|
||||
page = sg_page(sge);
|
||||
if (copied + copy > len)
|
||||
copy = len - copied;
|
||||
ret = copy_page_to_iter(page, sge->offset, copy, iter);
|
||||
if (ret != copy) {
|
||||
msg_rx->sg.start = i;
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
copied += copy;
|
||||
sge->offset += copy;
|
||||
sge->length -= copy;
|
||||
sk_mem_uncharge(sk, copy);
|
||||
if (!sge->length) {
|
||||
i++;
|
||||
if (i == MAX_SKB_FRAGS)
|
||||
i = 0;
|
||||
if (!msg_rx->skb)
|
||||
put_page(page);
|
||||
}
|
||||
|
||||
if (copied == len)
|
||||
break;
|
||||
} while (i != msg_rx->sg.end);
|
||||
|
||||
msg_rx->sg.start = i;
|
||||
if (!sge->length && msg_rx->sg.start == msg_rx->sg.end) {
|
||||
list_del(&msg_rx->list);
|
||||
if (msg_rx->skb)
|
||||
consume_skb(msg_rx->skb);
|
||||
kfree(msg_rx);
|
||||
}
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__tcp_bpf_recvmsg);
|
||||
|
||||
int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
|
||||
int nonblock, int flags, int *addr_len)
|
||||
{
|
||||
struct sk_psock *psock;
|
||||
int copied, ret;
|
||||
|
||||
if (unlikely(flags & MSG_ERRQUEUE))
|
||||
return inet_recv_error(sk, msg, len, addr_len);
|
||||
if (!skb_queue_empty(&sk->sk_receive_queue))
|
||||
return tcp_recvmsg(sk, msg, len, nonblock, flags, addr_len);
|
||||
|
||||
psock = sk_psock_get(sk);
|
||||
if (unlikely(!psock))
|
||||
return tcp_recvmsg(sk, msg, len, nonblock, flags, addr_len);
|
||||
lock_sock(sk);
|
||||
msg_bytes_ready:
|
||||
copied = __tcp_bpf_recvmsg(sk, psock, msg, len);
|
||||
if (!copied) {
|
||||
int data, err = 0;
|
||||
long timeo;
|
||||
|
||||
timeo = sock_rcvtimeo(sk, nonblock);
|
||||
data = tcp_bpf_wait_data(sk, psock, flags, timeo, &err);
|
||||
if (data) {
|
||||
if (skb_queue_empty(&sk->sk_receive_queue))
|
||||
goto msg_bytes_ready;
|
||||
release_sock(sk);
|
||||
sk_psock_put(sk, psock);
|
||||
return tcp_recvmsg(sk, msg, len, nonblock, flags, addr_len);
|
||||
}
|
||||
if (err) {
|
||||
ret = err;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
ret = copied;
|
||||
out:
|
||||
release_sock(sk);
|
||||
sk_psock_put(sk, psock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bpf_tcp_ingress(struct sock *sk, struct sk_psock *psock,
|
||||
struct sk_msg *msg, u32 apply_bytes, int flags)
|
||||
{
|
||||
bool apply = apply_bytes;
|
||||
struct scatterlist *sge;
|
||||
u32 size, copied = 0;
|
||||
struct sk_msg *tmp;
|
||||
int i, ret = 0;
|
||||
|
||||
tmp = kzalloc(sizeof(*tmp), __GFP_NOWARN | GFP_KERNEL);
|
||||
if (unlikely(!tmp))
|
||||
return -ENOMEM;
|
||||
|
||||
lock_sock(sk);
|
||||
tmp->sg.start = msg->sg.start;
|
||||
i = msg->sg.start;
|
||||
do {
|
||||
sge = sk_msg_elem(msg, i);
|
||||
size = (apply && apply_bytes < sge->length) ?
|
||||
apply_bytes : sge->length;
|
||||
if (!sk_wmem_schedule(sk, size)) {
|
||||
if (!copied)
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
sk_mem_charge(sk, size);
|
||||
sk_msg_xfer(tmp, msg, i, size);
|
||||
copied += size;
|
||||
if (sge->length)
|
||||
get_page(sk_msg_page(tmp, i));
|
||||
sk_msg_iter_var_next(i);
|
||||
tmp->sg.end = i;
|
||||
if (apply) {
|
||||
apply_bytes -= size;
|
||||
if (!apply_bytes)
|
||||
break;
|
||||
}
|
||||
} while (i != msg->sg.end);
|
||||
|
||||
if (!ret) {
|
||||
msg->sg.start = i;
|
||||
msg->sg.size -= apply_bytes;
|
||||
sk_psock_queue_msg(psock, tmp);
|
||||
sk->sk_data_ready(sk);
|
||||
} else {
|
||||
sk_msg_free(sk, tmp);
|
||||
kfree(tmp);
|
||||
}
|
||||
|
||||
release_sock(sk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
|
||||
int flags, bool uncharge)
|
||||
{
|
||||
bool apply = apply_bytes;
|
||||
struct scatterlist *sge;
|
||||
struct page *page;
|
||||
int size, ret = 0;
|
||||
u32 off;
|
||||
|
||||
while (1) {
|
||||
sge = sk_msg_elem(msg, msg->sg.start);
|
||||
size = (apply && apply_bytes < sge->length) ?
|
||||
apply_bytes : sge->length;
|
||||
off = sge->offset;
|
||||
page = sg_page(sge);
|
||||
|
||||
tcp_rate_check_app_limited(sk);
|
||||
retry:
|
||||
ret = do_tcp_sendpages(sk, page, off, size, flags);
|
||||
if (ret <= 0)
|
||||
return ret;
|
||||
if (apply)
|
||||
apply_bytes -= ret;
|
||||
msg->sg.size -= ret;
|
||||
sge->offset += ret;
|
||||
sge->length -= ret;
|
||||
if (uncharge)
|
||||
sk_mem_uncharge(sk, ret);
|
||||
if (ret != size) {
|
||||
size -= ret;
|
||||
off += ret;
|
||||
goto retry;
|
||||
}
|
||||
if (!sge->length) {
|
||||
put_page(page);
|
||||
sk_msg_iter_next(msg, start);
|
||||
sg_init_table(sge, 1);
|
||||
if (msg->sg.start == msg->sg.end)
|
||||
break;
|
||||
}
|
||||
if (apply && !apply_bytes)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tcp_bpf_push_locked(struct sock *sk, struct sk_msg *msg,
|
||||
u32 apply_bytes, int flags, bool uncharge)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lock_sock(sk);
|
||||
ret = tcp_bpf_push(sk, msg, apply_bytes, flags, uncharge);
|
||||
release_sock(sk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int tcp_bpf_sendmsg_redir(struct sock *sk, struct sk_msg *msg,
|
||||
u32 bytes, int flags)
|
||||
{
|
||||
bool ingress = sk_msg_to_ingress(msg);
|
||||
struct sk_psock *psock = sk_psock_get(sk);
|
||||
int ret;
|
||||
|
||||
if (unlikely(!psock)) {
|
||||
sk_msg_free(sk, msg);
|
||||
return 0;
|
||||
}
|
||||
ret = ingress ? bpf_tcp_ingress(sk, psock, msg, bytes, flags) :
|
||||
tcp_bpf_push_locked(sk, msg, bytes, flags, false);
|
||||
sk_psock_put(sk, psock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tcp_bpf_sendmsg_redir);
|
||||
|
||||
static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
|
||||
struct sk_msg *msg, int *copied, int flags)
|
||||
{
|
||||
bool cork = false, enospc = msg->sg.start == msg->sg.end;
|
||||
struct sock *sk_redir;
|
||||
u32 tosend;
|
||||
int ret;
|
||||
|
||||
more_data:
|
||||
if (psock->eval == __SK_NONE)
|
||||
psock->eval = sk_psock_msg_verdict(sk, psock, msg);
|
||||
|
||||
if (msg->cork_bytes &&
|
||||
msg->cork_bytes > msg->sg.size && !enospc) {
|
||||
psock->cork_bytes = msg->cork_bytes - msg->sg.size;
|
||||
if (!psock->cork) {
|
||||
psock->cork = kzalloc(sizeof(*psock->cork),
|
||||
GFP_ATOMIC | __GFP_NOWARN);
|
||||
if (!psock->cork)
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(psock->cork, msg, sizeof(*msg));
|
||||
return 0;
|
||||
}
|
||||
|
||||
tosend = msg->sg.size;
|
||||
if (psock->apply_bytes && psock->apply_bytes < tosend)
|
||||
tosend = psock->apply_bytes;
|
||||
|
||||
switch (psock->eval) {
|
||||
case __SK_PASS:
|
||||
ret = tcp_bpf_push(sk, msg, tosend, flags, true);
|
||||
if (unlikely(ret)) {
|
||||
*copied -= sk_msg_free(sk, msg);
|
||||
break;
|
||||
}
|
||||
sk_msg_apply_bytes(psock, tosend);
|
||||
break;
|
||||
case __SK_REDIRECT:
|
||||
sk_redir = psock->sk_redir;
|
||||
sk_msg_apply_bytes(psock, tosend);
|
||||
if (psock->cork) {
|
||||
cork = true;
|
||||
psock->cork = NULL;
|
||||
}
|
||||
sk_msg_return(sk, msg, tosend);
|
||||
release_sock(sk);
|
||||
ret = tcp_bpf_sendmsg_redir(sk_redir, msg, tosend, flags);
|
||||
lock_sock(sk);
|
||||
if (unlikely(ret < 0)) {
|
||||
int free = sk_msg_free_nocharge(sk, msg);
|
||||
|
||||
if (!cork)
|
||||
*copied -= free;
|
||||
}
|
||||
if (cork) {
|
||||
sk_msg_free(sk, msg);
|
||||
kfree(msg);
|
||||
msg = NULL;
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
case __SK_DROP:
|
||||
default:
|
||||
sk_msg_free_partial(sk, msg, tosend);
|
||||
sk_msg_apply_bytes(psock, tosend);
|
||||
*copied -= tosend;
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
if (likely(!ret)) {
|
||||
if (!psock->apply_bytes) {
|
||||
psock->eval = __SK_NONE;
|
||||
if (psock->sk_redir) {
|
||||
sock_put(psock->sk_redir);
|
||||
psock->sk_redir = NULL;
|
||||
}
|
||||
}
|
||||
if (msg &&
|
||||
msg->sg.data[msg->sg.start].page_link &&
|
||||
msg->sg.data[msg->sg.start].length)
|
||||
goto more_data;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
|
||||
{
|
||||
struct sk_msg tmp, *msg_tx = NULL;
|
||||
int flags = msg->msg_flags | MSG_NO_SHARED_FRAGS;
|
||||
int copied = 0, err = 0;
|
||||
struct sk_psock *psock;
|
||||
long timeo;
|
||||
|
||||
psock = sk_psock_get(sk);
|
||||
if (unlikely(!psock))
|
||||
return tcp_sendmsg(sk, msg, size);
|
||||
|
||||
lock_sock(sk);
|
||||
timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
|
||||
while (msg_data_left(msg)) {
|
||||
bool enospc = false;
|
||||
u32 copy, osize;
|
||||
|
||||
if (sk->sk_err) {
|
||||
err = -sk->sk_err;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
copy = msg_data_left(msg);
|
||||
if (!sk_stream_memory_free(sk))
|
||||
goto wait_for_sndbuf;
|
||||
if (psock->cork) {
|
||||
msg_tx = psock->cork;
|
||||
} else {
|
||||
msg_tx = &tmp;
|
||||
sk_msg_init(msg_tx);
|
||||
}
|
||||
|
||||
osize = msg_tx->sg.size;
|
||||
err = sk_msg_alloc(sk, msg_tx, msg_tx->sg.size + copy, msg_tx->sg.end - 1);
|
||||
if (err) {
|
||||
if (err != -ENOSPC)
|
||||
goto wait_for_memory;
|
||||
enospc = true;
|
||||
copy = msg_tx->sg.size - osize;
|
||||
}
|
||||
|
||||
err = sk_msg_memcopy_from_iter(sk, &msg->msg_iter, msg_tx,
|
||||
copy);
|
||||
if (err < 0) {
|
||||
sk_msg_trim(sk, msg_tx, osize);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
copied += copy;
|
||||
if (psock->cork_bytes) {
|
||||
if (size > psock->cork_bytes)
|
||||
psock->cork_bytes = 0;
|
||||
else
|
||||
psock->cork_bytes -= size;
|
||||
if (psock->cork_bytes && !enospc)
|
||||
goto out_err;
|
||||
/* All cork bytes are accounted, rerun the prog. */
|
||||
psock->eval = __SK_NONE;
|
||||
psock->cork_bytes = 0;
|
||||
}
|
||||
|
||||
err = tcp_bpf_send_verdict(sk, psock, msg_tx, &copied, flags);
|
||||
if (unlikely(err < 0))
|
||||
goto out_err;
|
||||
continue;
|
||||
wait_for_sndbuf:
|
||||
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
|
||||
wait_for_memory:
|
||||
err = sk_stream_wait_memory(sk, &timeo);
|
||||
if (err) {
|
||||
if (msg_tx && msg_tx != psock->cork)
|
||||
sk_msg_free(sk, msg_tx);
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
out_err:
|
||||
if (err < 0)
|
||||
err = sk_stream_error(sk, msg->msg_flags, err);
|
||||
release_sock(sk);
|
||||
sk_psock_put(sk, psock);
|
||||
return copied ? copied : err;
|
||||
}
|
||||
|
||||
static int tcp_bpf_sendpage(struct sock *sk, struct page *page, int offset,
|
||||
size_t size, int flags)
|
||||
{
|
||||
struct sk_msg tmp, *msg = NULL;
|
||||
int err = 0, copied = 0;
|
||||
struct sk_psock *psock;
|
||||
bool enospc = false;
|
||||
|
||||
psock = sk_psock_get(sk);
|
||||
if (unlikely(!psock))
|
||||
return tcp_sendpage(sk, page, offset, size, flags);
|
||||
|
||||
lock_sock(sk);
|
||||
if (psock->cork) {
|
||||
msg = psock->cork;
|
||||
} else {
|
||||
msg = &tmp;
|
||||
sk_msg_init(msg);
|
||||
}
|
||||
|
||||
/* Catch case where ring is full and sendpage is stalled. */
|
||||
if (unlikely(sk_msg_full(msg)))
|
||||
goto out_err;
|
||||
|
||||
sk_msg_page_add(msg, page, size, offset);
|
||||
sk_mem_charge(sk, size);
|
||||
copied = size;
|
||||
if (sk_msg_full(msg))
|
||||
enospc = true;
|
||||
if (psock->cork_bytes) {
|
||||
if (size > psock->cork_bytes)
|
||||
psock->cork_bytes = 0;
|
||||
else
|
||||
psock->cork_bytes -= size;
|
||||
if (psock->cork_bytes && !enospc)
|
||||
goto out_err;
|
||||
/* All cork bytes are accounted, rerun the prog. */
|
||||
psock->eval = __SK_NONE;
|
||||
psock->cork_bytes = 0;
|
||||
}
|
||||
|
||||
err = tcp_bpf_send_verdict(sk, psock, msg, &copied, flags);
|
||||
out_err:
|
||||
release_sock(sk);
|
||||
sk_psock_put(sk, psock);
|
||||
return copied ? copied : err;
|
||||
}
|
||||
|
||||
static void tcp_bpf_remove(struct sock *sk, struct sk_psock *psock)
|
||||
{
|
||||
struct sk_psock_link *link;
|
||||
|
||||
sk_psock_cork_free(psock);
|
||||
__sk_psock_purge_ingress_msg(psock);
|
||||
while ((link = sk_psock_link_pop(psock))) {
|
||||
sk_psock_unlink(sk, link);
|
||||
sk_psock_free_link(link);
|
||||
}
|
||||
}
|
||||
|
||||
static void tcp_bpf_unhash(struct sock *sk)
|
||||
{
|
||||
void (*saved_unhash)(struct sock *sk);
|
||||
struct sk_psock *psock;
|
||||
|
||||
rcu_read_lock();
|
||||
psock = sk_psock(sk);
|
||||
if (unlikely(!psock)) {
|
||||
rcu_read_unlock();
|
||||
if (sk->sk_prot->unhash)
|
||||
sk->sk_prot->unhash(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
saved_unhash = psock->saved_unhash;
|
||||
tcp_bpf_remove(sk, psock);
|
||||
rcu_read_unlock();
|
||||
saved_unhash(sk);
|
||||
}
|
||||
|
||||
static void tcp_bpf_close(struct sock *sk, long timeout)
|
||||
{
|
||||
void (*saved_close)(struct sock *sk, long timeout);
|
||||
struct sk_psock *psock;
|
||||
|
||||
lock_sock(sk);
|
||||
rcu_read_lock();
|
||||
psock = sk_psock(sk);
|
||||
if (unlikely(!psock)) {
|
||||
rcu_read_unlock();
|
||||
release_sock(sk);
|
||||
return sk->sk_prot->close(sk, timeout);
|
||||
}
|
||||
|
||||
saved_close = psock->saved_close;
|
||||
tcp_bpf_remove(sk, psock);
|
||||
rcu_read_unlock();
|
||||
release_sock(sk);
|
||||
saved_close(sk, timeout);
|
||||
}
|
||||
|
||||
enum {
|
||||
TCP_BPF_IPV4,
|
||||
TCP_BPF_IPV6,
|
||||
TCP_BPF_NUM_PROTS,
|
||||
};
|
||||
|
||||
enum {
|
||||
TCP_BPF_BASE,
|
||||
TCP_BPF_TX,
|
||||
TCP_BPF_NUM_CFGS,
|
||||
};
|
||||
|
||||
static struct proto *tcpv6_prot_saved __read_mostly;
|
||||
static DEFINE_SPINLOCK(tcpv6_prot_lock);
|
||||
static struct proto tcp_bpf_prots[TCP_BPF_NUM_PROTS][TCP_BPF_NUM_CFGS];
|
||||
|
||||
static void tcp_bpf_rebuild_protos(struct proto prot[TCP_BPF_NUM_CFGS],
|
||||
struct proto *base)
|
||||
{
|
||||
prot[TCP_BPF_BASE] = *base;
|
||||
prot[TCP_BPF_BASE].unhash = tcp_bpf_unhash;
|
||||
prot[TCP_BPF_BASE].close = tcp_bpf_close;
|
||||
prot[TCP_BPF_BASE].recvmsg = tcp_bpf_recvmsg;
|
||||
prot[TCP_BPF_BASE].stream_memory_read = tcp_bpf_stream_read;
|
||||
|
||||
prot[TCP_BPF_TX] = prot[TCP_BPF_BASE];
|
||||
prot[TCP_BPF_TX].sendmsg = tcp_bpf_sendmsg;
|
||||
prot[TCP_BPF_TX].sendpage = tcp_bpf_sendpage;
|
||||
}
|
||||
|
||||
static void tcp_bpf_check_v6_needs_rebuild(struct sock *sk, struct proto *ops)
|
||||
{
|
||||
if (sk->sk_family == AF_INET6 &&
|
||||
unlikely(ops != smp_load_acquire(&tcpv6_prot_saved))) {
|
||||
spin_lock_bh(&tcpv6_prot_lock);
|
||||
if (likely(ops != tcpv6_prot_saved)) {
|
||||
tcp_bpf_rebuild_protos(tcp_bpf_prots[TCP_BPF_IPV6], ops);
|
||||
smp_store_release(&tcpv6_prot_saved, ops);
|
||||
}
|
||||
spin_unlock_bh(&tcpv6_prot_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init tcp_bpf_v4_build_proto(void)
|
||||
{
|
||||
tcp_bpf_rebuild_protos(tcp_bpf_prots[TCP_BPF_IPV4], &tcp_prot);
|
||||
return 0;
|
||||
}
|
||||
core_initcall(tcp_bpf_v4_build_proto);
|
||||
|
||||
static void tcp_bpf_update_sk_prot(struct sock *sk, struct sk_psock *psock)
|
||||
{
|
||||
int family = sk->sk_family == AF_INET6 ? TCP_BPF_IPV6 : TCP_BPF_IPV4;
|
||||
int config = psock->progs.msg_parser ? TCP_BPF_TX : TCP_BPF_BASE;
|
||||
|
||||
sk_psock_update_proto(sk, psock, &tcp_bpf_prots[family][config]);
|
||||
}
|
||||
|
||||
static void tcp_bpf_reinit_sk_prot(struct sock *sk, struct sk_psock *psock)
|
||||
{
|
||||
int family = sk->sk_family == AF_INET6 ? TCP_BPF_IPV6 : TCP_BPF_IPV4;
|
||||
int config = psock->progs.msg_parser ? TCP_BPF_TX : TCP_BPF_BASE;
|
||||
|
||||
/* Reinit occurs when program types change e.g. TCP_BPF_TX is removed
|
||||
* or added requiring sk_prot hook updates. We keep original saved
|
||||
* hooks in this case.
|
||||
*/
|
||||
sk->sk_prot = &tcp_bpf_prots[family][config];
|
||||
}
|
||||
|
||||
static int tcp_bpf_assert_proto_ops(struct proto *ops)
|
||||
{
|
||||
/* In order to avoid retpoline, we make assumptions when we call
|
||||
* into ops if e.g. a psock is not present. Make sure they are
|
||||
* indeed valid assumptions.
|
||||
*/
|
||||
return ops->recvmsg == tcp_recvmsg &&
|
||||
ops->sendmsg == tcp_sendmsg &&
|
||||
ops->sendpage == tcp_sendpage ? 0 : -ENOTSUPP;
|
||||
}
|
||||
|
||||
void tcp_bpf_reinit(struct sock *sk)
|
||||
{
|
||||
struct sk_psock *psock;
|
||||
|
||||
sock_owned_by_me(sk);
|
||||
|
||||
rcu_read_lock();
|
||||
psock = sk_psock(sk);
|
||||
tcp_bpf_reinit_sk_prot(sk, psock);
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
int tcp_bpf_init(struct sock *sk)
|
||||
{
|
||||
struct proto *ops = READ_ONCE(sk->sk_prot);
|
||||
struct sk_psock *psock;
|
||||
|
||||
sock_owned_by_me(sk);
|
||||
|
||||
rcu_read_lock();
|
||||
psock = sk_psock(sk);
|
||||
if (unlikely(!psock || psock->sk_proto ||
|
||||
tcp_bpf_assert_proto_ops(ops))) {
|
||||
rcu_read_unlock();
|
||||
return -EINVAL;
|
||||
}
|
||||
tcp_bpf_check_v6_needs_rebuild(sk, ops);
|
||||
tcp_bpf_update_sk_prot(sk, psock);
|
||||
rcu_read_unlock();
|
||||
return 0;
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include<linux/module.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
@ -29,18 +29,6 @@ static struct tcp_ulp_ops *tcp_ulp_find(const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct tcp_ulp_ops *tcp_ulp_find_id(const int ulp)
|
||||
{
|
||||
struct tcp_ulp_ops *e;
|
||||
|
||||
list_for_each_entry_rcu(e, &tcp_ulp_list, list) {
|
||||
if (e->uid == ulp)
|
||||
return e;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct tcp_ulp_ops *__tcp_ulp_find_autoload(const char *name)
|
||||
{
|
||||
const struct tcp_ulp_ops *ulp = NULL;
|
||||
@ -63,18 +51,6 @@ static const struct tcp_ulp_ops *__tcp_ulp_find_autoload(const char *name)
|
||||
return ulp;
|
||||
}
|
||||
|
||||
static const struct tcp_ulp_ops *__tcp_ulp_lookup(const int uid)
|
||||
{
|
||||
const struct tcp_ulp_ops *ulp;
|
||||
|
||||
rcu_read_lock();
|
||||
ulp = tcp_ulp_find_id(uid);
|
||||
if (!ulp || !try_module_get(ulp->owner))
|
||||
ulp = NULL;
|
||||
rcu_read_unlock();
|
||||
return ulp;
|
||||
}
|
||||
|
||||
/* Attach new upper layer protocol to the list
|
||||
* of available protocols.
|
||||
*/
|
||||
@ -123,6 +99,8 @@ void tcp_cleanup_ulp(struct sock *sk)
|
||||
{
|
||||
struct inet_connection_sock *icsk = inet_csk(sk);
|
||||
|
||||
sock_owned_by_me(sk);
|
||||
|
||||
if (!icsk->icsk_ulp_ops)
|
||||
return;
|
||||
|
||||
@ -133,54 +111,35 @@ void tcp_cleanup_ulp(struct sock *sk)
|
||||
icsk->icsk_ulp_ops = NULL;
|
||||
}
|
||||
|
||||
/* Change upper layer protocol for socket */
|
||||
int tcp_set_ulp(struct sock *sk, const char *name)
|
||||
static int __tcp_set_ulp(struct sock *sk, const struct tcp_ulp_ops *ulp_ops)
|
||||
{
|
||||
struct inet_connection_sock *icsk = inet_csk(sk);
|
||||
const struct tcp_ulp_ops *ulp_ops;
|
||||
int err = 0;
|
||||
int err;
|
||||
|
||||
err = -EEXIST;
|
||||
if (icsk->icsk_ulp_ops)
|
||||
return -EEXIST;
|
||||
goto out_err;
|
||||
|
||||
err = ulp_ops->init(sk);
|
||||
if (err)
|
||||
goto out_err;
|
||||
|
||||
icsk->icsk_ulp_ops = ulp_ops;
|
||||
return 0;
|
||||
out_err:
|
||||
module_put(ulp_ops->owner);
|
||||
return err;
|
||||
}
|
||||
|
||||
int tcp_set_ulp(struct sock *sk, const char *name)
|
||||
{
|
||||
const struct tcp_ulp_ops *ulp_ops;
|
||||
|
||||
sock_owned_by_me(sk);
|
||||
|
||||
ulp_ops = __tcp_ulp_find_autoload(name);
|
||||
if (!ulp_ops)
|
||||
return -ENOENT;
|
||||
|
||||
if (!ulp_ops->user_visible) {
|
||||
module_put(ulp_ops->owner);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
err = ulp_ops->init(sk);
|
||||
if (err) {
|
||||
module_put(ulp_ops->owner);
|
||||
return err;
|
||||
}
|
||||
|
||||
icsk->icsk_ulp_ops = ulp_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_set_ulp_id(struct sock *sk, int ulp)
|
||||
{
|
||||
struct inet_connection_sock *icsk = inet_csk(sk);
|
||||
const struct tcp_ulp_ops *ulp_ops;
|
||||
int err;
|
||||
|
||||
if (icsk->icsk_ulp_ops)
|
||||
return -EEXIST;
|
||||
|
||||
ulp_ops = __tcp_ulp_lookup(ulp);
|
||||
if (!ulp_ops)
|
||||
return -ENOENT;
|
||||
|
||||
err = ulp_ops->init(sk);
|
||||
if (err) {
|
||||
module_put(ulp_ops->owner);
|
||||
return err;
|
||||
}
|
||||
|
||||
icsk->icsk_ulp_ops = ulp_ops;
|
||||
return 0;
|
||||
return __tcp_set_ulp(sk, ulp_ops);
|
||||
}
|
||||
|
@ -901,6 +901,7 @@ static const struct ipv6_stub ipv6_stub_impl = {
|
||||
|
||||
static const struct ipv6_bpf_stub ipv6_bpf_stub_impl = {
|
||||
.inet6_bind = __inet6_bind,
|
||||
.udp6_lib_lookup = __udp6_lib_lookup,
|
||||
};
|
||||
|
||||
static int __init inet6_init(void)
|
||||
|
@ -1,4 +1,2 @@
|
||||
|
||||
config STREAM_PARSER
|
||||
tristate
|
||||
default n
|
||||
def_bool n
|
||||
|
@ -8,6 +8,7 @@ config TLS
|
||||
select CRYPTO_AES
|
||||
select CRYPTO_GCM
|
||||
select STREAM_PARSER
|
||||
select NET_SOCK_MSG
|
||||
default n
|
||||
---help---
|
||||
Enable kernel support for TLS protocol. This allows symmetric
|
||||
|
@ -421,7 +421,7 @@ last_record:
|
||||
tls_push_record_flags = flags;
|
||||
if (more) {
|
||||
tls_ctx->pending_open_record_frags =
|
||||
record->num_frags;
|
||||
!!record->num_frags;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -620,12 +620,14 @@ static void build_protos(struct proto prot[TLS_NUM_CONFIG][TLS_NUM_CONFIG],
|
||||
prot[TLS_SW][TLS_BASE].sendpage = tls_sw_sendpage;
|
||||
|
||||
prot[TLS_BASE][TLS_SW] = prot[TLS_BASE][TLS_BASE];
|
||||
prot[TLS_BASE][TLS_SW].recvmsg = tls_sw_recvmsg;
|
||||
prot[TLS_BASE][TLS_SW].close = tls_sk_proto_close;
|
||||
prot[TLS_BASE][TLS_SW].recvmsg = tls_sw_recvmsg;
|
||||
prot[TLS_BASE][TLS_SW].stream_memory_read = tls_sw_stream_read;
|
||||
prot[TLS_BASE][TLS_SW].close = tls_sk_proto_close;
|
||||
|
||||
prot[TLS_SW][TLS_SW] = prot[TLS_SW][TLS_BASE];
|
||||
prot[TLS_SW][TLS_SW].recvmsg = tls_sw_recvmsg;
|
||||
prot[TLS_SW][TLS_SW].close = tls_sk_proto_close;
|
||||
prot[TLS_SW][TLS_SW].recvmsg = tls_sw_recvmsg;
|
||||
prot[TLS_SW][TLS_SW].stream_memory_read = tls_sw_stream_read;
|
||||
prot[TLS_SW][TLS_SW].close = tls_sk_proto_close;
|
||||
|
||||
#ifdef CONFIG_TLS_DEVICE
|
||||
prot[TLS_HW][TLS_BASE] = prot[TLS_BASE][TLS_BASE];
|
||||
@ -724,7 +726,6 @@ static int __init tls_register(void)
|
||||
build_protos(tls_prots[TLSV4], &tcp_prot);
|
||||
|
||||
tls_sw_proto_ops = inet_stream_ops;
|
||||
tls_sw_proto_ops.poll = tls_sw_poll;
|
||||
tls_sw_proto_ops.splice_read = tls_sw_splice_read;
|
||||
|
||||
#ifdef CONFIG_TLS_DEVICE
|
||||
|
912
net/tls/tls_sw.c
912
net/tls/tls_sw.c
File diff suppressed because it is too large
Load Diff
@ -15,13 +15,15 @@ SYNOPSIS
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-f** | **--bpffs** } }
|
||||
|
||||
*COMMANDS* :=
|
||||
{ **show** | **list** | **dump** | **update** | **lookup** | **getnext** | **delete**
|
||||
| **pin** | **help** }
|
||||
{ **show** | **list** | **create** | **dump** | **update** | **lookup** | **getnext**
|
||||
| **delete** | **pin** | **help** }
|
||||
|
||||
MAP COMMANDS
|
||||
=============
|
||||
|
||||
| **bpftool** **map { show | list }** [*MAP*]
|
||||
| **bpftool** **map create** *FILE* **type** *TYPE* **key** *KEY_SIZE* **value** *VALUE_SIZE* \
|
||||
| **entries** *MAX_ENTRIES* **name** *NAME* [**flags** *FLAGS*] [**dev** *NAME*]
|
||||
| **bpftool** **map dump** *MAP*
|
||||
| **bpftool** **map update** *MAP* **key** *DATA* **value** *VALUE* [*UPDATE_FLAGS*]
|
||||
| **bpftool** **map lookup** *MAP* **key** *DATA*
|
||||
@ -36,6 +38,11 @@ MAP COMMANDS
|
||||
| *PROG* := { **id** *PROG_ID* | **pinned** *FILE* | **tag** *PROG_TAG* }
|
||||
| *VALUE* := { *DATA* | *MAP* | *PROG* }
|
||||
| *UPDATE_FLAGS* := { **any** | **exist** | **noexist** }
|
||||
| *TYPE* := { **hash** | **array** | **prog_array** | **perf_event_array** | **percpu_hash**
|
||||
| | **percpu_array** | **stack_trace** | **cgroup_array** | **lru_hash**
|
||||
| | **lru_percpu_hash** | **lpm_trie** | **array_of_maps** | **hash_of_maps**
|
||||
| | **devmap** | **sockmap** | **cpumap** | **xskmap** | **sockhash**
|
||||
| | **cgroup_storage** | **reuseport_sockarray** | **percpu_cgroup_storage** }
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
@ -47,6 +54,10 @@ DESCRIPTION
|
||||
Output will start with map ID followed by map type and
|
||||
zero or more named attributes (depending on kernel version).
|
||||
|
||||
**bpftool map create** *FILE* **type** *TYPE* **key** *KEY_SIZE* **value** *VALUE_SIZE* **entries** *MAX_ENTRIES* **name** *NAME* [**flags** *FLAGS*] [**dev** *NAME*]
|
||||
Create a new map with given parameters and pin it to *bpffs*
|
||||
as *FILE*.
|
||||
|
||||
**bpftool map dump** *MAP*
|
||||
Dump all entries in a given *MAP*.
|
||||
|
||||
|
@ -25,6 +25,8 @@ MAP COMMANDS
|
||||
| **bpftool** **prog dump jited** *PROG* [{**file** *FILE* | **opcodes**}]
|
||||
| **bpftool** **prog pin** *PROG* *FILE*
|
||||
| **bpftool** **prog load** *OBJ* *FILE* [**type** *TYPE*] [**map** {**idx** *IDX* | **name** *NAME*} *MAP*] [**dev** *NAME*]
|
||||
| **bpftool** **prog attach** *PROG* *ATTACH_TYPE* *MAP*
|
||||
| **bpftool** **prog detach** *PROG* *ATTACH_TYPE* *MAP*
|
||||
| **bpftool** **prog help**
|
||||
|
|
||||
| *MAP* := { **id** *MAP_ID* | **pinned** *FILE* }
|
||||
@ -37,6 +39,7 @@ MAP COMMANDS
|
||||
| **cgroup/bind4** | **cgroup/bind6** | **cgroup/post_bind4** | **cgroup/post_bind6** |
|
||||
| **cgroup/connect4** | **cgroup/connect6** | **cgroup/sendmsg4** | **cgroup/sendmsg6**
|
||||
| }
|
||||
| *ATTACH_TYPE* := { **msg_verdict** | **skb_verdict** | **skb_parse** }
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
@ -90,6 +93,14 @@ DESCRIPTION
|
||||
|
||||
Note: *FILE* must be located in *bpffs* mount.
|
||||
|
||||
**bpftool prog attach** *PROG* *ATTACH_TYPE* *MAP*
|
||||
Attach bpf program *PROG* (with type specified by *ATTACH_TYPE*)
|
||||
to the map *MAP*.
|
||||
|
||||
**bpftool prog detach** *PROG* *ATTACH_TYPE* *MAP*
|
||||
Detach bpf program *PROG* (with type specified by *ATTACH_TYPE*)
|
||||
from the map *MAP*.
|
||||
|
||||
**bpftool prog help**
|
||||
Print short help message.
|
||||
|
||||
|
@ -22,11 +22,11 @@ SYNOPSIS
|
||||
| { **-j** | **--json** } [{ **-p** | **--pretty** }] }
|
||||
|
||||
*MAP-COMMANDS* :=
|
||||
{ **show** | **list** | **dump** | **update** | **lookup** | **getnext** | **delete**
|
||||
| **pin** | **event_pipe** | **help** }
|
||||
{ **show** | **list** | **create** | **dump** | **update** | **lookup** | **getnext**
|
||||
| **delete** | **pin** | **event_pipe** | **help** }
|
||||
|
||||
*PROG-COMMANDS* := { **show** | **list** | **dump jited** | **dump xlated** | **pin**
|
||||
| **load** | **help** }
|
||||
| **load** | **attach** | **detach** | **help** }
|
||||
|
||||
*CGROUP-COMMANDS* := { **show** | **list** | **attach** | **detach** | **help** }
|
||||
|
||||
@ -57,6 +57,10 @@ OPTIONS
|
||||
-p, --pretty
|
||||
Generate human-readable JSON output. Implies **-j**.
|
||||
|
||||
-m, --mapcompat
|
||||
Allow loading maps with unknown map definitions.
|
||||
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
**bpftool-map**\ (8), **bpftool-prog**\ (8), **bpftool-cgroup**\ (8)
|
||||
|
@ -46,6 +46,13 @@ CFLAGS += -DPACKAGE='"bpftool"' -D__EXPORTED_HEADERS__ \
|
||||
-I$(srctree)/tools/lib/bpf \
|
||||
-I$(srctree)/tools/perf
|
||||
CFLAGS += -DBPFTOOL_VERSION='"$(BPFTOOL_VERSION)"'
|
||||
ifneq ($(EXTRA_CFLAGS),)
|
||||
CFLAGS += $(EXTRA_CFLAGS)
|
||||
endif
|
||||
ifneq ($(EXTRA_LDFLAGS),)
|
||||
LDFLAGS += $(EXTRA_LDFLAGS)
|
||||
endif
|
||||
|
||||
LIBS = -lelf -lbfd -lopcodes $(LIBBPF)
|
||||
|
||||
INSTALL ?= install
|
||||
@ -90,7 +97,7 @@ $(OUTPUT)disasm.o: $(srctree)/kernel/bpf/disasm.c
|
||||
$(QUIET_CC)$(COMPILE.c) -MMD -o $@ $<
|
||||
|
||||
$(OUTPUT)bpftool: $(OBJS) $(LIBBPF)
|
||||
$(QUIET_LINK)$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
|
||||
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||
|
||||
$(OUTPUT)%.o: %.c
|
||||
$(QUIET_CC)$(COMPILE.c) -MMD -o $@ $<
|
||||
|
@ -184,7 +184,7 @@ _bpftool()
|
||||
|
||||
# Deal with options
|
||||
if [[ ${words[cword]} == -* ]]; then
|
||||
local c='--version --json --pretty --bpffs'
|
||||
local c='--version --json --pretty --bpffs --mapcompat'
|
||||
COMPREPLY=( $( compgen -W "$c" -- "$cur" ) )
|
||||
return 0
|
||||
fi
|
||||
@ -292,6 +292,23 @@ _bpftool()
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
attach|detach)
|
||||
if [[ ${#words[@]} == 7 ]]; then
|
||||
COMPREPLY=( $( compgen -W "id pinned" -- "$cur" ) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ${#words[@]} == 6 ]]; then
|
||||
COMPREPLY=( $( compgen -W "msg_verdict skb_verdict skb_parse" -- "$cur" ) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ $prev == "$command" ]]; then
|
||||
COMPREPLY=( $( compgen -W "id pinned" -- "$cur" ) )
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
load)
|
||||
local obj
|
||||
|
||||
@ -347,7 +364,7 @@ _bpftool()
|
||||
;;
|
||||
*)
|
||||
[[ $prev == $object ]] && \
|
||||
COMPREPLY=( $( compgen -W 'dump help pin load \
|
||||
COMPREPLY=( $( compgen -W 'dump help pin attach detach load \
|
||||
show list' -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
@ -370,6 +387,42 @@ _bpftool()
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
create)
|
||||
case $prev in
|
||||
$command)
|
||||
_filedir
|
||||
return 0
|
||||
;;
|
||||
type)
|
||||
COMPREPLY=( $( compgen -W 'hash array prog_array \
|
||||
perf_event_array percpu_hash percpu_array \
|
||||
stack_trace cgroup_array lru_hash \
|
||||
lru_percpu_hash lpm_trie array_of_maps \
|
||||
hash_of_maps devmap sockmap cpumap xskmap \
|
||||
sockhash cgroup_storage reuseport_sockarray \
|
||||
percpu_cgroup_storage' -- \
|
||||
"$cur" ) )
|
||||
return 0
|
||||
;;
|
||||
key|value|flags|name|entries)
|
||||
return 0
|
||||
;;
|
||||
dev)
|
||||
_sysfs_get_netdevs
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
_bpftool_once_attr 'type'
|
||||
_bpftool_once_attr 'key'
|
||||
_bpftool_once_attr 'value'
|
||||
_bpftool_once_attr 'entries'
|
||||
_bpftool_once_attr 'name'
|
||||
_bpftool_once_attr 'flags'
|
||||
_bpftool_once_attr 'dev'
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
lookup|getnext|delete)
|
||||
case $prev in
|
||||
$command)
|
||||
@ -483,7 +536,7 @@ _bpftool()
|
||||
*)
|
||||
[[ $prev == $object ]] && \
|
||||
COMPREPLY=( $( compgen -W 'delete dump getnext help \
|
||||
lookup pin event_pipe show list update' -- \
|
||||
lookup pin event_pipe show list update create' -- \
|
||||
"$cur" ) )
|
||||
;;
|
||||
esac
|
||||
|
@ -618,3 +618,24 @@ void print_dev_json(__u32 ifindex, __u64 ns_dev, __u64 ns_inode)
|
||||
jsonw_string_field(json_wtr, "ifname", name);
|
||||
jsonw_end_object(json_wtr);
|
||||
}
|
||||
|
||||
int parse_u32_arg(int *argc, char ***argv, __u32 *val, const char *what)
|
||||
{
|
||||
char *endptr;
|
||||
|
||||
NEXT_ARGP();
|
||||
|
||||
if (*val) {
|
||||
p_err("%s already specified", what);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*val = strtoul(**argv, &endptr, 0);
|
||||
if (*endptr) {
|
||||
p_err("can't parse %s as %s", **argv, what);
|
||||
return -1;
|
||||
}
|
||||
NEXT_ARGP();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ json_writer_t *json_wtr;
|
||||
bool pretty_output;
|
||||
bool json_output;
|
||||
bool show_pinned;
|
||||
int bpf_flags;
|
||||
struct pinned_obj_table prog_table;
|
||||
struct pinned_obj_table map_table;
|
||||
|
||||
@ -341,6 +342,7 @@ int main(int argc, char **argv)
|
||||
{ "pretty", no_argument, NULL, 'p' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "bpffs", no_argument, NULL, 'f' },
|
||||
{ "mapcompat", no_argument, NULL, 'm' },
|
||||
{ 0 }
|
||||
};
|
||||
int opt, ret;
|
||||
@ -355,7 +357,7 @@ int main(int argc, char **argv)
|
||||
hash_init(map_table.table);
|
||||
|
||||
opterr = 0;
|
||||
while ((opt = getopt_long(argc, argv, "Vhpjf",
|
||||
while ((opt = getopt_long(argc, argv, "Vhpjfm",
|
||||
options, NULL)) >= 0) {
|
||||
switch (opt) {
|
||||
case 'V':
|
||||
@ -379,6 +381,9 @@ int main(int argc, char **argv)
|
||||
case 'f':
|
||||
show_pinned = true;
|
||||
break;
|
||||
case 'm':
|
||||
bpf_flags = MAPS_RELAX_COMPAT;
|
||||
break;
|
||||
default:
|
||||
p_err("unrecognized option '%s'", argv[optind - 1]);
|
||||
if (json_output)
|
||||
|
@ -74,7 +74,7 @@
|
||||
#define HELP_SPEC_PROGRAM \
|
||||
"PROG := { id PROG_ID | pinned FILE | tag PROG_TAG }"
|
||||
#define HELP_SPEC_OPTIONS \
|
||||
"OPTIONS := { {-j|--json} [{-p|--pretty}] | {-f|--bpffs} }"
|
||||
"OPTIONS := { {-j|--json} [{-p|--pretty}] | {-f|--bpffs} | {-m|--mapcompat}"
|
||||
#define HELP_SPEC_MAP \
|
||||
"MAP := { id MAP_ID | pinned FILE }"
|
||||
|
||||
@ -89,6 +89,7 @@ extern const char *bin_name;
|
||||
extern json_writer_t *json_wtr;
|
||||
extern bool json_output;
|
||||
extern bool show_pinned;
|
||||
extern int bpf_flags;
|
||||
extern struct pinned_obj_table prog_table;
|
||||
extern struct pinned_obj_table map_table;
|
||||
|
||||
@ -138,6 +139,7 @@ int do_cgroup(int argc, char **arg);
|
||||
int do_perf(int argc, char **arg);
|
||||
int do_net(int argc, char **arg);
|
||||
|
||||
int parse_u32_arg(int *argc, char ***argv, __u32 *val, const char *what);
|
||||
int prog_parse_fd(int *argc, char ***argv);
|
||||
int map_parse_fd(int *argc, char ***argv);
|
||||
int map_parse_fd_and_info(int *argc, char ***argv, void *info, __u32 *info_len);
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <net/if.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -94,6 +95,17 @@ static bool map_is_map_of_progs(__u32 type)
|
||||
return type == BPF_MAP_TYPE_PROG_ARRAY;
|
||||
}
|
||||
|
||||
static int map_type_from_str(const char *type)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(map_type_name); i++)
|
||||
/* Don't allow prefixing in case of possible future shadowing */
|
||||
if (map_type_name[i] && !strcmp(map_type_name[i], type))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void *alloc_value(struct bpf_map_info *info)
|
||||
{
|
||||
if (map_is_per_cpu(info->type))
|
||||
@ -336,6 +348,25 @@ static void print_entry_json(struct bpf_map_info *info, unsigned char *key,
|
||||
jsonw_end_object(json_wtr);
|
||||
}
|
||||
|
||||
static void print_entry_error(struct bpf_map_info *info, unsigned char *key,
|
||||
const char *value)
|
||||
{
|
||||
int value_size = strlen(value);
|
||||
bool single_line, break_names;
|
||||
|
||||
break_names = info->key_size > 16 || value_size > 16;
|
||||
single_line = info->key_size + value_size <= 24 && !break_names;
|
||||
|
||||
printf("key:%c", break_names ? '\n' : ' ');
|
||||
fprint_hex(stdout, key, info->key_size, " ");
|
||||
|
||||
printf(single_line ? " " : "\n");
|
||||
|
||||
printf("value:%c%s", break_names ? '\n' : ' ', value);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void print_entry_plain(struct bpf_map_info *info, unsigned char *key,
|
||||
unsigned char *value)
|
||||
{
|
||||
@ -658,6 +689,54 @@ static int do_show(int argc, char **argv)
|
||||
return errno == ENOENT ? 0 : -1;
|
||||
}
|
||||
|
||||
static int dump_map_elem(int fd, void *key, void *value,
|
||||
struct bpf_map_info *map_info, struct btf *btf,
|
||||
json_writer_t *btf_wtr)
|
||||
{
|
||||
int num_elems = 0;
|
||||
int lookup_errno;
|
||||
|
||||
if (!bpf_map_lookup_elem(fd, key, value)) {
|
||||
if (json_output) {
|
||||
print_entry_json(map_info, key, value, btf);
|
||||
} else {
|
||||
if (btf) {
|
||||
struct btf_dumper d = {
|
||||
.btf = btf,
|
||||
.jw = btf_wtr,
|
||||
.is_plain_text = true,
|
||||
};
|
||||
|
||||
do_dump_btf(&d, map_info, key, value);
|
||||
} else {
|
||||
print_entry_plain(map_info, key, value);
|
||||
}
|
||||
num_elems++;
|
||||
}
|
||||
return num_elems;
|
||||
}
|
||||
|
||||
/* lookup error handling */
|
||||
lookup_errno = errno;
|
||||
|
||||
if (map_is_map_of_maps(map_info->type) ||
|
||||
map_is_map_of_progs(map_info->type))
|
||||
return 0;
|
||||
|
||||
if (json_output) {
|
||||
jsonw_name(json_wtr, "key");
|
||||
print_hex_data_json(key, map_info->key_size);
|
||||
jsonw_name(json_wtr, "value");
|
||||
jsonw_start_object(json_wtr);
|
||||
jsonw_string_field(json_wtr, "error", strerror(lookup_errno));
|
||||
jsonw_end_object(json_wtr);
|
||||
} else {
|
||||
print_entry_error(map_info, key, strerror(lookup_errno));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_dump(int argc, char **argv)
|
||||
{
|
||||
struct bpf_map_info info = {};
|
||||
@ -713,40 +792,7 @@ static int do_dump(int argc, char **argv)
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bpf_map_lookup_elem(fd, key, value)) {
|
||||
if (json_output)
|
||||
print_entry_json(&info, key, value, btf);
|
||||
else
|
||||
if (btf) {
|
||||
struct btf_dumper d = {
|
||||
.btf = btf,
|
||||
.jw = btf_wtr,
|
||||
.is_plain_text = true,
|
||||
};
|
||||
|
||||
do_dump_btf(&d, &info, key, value);
|
||||
} else {
|
||||
print_entry_plain(&info, key, value);
|
||||
}
|
||||
num_elems++;
|
||||
} else if (!map_is_map_of_maps(info.type) &&
|
||||
!map_is_map_of_progs(info.type)) {
|
||||
if (json_output) {
|
||||
jsonw_name(json_wtr, "key");
|
||||
print_hex_data_json(key, info.key_size);
|
||||
jsonw_name(json_wtr, "value");
|
||||
jsonw_start_object(json_wtr);
|
||||
jsonw_string_field(json_wtr, "error",
|
||||
"can't lookup element");
|
||||
jsonw_end_object(json_wtr);
|
||||
} else {
|
||||
p_info("can't lookup element with key: ");
|
||||
fprint_hex(stderr, key, info.key_size, " ");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
num_elems += dump_map_elem(fd, key, value, &info, btf, btf_wtr);
|
||||
prev_key = key;
|
||||
}
|
||||
|
||||
@ -1024,6 +1070,92 @@ static int do_pin(int argc, char **argv)
|
||||
return err;
|
||||
}
|
||||
|
||||
static int do_create(int argc, char **argv)
|
||||
{
|
||||
struct bpf_create_map_attr attr = { NULL, };
|
||||
const char *pinfile;
|
||||
int err, fd;
|
||||
|
||||
if (!REQ_ARGS(7))
|
||||
return -1;
|
||||
pinfile = GET_ARG();
|
||||
|
||||
while (argc) {
|
||||
if (!REQ_ARGS(2))
|
||||
return -1;
|
||||
|
||||
if (is_prefix(*argv, "type")) {
|
||||
NEXT_ARG();
|
||||
|
||||
if (attr.map_type) {
|
||||
p_err("map type already specified");
|
||||
return -1;
|
||||
}
|
||||
|
||||
attr.map_type = map_type_from_str(*argv);
|
||||
if ((int)attr.map_type < 0) {
|
||||
p_err("unrecognized map type: %s", *argv);
|
||||
return -1;
|
||||
}
|
||||
NEXT_ARG();
|
||||
} else if (is_prefix(*argv, "name")) {
|
||||
NEXT_ARG();
|
||||
attr.name = GET_ARG();
|
||||
} else if (is_prefix(*argv, "key")) {
|
||||
if (parse_u32_arg(&argc, &argv, &attr.key_size,
|
||||
"key size"))
|
||||
return -1;
|
||||
} else if (is_prefix(*argv, "value")) {
|
||||
if (parse_u32_arg(&argc, &argv, &attr.value_size,
|
||||
"value size"))
|
||||
return -1;
|
||||
} else if (is_prefix(*argv, "entries")) {
|
||||
if (parse_u32_arg(&argc, &argv, &attr.max_entries,
|
||||
"max entries"))
|
||||
return -1;
|
||||
} else if (is_prefix(*argv, "flags")) {
|
||||
if (parse_u32_arg(&argc, &argv, &attr.map_flags,
|
||||
"flags"))
|
||||
return -1;
|
||||
} else if (is_prefix(*argv, "dev")) {
|
||||
NEXT_ARG();
|
||||
|
||||
if (attr.map_ifindex) {
|
||||
p_err("offload device already specified");
|
||||
return -1;
|
||||
}
|
||||
|
||||
attr.map_ifindex = if_nametoindex(*argv);
|
||||
if (!attr.map_ifindex) {
|
||||
p_err("unrecognized netdevice '%s': %s",
|
||||
*argv, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
NEXT_ARG();
|
||||
}
|
||||
}
|
||||
|
||||
if (!attr.name) {
|
||||
p_err("map name not specified");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = bpf_create_map_xattr(&attr);
|
||||
if (fd < 0) {
|
||||
p_err("map create failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = do_pin_fd(fd, pinfile);
|
||||
close(fd);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (json_output)
|
||||
jsonw_null(json_wtr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_help(int argc, char **argv)
|
||||
{
|
||||
if (json_output) {
|
||||
@ -1033,6 +1165,9 @@ static int do_help(int argc, char **argv)
|
||||
|
||||
fprintf(stderr,
|
||||
"Usage: %s %s { show | list } [MAP]\n"
|
||||
" %s %s create FILE type TYPE key KEY_SIZE value VALUE_SIZE \\\n"
|
||||
" entries MAX_ENTRIES name NAME [flags FLAGS] \\\n"
|
||||
" [dev NAME]\n"
|
||||
" %s %s dump MAP\n"
|
||||
" %s %s update MAP key DATA value VALUE [UPDATE_FLAGS]\n"
|
||||
" %s %s lookup MAP key DATA\n"
|
||||
@ -1047,11 +1182,17 @@ static int do_help(int argc, char **argv)
|
||||
" " HELP_SPEC_PROGRAM "\n"
|
||||
" VALUE := { DATA | MAP | PROG }\n"
|
||||
" UPDATE_FLAGS := { any | exist | noexist }\n"
|
||||
" TYPE := { hash | array | prog_array | perf_event_array | percpu_hash |\n"
|
||||
" percpu_array | stack_trace | cgroup_array | lru_hash |\n"
|
||||
" lru_percpu_hash | lpm_trie | array_of_maps | hash_of_maps |\n"
|
||||
" devmap | sockmap | cpumap | xskmap | sockhash |\n"
|
||||
" cgroup_storage | reuseport_sockarray | percpu_cgroup_storage }\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
"",
|
||||
bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2],
|
||||
bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2],
|
||||
bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2]);
|
||||
bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2],
|
||||
bin_name, argv[-2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1067,6 +1208,7 @@ static const struct cmd cmds[] = {
|
||||
{ "delete", do_delete },
|
||||
{ "pin", do_pin },
|
||||
{ "event_pipe", do_event_pipe },
|
||||
{ "create", do_create },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
@ -77,6 +77,26 @@ static const char * const prog_type_name[] = {
|
||||
[BPF_PROG_TYPE_FLOW_DISSECTOR] = "flow_dissector",
|
||||
};
|
||||
|
||||
static const char * const attach_type_strings[] = {
|
||||
[BPF_SK_SKB_STREAM_PARSER] = "stream_parser",
|
||||
[BPF_SK_SKB_STREAM_VERDICT] = "stream_verdict",
|
||||
[BPF_SK_MSG_VERDICT] = "msg_verdict",
|
||||
[__MAX_BPF_ATTACH_TYPE] = NULL,
|
||||
};
|
||||
|
||||
enum bpf_attach_type parse_attach_type(const char *str)
|
||||
{
|
||||
enum bpf_attach_type type;
|
||||
|
||||
for (type = 0; type < __MAX_BPF_ATTACH_TYPE; type++) {
|
||||
if (attach_type_strings[type] &&
|
||||
is_prefix(str, attach_type_strings[type]))
|
||||
return type;
|
||||
}
|
||||
|
||||
return __MAX_BPF_ATTACH_TYPE;
|
||||
}
|
||||
|
||||
static void print_boot_time(__u64 nsecs, char *buf, unsigned int size)
|
||||
{
|
||||
struct timespec real_time_ts, boot_time_ts;
|
||||
@ -697,6 +717,77 @@ int map_replace_compar(const void *p1, const void *p2)
|
||||
return a->idx - b->idx;
|
||||
}
|
||||
|
||||
static int do_attach(int argc, char **argv)
|
||||
{
|
||||
enum bpf_attach_type attach_type;
|
||||
int err, mapfd, progfd;
|
||||
|
||||
if (!REQ_ARGS(5)) {
|
||||
p_err("too few parameters for map attach");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
progfd = prog_parse_fd(&argc, &argv);
|
||||
if (progfd < 0)
|
||||
return progfd;
|
||||
|
||||
attach_type = parse_attach_type(*argv);
|
||||
if (attach_type == __MAX_BPF_ATTACH_TYPE) {
|
||||
p_err("invalid attach type");
|
||||
return -EINVAL;
|
||||
}
|
||||
NEXT_ARG();
|
||||
|
||||
mapfd = map_parse_fd(&argc, &argv);
|
||||
if (mapfd < 0)
|
||||
return mapfd;
|
||||
|
||||
err = bpf_prog_attach(progfd, mapfd, attach_type, 0);
|
||||
if (err) {
|
||||
p_err("failed prog attach to map");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (json_output)
|
||||
jsonw_null(json_wtr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_detach(int argc, char **argv)
|
||||
{
|
||||
enum bpf_attach_type attach_type;
|
||||
int err, mapfd, progfd;
|
||||
|
||||
if (!REQ_ARGS(5)) {
|
||||
p_err("too few parameters for map detach");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
progfd = prog_parse_fd(&argc, &argv);
|
||||
if (progfd < 0)
|
||||
return progfd;
|
||||
|
||||
attach_type = parse_attach_type(*argv);
|
||||
if (attach_type == __MAX_BPF_ATTACH_TYPE) {
|
||||
p_err("invalid attach type");
|
||||
return -EINVAL;
|
||||
}
|
||||
NEXT_ARG();
|
||||
|
||||
mapfd = map_parse_fd(&argc, &argv);
|
||||
if (mapfd < 0)
|
||||
return mapfd;
|
||||
|
||||
err = bpf_prog_detach2(progfd, mapfd, attach_type);
|
||||
if (err) {
|
||||
p_err("failed prog detach from map");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (json_output)
|
||||
jsonw_null(json_wtr);
|
||||
return 0;
|
||||
}
|
||||
static int do_load(int argc, char **argv)
|
||||
{
|
||||
enum bpf_attach_type expected_attach_type;
|
||||
@ -817,7 +908,7 @@ static int do_load(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
obj = bpf_object__open_xattr(&attr);
|
||||
obj = __bpf_object__open_xattr(&attr, bpf_flags);
|
||||
if (IS_ERR_OR_NULL(obj)) {
|
||||
p_err("failed to open object file");
|
||||
goto err_free_reuse_maps;
|
||||
@ -942,6 +1033,8 @@ static int do_help(int argc, char **argv)
|
||||
" %s %s pin PROG FILE\n"
|
||||
" %s %s load OBJ FILE [type TYPE] [dev NAME] \\\n"
|
||||
" [map { idx IDX | name NAME } MAP]\n"
|
||||
" %s %s attach PROG ATTACH_TYPE MAP\n"
|
||||
" %s %s detach PROG ATTACH_TYPE MAP\n"
|
||||
" %s %s help\n"
|
||||
"\n"
|
||||
" " HELP_SPEC_MAP "\n"
|
||||
@ -953,10 +1046,12 @@ static int do_help(int argc, char **argv)
|
||||
" cgroup/bind4 | cgroup/bind6 | cgroup/post_bind4 |\n"
|
||||
" cgroup/post_bind6 | cgroup/connect4 | cgroup/connect6 |\n"
|
||||
" cgroup/sendmsg4 | cgroup/sendmsg6 }\n"
|
||||
" ATTACH_TYPE := { msg_verdict | skb_verdict | skb_parse }\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
"",
|
||||
bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2],
|
||||
bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2]);
|
||||
bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2],
|
||||
bin_name, argv[-2], bin_name, argv[-2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -968,6 +1063,8 @@ static const struct cmd cmds[] = {
|
||||
{ "dump", do_dump },
|
||||
{ "pin", do_pin },
|
||||
{ "load", do_load },
|
||||
{ "attach", do_attach },
|
||||
{ "detach", do_detach },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
@ -69,7 +69,7 @@ FEATURE_USER = .libbpf
|
||||
FEATURE_TESTS = libelf libelf-mmap bpf reallocarray
|
||||
FEATURE_DISPLAY = libelf bpf
|
||||
|
||||
INCLUDES = -I. -I$(srctree)/tools/include -I$(srctree)/tools/arch/$(ARCH)/include/uapi -I$(srctree)/tools/include/uapi -I$(srctree)/tools/perf
|
||||
INCLUDES = -I. -I$(srctree)/tools/include -I$(srctree)/tools/arch/$(ARCH)/include/uapi -I$(srctree)/tools/include/uapi
|
||||
FEATURE_CHECK_CFLAGS-bpf = $(INCLUDES)
|
||||
|
||||
check_feat := 1
|
||||
|
@ -69,6 +69,9 @@ struct bpf_load_program_attr {
|
||||
__u32 prog_ifindex;
|
||||
};
|
||||
|
||||
/* Flags to direct loading requirements */
|
||||
#define MAPS_RELAX_COMPAT 0x01
|
||||
|
||||
/* Recommend log buffer size */
|
||||
#define BPF_LOG_BUF_SIZE (256 * 1024)
|
||||
int bpf_load_program_xattr(const struct bpf_load_program_attr *load_attr,
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <perf-sys.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
@ -27,6 +26,7 @@
|
||||
#include <linux/btf.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/vfs.h>
|
||||
@ -169,7 +169,7 @@ static LIST_HEAD(bpf_objects_list);
|
||||
|
||||
struct bpf_object {
|
||||
char license[64];
|
||||
u32 kern_version;
|
||||
__u32 kern_version;
|
||||
|
||||
struct bpf_program *programs;
|
||||
size_t nr_programs;
|
||||
@ -540,7 +540,7 @@ static int
|
||||
bpf_object__init_kversion(struct bpf_object *obj,
|
||||
void *data, size_t size)
|
||||
{
|
||||
u32 kver;
|
||||
__u32 kver;
|
||||
|
||||
if (size != sizeof(kver)) {
|
||||
pr_warning("invalid kver section in %s\n", obj->path);
|
||||
@ -562,8 +562,9 @@ static int compare_bpf_map(const void *_a, const void *_b)
|
||||
}
|
||||
|
||||
static int
|
||||
bpf_object__init_maps(struct bpf_object *obj)
|
||||
bpf_object__init_maps(struct bpf_object *obj, int flags)
|
||||
{
|
||||
bool strict = !(flags & MAPS_RELAX_COMPAT);
|
||||
int i, map_idx, map_def_sz, nr_maps = 0;
|
||||
Elf_Scn *scn;
|
||||
Elf_Data *data;
|
||||
@ -685,7 +686,8 @@ bpf_object__init_maps(struct bpf_object *obj)
|
||||
"has unrecognized, non-zero "
|
||||
"options\n",
|
||||
obj->path, map_name);
|
||||
return -EINVAL;
|
||||
if (strict)
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
memcpy(&obj->maps[map_idx].def, def,
|
||||
@ -716,7 +718,7 @@ static bool section_have_execinstr(struct bpf_object *obj, int idx)
|
||||
return false;
|
||||
}
|
||||
|
||||
static int bpf_object__elf_collect(struct bpf_object *obj)
|
||||
static int bpf_object__elf_collect(struct bpf_object *obj, int flags)
|
||||
{
|
||||
Elf *elf = obj->efile.elf;
|
||||
GElf_Ehdr *ep = &obj->efile.ehdr;
|
||||
@ -843,7 +845,7 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
|
||||
return LIBBPF_ERRNO__FORMAT;
|
||||
}
|
||||
if (obj->efile.maps_shndx >= 0) {
|
||||
err = bpf_object__init_maps(obj);
|
||||
err = bpf_object__init_maps(obj, flags);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
@ -1295,7 +1297,7 @@ static int bpf_object__collect_reloc(struct bpf_object *obj)
|
||||
static int
|
||||
load_program(enum bpf_prog_type type, enum bpf_attach_type expected_attach_type,
|
||||
const char *name, struct bpf_insn *insns, int insns_cnt,
|
||||
char *license, u32 kern_version, int *pfd, int prog_ifindex)
|
||||
char *license, __u32 kern_version, int *pfd, int prog_ifindex)
|
||||
{
|
||||
struct bpf_load_program_attr load_attr;
|
||||
char *cp, errmsg[STRERR_BUFSIZE];
|
||||
@ -1515,7 +1517,7 @@ static int bpf_object__validate(struct bpf_object *obj, bool needs_kver)
|
||||
|
||||
static struct bpf_object *
|
||||
__bpf_object__open(const char *path, void *obj_buf, size_t obj_buf_sz,
|
||||
bool needs_kver)
|
||||
bool needs_kver, int flags)
|
||||
{
|
||||
struct bpf_object *obj;
|
||||
int err;
|
||||
@ -1531,7 +1533,7 @@ __bpf_object__open(const char *path, void *obj_buf, size_t obj_buf_sz,
|
||||
|
||||
CHECK_ERR(bpf_object__elf_init(obj), err, out);
|
||||
CHECK_ERR(bpf_object__check_endianness(obj), err, out);
|
||||
CHECK_ERR(bpf_object__elf_collect(obj), err, out);
|
||||
CHECK_ERR(bpf_object__elf_collect(obj, flags), err, out);
|
||||
CHECK_ERR(bpf_object__collect_reloc(obj), err, out);
|
||||
CHECK_ERR(bpf_object__validate(obj, needs_kver), err, out);
|
||||
|
||||
@ -1542,7 +1544,8 @@ out:
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
struct bpf_object *bpf_object__open_xattr(struct bpf_object_open_attr *attr)
|
||||
struct bpf_object *__bpf_object__open_xattr(struct bpf_object_open_attr *attr,
|
||||
int flags)
|
||||
{
|
||||
/* param validation */
|
||||
if (!attr->file)
|
||||
@ -1551,7 +1554,13 @@ struct bpf_object *bpf_object__open_xattr(struct bpf_object_open_attr *attr)
|
||||
pr_debug("loading %s\n", attr->file);
|
||||
|
||||
return __bpf_object__open(attr->file, NULL, 0,
|
||||
bpf_prog_type__needs_kver(attr->prog_type));
|
||||
bpf_prog_type__needs_kver(attr->prog_type),
|
||||
flags);
|
||||
}
|
||||
|
||||
struct bpf_object *bpf_object__open_xattr(struct bpf_object_open_attr *attr)
|
||||
{
|
||||
return __bpf_object__open_xattr(attr, 0);
|
||||
}
|
||||
|
||||
struct bpf_object *bpf_object__open(const char *path)
|
||||
@ -1584,7 +1593,7 @@ struct bpf_object *bpf_object__open_buffer(void *obj_buf,
|
||||
pr_debug("loading object '%s' from buffer\n",
|
||||
name);
|
||||
|
||||
return __bpf_object__open(name, obj_buf, obj_buf_sz, true);
|
||||
return __bpf_object__open(name, obj_buf, obj_buf_sz, true, true);
|
||||
}
|
||||
|
||||
int bpf_object__unload(struct bpf_object *obj)
|
||||
|
@ -61,6 +61,8 @@ struct bpf_object_open_attr {
|
||||
|
||||
struct bpf_object *bpf_object__open(const char *path);
|
||||
struct bpf_object *bpf_object__open_xattr(struct bpf_object_open_attr *attr);
|
||||
struct bpf_object *__bpf_object__open_xattr(struct bpf_object_open_attr *attr,
|
||||
int flags);
|
||||
struct bpf_object *bpf_object__open_buffer(void *obj_buf,
|
||||
size_t obj_buf_sz,
|
||||
const char *name);
|
||||
|
@ -36,7 +36,8 @@ TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test
|
||||
test_get_stack_rawtp.o test_sockmap_kern.o test_sockhash_kern.o \
|
||||
test_lwt_seg6local.o sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
|
||||
get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
|
||||
test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o test_sk_lookup_kern.o
|
||||
test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o \
|
||||
test_sk_lookup_kern.o test_xdp_vlan.o
|
||||
|
||||
# Order correspond to 'make run_tests' order
|
||||
TEST_PROGS := test_kmod.sh \
|
||||
@ -49,7 +50,10 @@ TEST_PROGS := test_kmod.sh \
|
||||
test_lwt_seg6local.sh \
|
||||
test_lirc_mode2.sh \
|
||||
test_skb_cgroup_id.sh \
|
||||
test_flow_dissector.sh
|
||||
test_flow_dissector.sh \
|
||||
test_xdp_vlan.sh
|
||||
|
||||
TEST_PROGS_EXTENDED := with_addr.sh
|
||||
|
||||
# Compile but not part of 'make run_tests'
|
||||
TEST_GEN_PROGS_EXTENDED = test_libbpf_open test_sock_addr test_skb_cgroup_id_user \
|
||||
|
@ -155,6 +155,10 @@ static struct bpf_sock *(*bpf_sk_lookup_udp)(void *ctx,
|
||||
(void *) BPF_FUNC_sk_lookup_udp;
|
||||
static int (*bpf_sk_release)(struct bpf_sock *sk) =
|
||||
(void *) BPF_FUNC_sk_release;
|
||||
static int (*bpf_skb_vlan_push)(void *ctx, __be16 vlan_proto, __u16 vlan_tci) =
|
||||
(void *) BPF_FUNC_skb_vlan_push;
|
||||
static int (*bpf_skb_vlan_pop)(void *ctx) =
|
||||
(void *) BPF_FUNC_skb_vlan_pop;
|
||||
|
||||
/* llvm builtin functions that eBPF C program may use to
|
||||
* emit BPF_LD_ABS and BPF_LD_IND instructions
|
||||
|
@ -19,3 +19,4 @@ CONFIG_CRYPTO_SHA256=m
|
||||
CONFIG_VXLAN=y
|
||||
CONFIG_GENEVE=y
|
||||
CONFIG_NET_CLS_FLOWER=m
|
||||
CONFIG_LWTUNNEL=y
|
||||
|
@ -71,6 +71,7 @@ int txmsg_start;
|
||||
int txmsg_end;
|
||||
int txmsg_ingress;
|
||||
int txmsg_skb;
|
||||
int ktls;
|
||||
|
||||
static const struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h' },
|
||||
@ -92,6 +93,7 @@ static const struct option long_options[] = {
|
||||
{"txmsg_end", required_argument, NULL, 'e'},
|
||||
{"txmsg_ingress", no_argument, &txmsg_ingress, 1 },
|
||||
{"txmsg_skb", no_argument, &txmsg_skb, 1 },
|
||||
{"ktls", no_argument, &ktls, 1 },
|
||||
{0, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
@ -112,6 +114,76 @@ static void usage(char *argv[])
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#define TCP_ULP 31
|
||||
#define TLS_TX 1
|
||||
#define TLS_RX 2
|
||||
#include <linux/tls.h>
|
||||
|
||||
char *sock_to_string(int s)
|
||||
{
|
||||
if (s == c1)
|
||||
return "client1";
|
||||
else if (s == c2)
|
||||
return "client2";
|
||||
else if (s == s1)
|
||||
return "server1";
|
||||
else if (s == s2)
|
||||
return "server2";
|
||||
else if (s == p1)
|
||||
return "peer1";
|
||||
else if (s == p2)
|
||||
return "peer2";
|
||||
else
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static int sockmap_init_ktls(int verbose, int s)
|
||||
{
|
||||
struct tls12_crypto_info_aes_gcm_128 tls_tx = {
|
||||
.info = {
|
||||
.version = TLS_1_2_VERSION,
|
||||
.cipher_type = TLS_CIPHER_AES_GCM_128,
|
||||
},
|
||||
};
|
||||
struct tls12_crypto_info_aes_gcm_128 tls_rx = {
|
||||
.info = {
|
||||
.version = TLS_1_2_VERSION,
|
||||
.cipher_type = TLS_CIPHER_AES_GCM_128,
|
||||
},
|
||||
};
|
||||
int so_buf = 6553500;
|
||||
int err;
|
||||
|
||||
err = setsockopt(s, 6, TCP_ULP, "tls", sizeof("tls"));
|
||||
if (err) {
|
||||
fprintf(stderr, "setsockopt: TCP_ULP(%s) failed with error %i\n", sock_to_string(s), err);
|
||||
return -EINVAL;
|
||||
}
|
||||
err = setsockopt(s, SOL_TLS, TLS_TX, (void *)&tls_tx, sizeof(tls_tx));
|
||||
if (err) {
|
||||
fprintf(stderr, "setsockopt: TLS_TX(%s) failed with error %i\n", sock_to_string(s), err);
|
||||
return -EINVAL;
|
||||
}
|
||||
err = setsockopt(s, SOL_TLS, TLS_RX, (void *)&tls_rx, sizeof(tls_rx));
|
||||
if (err) {
|
||||
fprintf(stderr, "setsockopt: TLS_RX(%s) failed with error %i\n", sock_to_string(s), err);
|
||||
return -EINVAL;
|
||||
}
|
||||
err = setsockopt(s, SOL_SOCKET, SO_SNDBUF, &so_buf, sizeof(so_buf));
|
||||
if (err) {
|
||||
fprintf(stderr, "setsockopt: (%s) failed sndbuf with error %i\n", sock_to_string(s), err);
|
||||
return -EINVAL;
|
||||
}
|
||||
err = setsockopt(s, SOL_SOCKET, SO_RCVBUF, &so_buf, sizeof(so_buf));
|
||||
if (err) {
|
||||
fprintf(stderr, "setsockopt: (%s) failed rcvbuf with error %i\n", sock_to_string(s), err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
fprintf(stdout, "socket(%s) kTLS enabled\n", sock_to_string(s));
|
||||
return 0;
|
||||
}
|
||||
static int sockmap_init_sockets(int verbose)
|
||||
{
|
||||
int i, err, one = 1;
|
||||
@ -456,6 +528,21 @@ static int sendmsg_test(struct sockmap_options *opt)
|
||||
else
|
||||
rx_fd = p2;
|
||||
|
||||
if (ktls) {
|
||||
/* Redirecting into non-TLS socket which sends into a TLS
|
||||
* socket is not a valid test. So in this case lets not
|
||||
* enable kTLS but still run the test.
|
||||
*/
|
||||
if (!txmsg_redir || (txmsg_redir && txmsg_ingress)) {
|
||||
err = sockmap_init_ktls(opt->verbose, rx_fd);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
err = sockmap_init_ktls(opt->verbose, c1);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
rxpid = fork();
|
||||
if (rxpid == 0) {
|
||||
if (opt->drop_expected)
|
||||
@ -907,6 +994,8 @@ static void test_options(char *options)
|
||||
strncat(options, "ingress,", OPTSTRING);
|
||||
if (txmsg_skb)
|
||||
strncat(options, "skb,", OPTSTRING);
|
||||
if (ktls)
|
||||
strncat(options, "ktls,", OPTSTRING);
|
||||
}
|
||||
|
||||
static int __test_exec(int cgrp, int test, struct sockmap_options *opt)
|
||||
|
File diff suppressed because it is too large
Load Diff
292
tools/testing/selftests/bpf/test_xdp_vlan.c
Normal file
292
tools/testing/selftests/bpf/test_xdp_vlan.c
Normal file
@ -0,0 +1,292 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0
|
||||
* Copyright(c) 2018 Jesper Dangaard Brouer.
|
||||
*
|
||||
* XDP/TC VLAN manipulation example
|
||||
*
|
||||
* GOTCHA: Remember to disable NIC hardware offloading of VLANs,
|
||||
* else the VLAN tags are NOT inlined in the packet payload:
|
||||
*
|
||||
* # ethtool -K ixgbe2 rxvlan off
|
||||
*
|
||||
* Verify setting:
|
||||
* # ethtool -k ixgbe2 | grep rx-vlan-offload
|
||||
* rx-vlan-offload: off
|
||||
*
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_vlan.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/pkt_cls.h>
|
||||
|
||||
#include "bpf_helpers.h"
|
||||
#include "bpf_endian.h"
|
||||
|
||||
/* linux/if_vlan.h have not exposed this as UAPI, thus mirror some here
|
||||
*
|
||||
* struct vlan_hdr - vlan header
|
||||
* @h_vlan_TCI: priority and VLAN ID
|
||||
* @h_vlan_encapsulated_proto: packet type ID or len
|
||||
*/
|
||||
struct _vlan_hdr {
|
||||
__be16 h_vlan_TCI;
|
||||
__be16 h_vlan_encapsulated_proto;
|
||||
};
|
||||
#define VLAN_PRIO_MASK 0xe000 /* Priority Code Point */
|
||||
#define VLAN_PRIO_SHIFT 13
|
||||
#define VLAN_CFI_MASK 0x1000 /* Canonical Format Indicator */
|
||||
#define VLAN_TAG_PRESENT VLAN_CFI_MASK
|
||||
#define VLAN_VID_MASK 0x0fff /* VLAN Identifier */
|
||||
#define VLAN_N_VID 4096
|
||||
|
||||
struct parse_pkt {
|
||||
__u16 l3_proto;
|
||||
__u16 l3_offset;
|
||||
__u16 vlan_outer;
|
||||
__u16 vlan_inner;
|
||||
__u8 vlan_outer_offset;
|
||||
__u8 vlan_inner_offset;
|
||||
};
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
static __always_inline
|
||||
bool parse_eth_frame(struct ethhdr *eth, void *data_end, struct parse_pkt *pkt)
|
||||
{
|
||||
__u16 eth_type;
|
||||
__u8 offset;
|
||||
|
||||
offset = sizeof(*eth);
|
||||
/* Make sure packet is large enough for parsing eth + 2 VLAN headers */
|
||||
if ((void *)eth + offset + (2*sizeof(struct _vlan_hdr)) > data_end)
|
||||
return false;
|
||||
|
||||
eth_type = eth->h_proto;
|
||||
|
||||
/* Handle outer VLAN tag */
|
||||
if (eth_type == bpf_htons(ETH_P_8021Q)
|
||||
|| eth_type == bpf_htons(ETH_P_8021AD)) {
|
||||
struct _vlan_hdr *vlan_hdr;
|
||||
|
||||
vlan_hdr = (void *)eth + offset;
|
||||
pkt->vlan_outer_offset = offset;
|
||||
pkt->vlan_outer = bpf_ntohs(vlan_hdr->h_vlan_TCI)
|
||||
& VLAN_VID_MASK;
|
||||
eth_type = vlan_hdr->h_vlan_encapsulated_proto;
|
||||
offset += sizeof(*vlan_hdr);
|
||||
}
|
||||
|
||||
/* Handle inner (double) VLAN tag */
|
||||
if (eth_type == bpf_htons(ETH_P_8021Q)
|
||||
|| eth_type == bpf_htons(ETH_P_8021AD)) {
|
||||
struct _vlan_hdr *vlan_hdr;
|
||||
|
||||
vlan_hdr = (void *)eth + offset;
|
||||
pkt->vlan_inner_offset = offset;
|
||||
pkt->vlan_inner = bpf_ntohs(vlan_hdr->h_vlan_TCI)
|
||||
& VLAN_VID_MASK;
|
||||
eth_type = vlan_hdr->h_vlan_encapsulated_proto;
|
||||
offset += sizeof(*vlan_hdr);
|
||||
}
|
||||
|
||||
pkt->l3_proto = bpf_ntohs(eth_type); /* Convert to host-byte-order */
|
||||
pkt->l3_offset = offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Hint, VLANs are choosen to hit network-byte-order issues */
|
||||
#define TESTVLAN 4011 /* 0xFAB */
|
||||
// #define TO_VLAN 4000 /* 0xFA0 (hint 0xOA0 = 160) */
|
||||
|
||||
SEC("xdp_drop_vlan_4011")
|
||||
int xdp_prognum0(struct xdp_md *ctx)
|
||||
{
|
||||
void *data_end = (void *)(long)ctx->data_end;
|
||||
void *data = (void *)(long)ctx->data;
|
||||
struct parse_pkt pkt = { 0 };
|
||||
|
||||
if (!parse_eth_frame(data, data_end, &pkt))
|
||||
return XDP_ABORTED;
|
||||
|
||||
/* Drop specific VLAN ID example */
|
||||
if (pkt.vlan_outer == TESTVLAN)
|
||||
return XDP_ABORTED;
|
||||
/*
|
||||
* Using XDP_ABORTED makes it possible to record this event,
|
||||
* via tracepoint xdp:xdp_exception like:
|
||||
* # perf record -a -e xdp:xdp_exception
|
||||
* # perf script
|
||||
*/
|
||||
return XDP_PASS;
|
||||
}
|
||||
/*
|
||||
Commands to setup VLAN on Linux to test packets gets dropped:
|
||||
|
||||
export ROOTDEV=ixgbe2
|
||||
export VLANID=4011
|
||||
ip link add link $ROOTDEV name $ROOTDEV.$VLANID type vlan id $VLANID
|
||||
ip link set dev $ROOTDEV.$VLANID up
|
||||
|
||||
ip link set dev $ROOTDEV mtu 1508
|
||||
ip addr add 100.64.40.11/24 dev $ROOTDEV.$VLANID
|
||||
|
||||
Load prog with ip tool:
|
||||
|
||||
ip link set $ROOTDEV xdp off
|
||||
ip link set $ROOTDEV xdp object xdp_vlan01_kern.o section xdp_drop_vlan_4011
|
||||
|
||||
*/
|
||||
|
||||
/* Changing VLAN to zero, have same practical effect as removing the VLAN. */
|
||||
#define TO_VLAN 0
|
||||
|
||||
SEC("xdp_vlan_change")
|
||||
int xdp_prognum1(struct xdp_md *ctx)
|
||||
{
|
||||
void *data_end = (void *)(long)ctx->data_end;
|
||||
void *data = (void *)(long)ctx->data;
|
||||
struct parse_pkt pkt = { 0 };
|
||||
|
||||
if (!parse_eth_frame(data, data_end, &pkt))
|
||||
return XDP_ABORTED;
|
||||
|
||||
/* Change specific VLAN ID */
|
||||
if (pkt.vlan_outer == TESTVLAN) {
|
||||
struct _vlan_hdr *vlan_hdr = data + pkt.vlan_outer_offset;
|
||||
|
||||
/* Modifying VLAN, preserve top 4 bits */
|
||||
vlan_hdr->h_vlan_TCI =
|
||||
bpf_htons((bpf_ntohs(vlan_hdr->h_vlan_TCI) & 0xf000)
|
||||
| TO_VLAN);
|
||||
}
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Show XDP+TC can cooperate, on creating a VLAN rewriter.
|
||||
* 1. Create a XDP prog that can "pop"/remove a VLAN header.
|
||||
* 2. Create a TC-bpf prog that egress can add a VLAN header.
|
||||
*/
|
||||
|
||||
#ifndef ETH_ALEN /* Ethernet MAC address length */
|
||||
#define ETH_ALEN 6 /* bytes */
|
||||
#endif
|
||||
#define VLAN_HDR_SZ 4 /* bytes */
|
||||
|
||||
SEC("xdp_vlan_remove_outer")
|
||||
int xdp_prognum2(struct xdp_md *ctx)
|
||||
{
|
||||
void *data_end = (void *)(long)ctx->data_end;
|
||||
void *data = (void *)(long)ctx->data;
|
||||
struct parse_pkt pkt = { 0 };
|
||||
char *dest;
|
||||
|
||||
if (!parse_eth_frame(data, data_end, &pkt))
|
||||
return XDP_ABORTED;
|
||||
|
||||
/* Skip packet if no outer VLAN was detected */
|
||||
if (pkt.vlan_outer_offset == 0)
|
||||
return XDP_PASS;
|
||||
|
||||
/* Moving Ethernet header, dest overlap with src, memmove handle this */
|
||||
dest = data;
|
||||
dest+= VLAN_HDR_SZ;
|
||||
/*
|
||||
* Notice: Taking over vlan_hdr->h_vlan_encapsulated_proto, by
|
||||
* only moving two MAC addrs (12 bytes), not overwriting last 2 bytes
|
||||
*/
|
||||
__builtin_memmove(dest, data, ETH_ALEN * 2);
|
||||
/* Note: LLVM built-in memmove inlining require size to be constant */
|
||||
|
||||
/* Move start of packet header seen by Linux kernel stack */
|
||||
bpf_xdp_adjust_head(ctx, VLAN_HDR_SZ);
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
static __always_inline
|
||||
void shift_mac_4bytes_16bit(void *data)
|
||||
{
|
||||
__u16 *p = data;
|
||||
|
||||
p[7] = p[5]; /* delete p[7] was vlan_hdr->h_vlan_TCI */
|
||||
p[6] = p[4]; /* delete p[6] was ethhdr->h_proto */
|
||||
p[5] = p[3];
|
||||
p[4] = p[2];
|
||||
p[3] = p[1];
|
||||
p[2] = p[0];
|
||||
}
|
||||
|
||||
static __always_inline
|
||||
void shift_mac_4bytes_32bit(void *data)
|
||||
{
|
||||
__u32 *p = data;
|
||||
|
||||
/* Assuming VLAN hdr present. The 4 bytes in p[3] that gets
|
||||
* overwritten, is ethhdr->h_proto and vlan_hdr->h_vlan_TCI.
|
||||
* The vlan_hdr->h_vlan_encapsulated_proto take over role as
|
||||
* ethhdr->h_proto.
|
||||
*/
|
||||
p[3] = p[2];
|
||||
p[2] = p[1];
|
||||
p[1] = p[0];
|
||||
}
|
||||
|
||||
SEC("xdp_vlan_remove_outer2")
|
||||
int xdp_prognum3(struct xdp_md *ctx)
|
||||
{
|
||||
void *data_end = (void *)(long)ctx->data_end;
|
||||
void *data = (void *)(long)ctx->data;
|
||||
struct ethhdr *orig_eth = data;
|
||||
struct parse_pkt pkt = { 0 };
|
||||
|
||||
if (!parse_eth_frame(orig_eth, data_end, &pkt))
|
||||
return XDP_ABORTED;
|
||||
|
||||
/* Skip packet if no outer VLAN was detected */
|
||||
if (pkt.vlan_outer_offset == 0)
|
||||
return XDP_PASS;
|
||||
|
||||
/* Simply shift down MAC addrs 4 bytes, overwrite h_proto + TCI */
|
||||
shift_mac_4bytes_32bit(data);
|
||||
|
||||
/* Move start of packet header seen by Linux kernel stack */
|
||||
bpf_xdp_adjust_head(ctx, VLAN_HDR_SZ);
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
/*=====================================
|
||||
* BELOW: TC-hook based ebpf programs
|
||||
* ====================================
|
||||
* The TC-clsact eBPF programs (currently) need to be attach via TC commands
|
||||
*/
|
||||
|
||||
SEC("tc_vlan_push")
|
||||
int _tc_progA(struct __sk_buff *ctx)
|
||||
{
|
||||
bpf_skb_vlan_push(ctx, bpf_htons(ETH_P_8021Q), TESTVLAN);
|
||||
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
/*
|
||||
Commands to setup TC to use above bpf prog:
|
||||
|
||||
export ROOTDEV=ixgbe2
|
||||
export FILE=xdp_vlan01_kern.o
|
||||
|
||||
# Re-attach clsact to clear/flush existing role
|
||||
tc qdisc del dev $ROOTDEV clsact 2> /dev/null ;\
|
||||
tc qdisc add dev $ROOTDEV clsact
|
||||
|
||||
# Attach BPF prog EGRESS
|
||||
tc filter add dev $ROOTDEV egress \
|
||||
prio 1 handle 1 bpf da obj $FILE sec tc_vlan_push
|
||||
|
||||
tc filter show dev $ROOTDEV egress
|
||||
*/
|
195
tools/testing/selftests/bpf/test_xdp_vlan.sh
Executable file
195
tools/testing/selftests/bpf/test_xdp_vlan.sh
Executable file
@ -0,0 +1,195 @@
|
||||
#!/bin/bash
|
||||
|
||||
TESTNAME=xdp_vlan
|
||||
|
||||
usage() {
|
||||
echo "Testing XDP + TC eBPF VLAN manipulations: $TESTNAME"
|
||||
echo ""
|
||||
echo "Usage: $0 [-vfh]"
|
||||
echo " -v | --verbose : Verbose"
|
||||
echo " --flush : Flush before starting (e.g. after --interactive)"
|
||||
echo " --interactive : Keep netns setup running after test-run"
|
||||
echo ""
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
local status=$?
|
||||
|
||||
if [ "$status" = "0" ]; then
|
||||
echo "selftests: $TESTNAME [PASS]";
|
||||
else
|
||||
echo "selftests: $TESTNAME [FAILED]";
|
||||
fi
|
||||
|
||||
if [ -n "$INTERACTIVE" ]; then
|
||||
echo "Namespace setup still active explore with:"
|
||||
echo " ip netns exec ns1 bash"
|
||||
echo " ip netns exec ns2 bash"
|
||||
exit $status
|
||||
fi
|
||||
|
||||
set +e
|
||||
ip link del veth1 2> /dev/null
|
||||
ip netns del ns1 2> /dev/null
|
||||
ip netns del ns2 2> /dev/null
|
||||
}
|
||||
|
||||
# Using external program "getopt" to get --long-options
|
||||
OPTIONS=$(getopt -o hvfi: \
|
||||
--long verbose,flush,help,interactive,debug -- "$@")
|
||||
if (( $? != 0 )); then
|
||||
usage
|
||||
echo "selftests: $TESTNAME [FAILED] Error calling getopt, unknown option?"
|
||||
exit 2
|
||||
fi
|
||||
eval set -- "$OPTIONS"
|
||||
|
||||
## --- Parse command line arguments / parameters ---
|
||||
while true; do
|
||||
case "$1" in
|
||||
-v | --verbose)
|
||||
export VERBOSE=yes
|
||||
shift
|
||||
;;
|
||||
-i | --interactive | --debug )
|
||||
INTERACTIVE=yes
|
||||
shift
|
||||
;;
|
||||
-f | --flush )
|
||||
cleanup
|
||||
shift
|
||||
;;
|
||||
-- )
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-h | --help )
|
||||
usage;
|
||||
echo "selftests: $TESTNAME [SKIP] usage help info requested"
|
||||
exit 0
|
||||
;;
|
||||
* )
|
||||
shift
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "selftests: $TESTNAME [FAILED] need root privileges"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ip link set dev lo xdp off 2>/dev/null > /dev/null
|
||||
if [ $? -ne 0 ];then
|
||||
echo "selftests: $TESTNAME [SKIP] need ip xdp support"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Interactive mode likely require us to cleanup netns
|
||||
if [ -n "$INTERACTIVE" ]; then
|
||||
ip link del veth1 2> /dev/null
|
||||
ip netns del ns1 2> /dev/null
|
||||
ip netns del ns2 2> /dev/null
|
||||
fi
|
||||
|
||||
# Exit on failure
|
||||
set -e
|
||||
|
||||
# Some shell-tools dependencies
|
||||
which ip > /dev/null
|
||||
which tc > /dev/null
|
||||
which ethtool > /dev/null
|
||||
|
||||
# Make rest of shell verbose, showing comments as doc/info
|
||||
if [ -n "$VERBOSE" ]; then
|
||||
set -v
|
||||
fi
|
||||
|
||||
# Create two namespaces
|
||||
ip netns add ns1
|
||||
ip netns add ns2
|
||||
|
||||
# Run cleanup if failing or on kill
|
||||
trap cleanup 0 2 3 6 9
|
||||
|
||||
# Create veth pair
|
||||
ip link add veth1 type veth peer name veth2
|
||||
|
||||
# Move veth1 and veth2 into the respective namespaces
|
||||
ip link set veth1 netns ns1
|
||||
ip link set veth2 netns ns2
|
||||
|
||||
# NOTICE: XDP require VLAN header inside packet payload
|
||||
# - Thus, disable VLAN offloading driver features
|
||||
# - For veth REMEMBER TX side VLAN-offload
|
||||
#
|
||||
# Disable rx-vlan-offload (mostly needed on ns1)
|
||||
ip netns exec ns1 ethtool -K veth1 rxvlan off
|
||||
ip netns exec ns2 ethtool -K veth2 rxvlan off
|
||||
#
|
||||
# Disable tx-vlan-offload (mostly needed on ns2)
|
||||
ip netns exec ns2 ethtool -K veth2 txvlan off
|
||||
ip netns exec ns1 ethtool -K veth1 txvlan off
|
||||
|
||||
export IPADDR1=100.64.41.1
|
||||
export IPADDR2=100.64.41.2
|
||||
|
||||
# In ns1/veth1 add IP-addr on plain net_device
|
||||
ip netns exec ns1 ip addr add ${IPADDR1}/24 dev veth1
|
||||
ip netns exec ns1 ip link set veth1 up
|
||||
|
||||
# In ns2/veth2 create VLAN device
|
||||
export VLAN=4011
|
||||
export DEVNS2=veth2
|
||||
ip netns exec ns2 ip link add link $DEVNS2 name $DEVNS2.$VLAN type vlan id $VLAN
|
||||
ip netns exec ns2 ip addr add ${IPADDR2}/24 dev $DEVNS2.$VLAN
|
||||
ip netns exec ns2 ip link set $DEVNS2 up
|
||||
ip netns exec ns2 ip link set $DEVNS2.$VLAN up
|
||||
|
||||
# Bringup lo in netns (to avoids confusing people using --interactive)
|
||||
ip netns exec ns1 ip link set lo up
|
||||
ip netns exec ns2 ip link set lo up
|
||||
|
||||
# At this point, the hosts cannot reach each-other,
|
||||
# because ns2 are using VLAN tags on the packets.
|
||||
|
||||
ip netns exec ns2 sh -c 'ping -W 1 -c 1 100.64.41.1 || echo "Okay ping fails"'
|
||||
|
||||
|
||||
# Now we can use the test_xdp_vlan.c program to pop/push these VLAN tags
|
||||
# ----------------------------------------------------------------------
|
||||
# In ns1: ingress use XDP to remove VLAN tags
|
||||
export DEVNS1=veth1
|
||||
export FILE=test_xdp_vlan.o
|
||||
|
||||
# First test: Remove VLAN by setting VLAN ID 0, using "xdp_vlan_change"
|
||||
export XDP_PROG=xdp_vlan_change
|
||||
ip netns exec ns1 ip link set $DEVNS1 xdp object $FILE section $XDP_PROG
|
||||
|
||||
# In ns1: egress use TC to add back VLAN tag 4011
|
||||
# (del cmd)
|
||||
# tc qdisc del dev $DEVNS1 clsact 2> /dev/null
|
||||
#
|
||||
ip netns exec ns1 tc qdisc add dev $DEVNS1 clsact
|
||||
ip netns exec ns1 tc filter add dev $DEVNS1 egress \
|
||||
prio 1 handle 1 bpf da obj $FILE sec tc_vlan_push
|
||||
|
||||
# Now the namespaces can reach each-other, test with ping:
|
||||
ip netns exec ns2 ping -W 2 -c 3 $IPADDR1
|
||||
ip netns exec ns1 ping -W 2 -c 3 $IPADDR2
|
||||
|
||||
# Second test: Replace xdp prog, that fully remove vlan header
|
||||
#
|
||||
# Catch kernel bug for generic-XDP, that does didn't allow us to
|
||||
# remove a VLAN header, because skb->protocol still contain VLAN
|
||||
# ETH_P_8021Q indication, and this cause overwriting of our changes.
|
||||
#
|
||||
export XDP_PROG=xdp_vlan_remove_outer2
|
||||
ip netns exec ns1 ip link set $DEVNS1 xdp off
|
||||
ip netns exec ns1 ip link set $DEVNS1 xdp object $FILE section $XDP_PROG
|
||||
|
||||
# Now the namespaces should still be able reach each-other, test with ping:
|
||||
ip netns exec ns2 ping -W 2 -c 3 $IPADDR1
|
||||
ip netns exec ns1 ping -W 2 -c 3 $IPADDR2
|
Loading…
Reference in New Issue
Block a user