|
@@ -54,6 +54,7 @@
|
|
#include <net/dst.h>
|
|
#include <net/dst.h>
|
|
#include <net/sock_reuseport.h>
|
|
#include <net/sock_reuseport.h>
|
|
#include <net/busy_poll.h>
|
|
#include <net/busy_poll.h>
|
|
|
|
+#include <net/tcp.h>
|
|
|
|
|
|
/**
|
|
/**
|
|
* sk_filter_trim_cap - run a packet through a socket filter
|
|
* sk_filter_trim_cap - run a packet through a socket filter
|
|
@@ -2672,6 +2673,110 @@ static const struct bpf_func_proto bpf_get_socket_uid_proto = {
|
|
.arg1_type = ARG_PTR_TO_CTX,
|
|
.arg1_type = ARG_PTR_TO_CTX,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+BPF_CALL_5(bpf_setsockopt, struct bpf_sock_ops_kern *, bpf_sock,
|
|
|
|
+ int, level, int, optname, char *, optval, int, optlen)
|
|
|
|
+{
|
|
|
|
+ struct sock *sk = bpf_sock->sk;
|
|
|
|
+ int ret = 0;
|
|
|
|
+ int val;
|
|
|
|
+
|
|
|
|
+ if (!sk_fullsock(sk))
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ if (level == SOL_SOCKET) {
|
|
|
|
+ if (optlen != sizeof(int))
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ val = *((int *)optval);
|
|
|
|
+
|
|
|
|
+ /* Only some socketops are supported */
|
|
|
|
+ switch (optname) {
|
|
|
|
+ case SO_RCVBUF:
|
|
|
|
+ sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
|
|
|
|
+ sk->sk_rcvbuf = max_t(int, val * 2, SOCK_MIN_RCVBUF);
|
|
|
|
+ break;
|
|
|
|
+ case SO_SNDBUF:
|
|
|
|
+ sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
|
|
|
|
+ sk->sk_sndbuf = max_t(int, val * 2, SOCK_MIN_SNDBUF);
|
|
|
|
+ break;
|
|
|
|
+ case SO_MAX_PACING_RATE:
|
|
|
|
+ sk->sk_max_pacing_rate = val;
|
|
|
|
+ sk->sk_pacing_rate = min(sk->sk_pacing_rate,
|
|
|
|
+ sk->sk_max_pacing_rate);
|
|
|
|
+ break;
|
|
|
|
+ case SO_PRIORITY:
|
|
|
|
+ sk->sk_priority = val;
|
|
|
|
+ break;
|
|
|
|
+ case SO_RCVLOWAT:
|
|
|
|
+ if (val < 0)
|
|
|
|
+ val = INT_MAX;
|
|
|
|
+ sk->sk_rcvlowat = val ? : 1;
|
|
|
|
+ break;
|
|
|
|
+ case SO_MARK:
|
|
|
|
+ sk->sk_mark = val;
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ }
|
|
|
|
+ } else if (level == SOL_TCP &&
|
|
|
|
+ sk->sk_prot->setsockopt == tcp_setsockopt) {
|
|
|
|
+#ifdef CONFIG_INET
|
|
|
|
+ if (optname == TCP_CONGESTION) {
|
|
|
|
+ char name[TCP_CA_NAME_MAX];
|
|
|
|
+
|
|
|
|
+ strncpy(name, optval, min_t(long, optlen,
|
|
|
|
+ TCP_CA_NAME_MAX-1));
|
|
|
|
+ name[TCP_CA_NAME_MAX-1] = 0;
|
|
|
|
+ ret = tcp_set_congestion_control(sk, name, false);
|
|
|
|
+ if (!ret && bpf_sock->op > BPF_SOCK_OPS_NEEDS_ECN)
|
|
|
|
+ /* replacing an existing ca */
|
|
|
|
+ tcp_reinit_congestion_control(sk,
|
|
|
|
+ inet_csk(sk)->icsk_ca_ops);
|
|
|
|
+ } else {
|
|
|
|
+ struct tcp_sock *tp = tcp_sk(sk);
|
|
|
|
+
|
|
|
|
+ if (optlen != sizeof(int))
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ val = *((int *)optval);
|
|
|
|
+ /* Only some options are supported */
|
|
|
|
+ switch (optname) {
|
|
|
|
+ case TCP_BPF_IW:
|
|
|
|
+ if (val <= 0 || tp->data_segs_out > 0)
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ else
|
|
|
|
+ tp->snd_cwnd = val;
|
|
|
|
+ break;
|
|
|
|
+ case TCP_BPF_SNDCWND_CLAMP:
|
|
|
|
+ if (val <= 0) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ } else {
|
|
|
|
+ tp->snd_cwnd_clamp = val;
|
|
|
|
+ tp->snd_ssthresh = val;
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+#else
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+#endif
|
|
|
|
+ } else {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct bpf_func_proto bpf_setsockopt_proto = {
|
|
|
|
+ .func = bpf_setsockopt,
|
|
|
|
+ .gpl_only = true,
|
|
|
|
+ .ret_type = RET_INTEGER,
|
|
|
|
+ .arg1_type = ARG_PTR_TO_CTX,
|
|
|
|
+ .arg2_type = ARG_ANYTHING,
|
|
|
|
+ .arg3_type = ARG_ANYTHING,
|
|
|
|
+ .arg4_type = ARG_PTR_TO_MEM,
|
|
|
|
+ .arg5_type = ARG_CONST_SIZE,
|
|
|
|
+};
|
|
|
|
+
|
|
static const struct bpf_func_proto *
|
|
static const struct bpf_func_proto *
|
|
bpf_base_func_proto(enum bpf_func_id func_id)
|
|
bpf_base_func_proto(enum bpf_func_id func_id)
|
|
{
|
|
{
|
|
@@ -2822,6 +2927,17 @@ lwt_inout_func_proto(enum bpf_func_id func_id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static const struct bpf_func_proto *
|
|
|
|
+ sock_ops_func_proto(enum bpf_func_id func_id)
|
|
|
|
+{
|
|
|
|
+ switch (func_id) {
|
|
|
|
+ case BPF_FUNC_setsockopt:
|
|
|
|
+ return &bpf_setsockopt_proto;
|
|
|
|
+ default:
|
|
|
|
+ return bpf_base_func_proto(func_id);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
static const struct bpf_func_proto *
|
|
static const struct bpf_func_proto *
|
|
lwt_xmit_func_proto(enum bpf_func_id func_id)
|
|
lwt_xmit_func_proto(enum bpf_func_id func_id)
|
|
{
|
|
{
|
|
@@ -3110,6 +3226,36 @@ void bpf_warn_invalid_xdp_action(u32 act)
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(bpf_warn_invalid_xdp_action);
|
|
EXPORT_SYMBOL_GPL(bpf_warn_invalid_xdp_action);
|
|
|
|
|
|
|
|
+static bool __is_valid_sock_ops_access(int off, int size)
|
|
|
|
+{
|
|
|
|
+ if (off < 0 || off >= sizeof(struct bpf_sock_ops))
|
|
|
|
+ return false;
|
|
|
|
+ /* The verifier guarantees that size > 0. */
|
|
|
|
+ if (off % size != 0)
|
|
|
|
+ return false;
|
|
|
|
+ if (size != sizeof(__u32))
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool sock_ops_is_valid_access(int off, int size,
|
|
|
|
+ enum bpf_access_type type,
|
|
|
|
+ struct bpf_insn_access_aux *info)
|
|
|
|
+{
|
|
|
|
+ if (type == BPF_WRITE) {
|
|
|
|
+ switch (off) {
|
|
|
|
+ case offsetof(struct bpf_sock_ops, op) ...
|
|
|
|
+ offsetof(struct bpf_sock_ops, replylong[3]):
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return __is_valid_sock_ops_access(off, size);
|
|
|
|
+}
|
|
|
|
+
|
|
static u32 bpf_convert_ctx_access(enum bpf_access_type type,
|
|
static u32 bpf_convert_ctx_access(enum bpf_access_type type,
|
|
const struct bpf_insn *si,
|
|
const struct bpf_insn *si,
|
|
struct bpf_insn *insn_buf,
|
|
struct bpf_insn *insn_buf,
|
|
@@ -3379,6 +3525,138 @@ static u32 xdp_convert_ctx_access(enum bpf_access_type type,
|
|
return insn - insn_buf;
|
|
return insn - insn_buf;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static u32 sock_ops_convert_ctx_access(enum bpf_access_type type,
|
|
|
|
+ const struct bpf_insn *si,
|
|
|
|
+ struct bpf_insn *insn_buf,
|
|
|
|
+ struct bpf_prog *prog)
|
|
|
|
+{
|
|
|
|
+ struct bpf_insn *insn = insn_buf;
|
|
|
|
+ int off;
|
|
|
|
+
|
|
|
|
+ switch (si->off) {
|
|
|
|
+ case offsetof(struct bpf_sock_ops, op) ...
|
|
|
|
+ offsetof(struct bpf_sock_ops, replylong[3]):
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct bpf_sock_ops, op) !=
|
|
|
|
+ FIELD_SIZEOF(struct bpf_sock_ops_kern, op));
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct bpf_sock_ops, reply) !=
|
|
|
|
+ FIELD_SIZEOF(struct bpf_sock_ops_kern, reply));
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct bpf_sock_ops, replylong) !=
|
|
|
|
+ FIELD_SIZEOF(struct bpf_sock_ops_kern, replylong));
|
|
|
|
+ off = si->off;
|
|
|
|
+ off -= offsetof(struct bpf_sock_ops, op);
|
|
|
|
+ off += offsetof(struct bpf_sock_ops_kern, op);
|
|
|
|
+ if (type == BPF_WRITE)
|
|
|
|
+ *insn++ = BPF_STX_MEM(BPF_W, si->dst_reg, si->src_reg,
|
|
|
|
+ off);
|
|
|
|
+ else
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg,
|
|
|
|
+ off);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case offsetof(struct bpf_sock_ops, family):
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_family) != 2);
|
|
|
|
+
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
|
|
|
+ struct bpf_sock_ops_kern, sk),
|
|
|
|
+ si->dst_reg, si->src_reg,
|
|
|
|
+ offsetof(struct bpf_sock_ops_kern, sk));
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
|
|
|
|
+ offsetof(struct sock_common, skc_family));
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case offsetof(struct bpf_sock_ops, remote_ip4):
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_daddr) != 4);
|
|
|
|
+
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
|
|
|
+ struct bpf_sock_ops_kern, sk),
|
|
|
|
+ si->dst_reg, si->src_reg,
|
|
|
|
+ offsetof(struct bpf_sock_ops_kern, sk));
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
|
|
|
|
+ offsetof(struct sock_common, skc_daddr));
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case offsetof(struct bpf_sock_ops, local_ip4):
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_rcv_saddr) != 4);
|
|
|
|
+
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
|
|
|
+ struct bpf_sock_ops_kern, sk),
|
|
|
|
+ si->dst_reg, si->src_reg,
|
|
|
|
+ offsetof(struct bpf_sock_ops_kern, sk));
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
|
|
|
|
+ offsetof(struct sock_common,
|
|
|
|
+ skc_rcv_saddr));
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case offsetof(struct bpf_sock_ops, remote_ip6[0]) ...
|
|
|
|
+ offsetof(struct bpf_sock_ops, remote_ip6[3]):
|
|
|
|
+#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common,
|
|
|
|
+ skc_v6_daddr.s6_addr32[0]) != 4);
|
|
|
|
+
|
|
|
|
+ off = si->off;
|
|
|
|
+ off -= offsetof(struct bpf_sock_ops, remote_ip6[0]);
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
|
|
|
+ struct bpf_sock_ops_kern, sk),
|
|
|
|
+ si->dst_reg, si->src_reg,
|
|
|
|
+ offsetof(struct bpf_sock_ops_kern, sk));
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
|
|
|
|
+ offsetof(struct sock_common,
|
|
|
|
+ skc_v6_daddr.s6_addr32[0]) +
|
|
|
|
+ off);
|
|
|
|
+#else
|
|
|
|
+ *insn++ = BPF_MOV32_IMM(si->dst_reg, 0);
|
|
|
|
+#endif
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case offsetof(struct bpf_sock_ops, local_ip6[0]) ...
|
|
|
|
+ offsetof(struct bpf_sock_ops, local_ip6[3]):
|
|
|
|
+#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common,
|
|
|
|
+ skc_v6_rcv_saddr.s6_addr32[0]) != 4);
|
|
|
|
+
|
|
|
|
+ off = si->off;
|
|
|
|
+ off -= offsetof(struct bpf_sock_ops, local_ip6[0]);
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
|
|
|
+ struct bpf_sock_ops_kern, sk),
|
|
|
|
+ si->dst_reg, si->src_reg,
|
|
|
|
+ offsetof(struct bpf_sock_ops_kern, sk));
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
|
|
|
|
+ offsetof(struct sock_common,
|
|
|
|
+ skc_v6_rcv_saddr.s6_addr32[0]) +
|
|
|
|
+ off);
|
|
|
|
+#else
|
|
|
|
+ *insn++ = BPF_MOV32_IMM(si->dst_reg, 0);
|
|
|
|
+#endif
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case offsetof(struct bpf_sock_ops, remote_port):
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_dport) != 2);
|
|
|
|
+
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
|
|
|
+ struct bpf_sock_ops_kern, sk),
|
|
|
|
+ si->dst_reg, si->src_reg,
|
|
|
|
+ offsetof(struct bpf_sock_ops_kern, sk));
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
|
|
|
|
+ offsetof(struct sock_common, skc_dport));
|
|
|
|
+#ifndef __BIG_ENDIAN_BITFIELD
|
|
|
|
+ *insn++ = BPF_ALU32_IMM(BPF_LSH, si->dst_reg, 16);
|
|
|
|
+#endif
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case offsetof(struct bpf_sock_ops, local_port):
|
|
|
|
+ BUILD_BUG_ON(FIELD_SIZEOF(struct sock_common, skc_num) != 2);
|
|
|
|
+
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(
|
|
|
|
+ struct bpf_sock_ops_kern, sk),
|
|
|
|
+ si->dst_reg, si->src_reg,
|
|
|
|
+ offsetof(struct bpf_sock_ops_kern, sk));
|
|
|
|
+ *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
|
|
|
|
+ offsetof(struct sock_common, skc_num));
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ return insn - insn_buf;
|
|
|
|
+}
|
|
|
|
+
|
|
const struct bpf_verifier_ops sk_filter_prog_ops = {
|
|
const struct bpf_verifier_ops sk_filter_prog_ops = {
|
|
.get_func_proto = sk_filter_func_proto,
|
|
.get_func_proto = sk_filter_func_proto,
|
|
.is_valid_access = sk_filter_is_valid_access,
|
|
.is_valid_access = sk_filter_is_valid_access,
|
|
@@ -3428,6 +3706,12 @@ const struct bpf_verifier_ops cg_sock_prog_ops = {
|
|
.convert_ctx_access = sock_filter_convert_ctx_access,
|
|
.convert_ctx_access = sock_filter_convert_ctx_access,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+const struct bpf_verifier_ops sock_ops_prog_ops = {
|
|
|
|
+ .get_func_proto = sock_ops_func_proto,
|
|
|
|
+ .is_valid_access = sock_ops_is_valid_access,
|
|
|
|
+ .convert_ctx_access = sock_ops_convert_ctx_access,
|
|
|
|
+};
|
|
|
|
+
|
|
int sk_detach_filter(struct sock *sk)
|
|
int sk_detach_filter(struct sock *sk)
|
|
{
|
|
{
|
|
int ret = -ENOENT;
|
|
int ret = -ENOENT;
|