bpf: add bpf_tcp_gen_syncookie helper
authorPetar Penkov <ppenkov@google.com>
Mon, 29 Jul 2019 16:59:15 +0000 (09:59 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 31 Jul 2019 04:03:05 +0000 (21:03 -0700)
This helper function allows BPF programs to try to generate SYN
cookies, given a reference to a listener socket. The function works
from XDP and with an skb context since bpf_skc_lookup_tcp can lookup a
socket in both cases.

Signed-off-by: Petar Penkov <ppenkov@google.com>
Suggested-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Lorenz Bauer <lmb@cloudflare.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
include/uapi/linux/bpf.h
net/core/filter.c

index 6bbef0c7f5853307b452516e960585a0a5514325..4393bd4b241973c1ad82edb70a40b63517a7f9ce 100644 (file)
@@ -2714,6 +2714,33 @@ union bpf_attr {
  *             **-EPERM** if no permission to send the *sig*.
  *
  *             **-EAGAIN** if bpf program can try again.
+ *
+ * s64 bpf_tcp_gen_syncookie(struct bpf_sock *sk, void *iph, u32 iph_len, struct tcphdr *th, u32 th_len)
+ *     Description
+ *             Try to issue a SYN cookie for the packet with corresponding
+ *             IP/TCP headers, *iph* and *th*, on the listening socket in *sk*.
+ *
+ *             *iph* points to the start of the IPv4 or IPv6 header, while
+ *             *iph_len* contains **sizeof**\ (**struct iphdr**) or
+ *             **sizeof**\ (**struct ip6hdr**).
+ *
+ *             *th* points to the start of the TCP header, while *th_len*
+ *             contains the length of the TCP header.
+ *
+ *     Return
+ *             On success, lower 32 bits hold the generated SYN cookie in
+ *             followed by 16 bits which hold the MSS value for that cookie,
+ *             and the top 16 bits are unused.
+ *
+ *             On failure, the returned value is one of the following:
+ *
+ *             **-EINVAL** SYN cookie cannot be issued due to error
+ *
+ *             **-ENOENT** SYN cookie should not be issued (no SYN flood)
+ *
+ *             **-EOPNOTSUPP** kernel configuration does not enable SYN cookies
+ *
+ *             **-EPROTONOSUPPORT** IP packet version is not 4 or 6
  */
 #define __BPF_FUNC_MAPPER(FN)          \
        FN(unspec),                     \
@@ -2825,7 +2852,8 @@ union bpf_attr {
        FN(strtoul),                    \
        FN(sk_storage_get),             \
        FN(sk_storage_delete),          \
-       FN(send_signal),
+       FN(send_signal),                \
+       FN(tcp_gen_syncookie),
 
 /* integer value in 'imm' field of BPF_CALL instruction selects which helper
  * function eBPF program intends to call
index 1eee70f80fbad224fe4eb64b4b9f7eca371da956..5a2707918629bc1112e3057f15c6e6da5d7dae64 100644 (file)
@@ -5855,6 +5855,75 @@ static const struct bpf_func_proto bpf_tcp_check_syncookie_proto = {
        .arg5_type      = ARG_CONST_SIZE,
 };
 
+BPF_CALL_5(bpf_tcp_gen_syncookie, struct sock *, sk, void *, iph, u32, iph_len,
+          struct tcphdr *, th, u32, th_len)
+{
+#ifdef CONFIG_SYN_COOKIES
+       u32 cookie;
+       u16 mss;
+
+       if (unlikely(th_len < sizeof(*th) || th_len != th->doff * 4))
+               return -EINVAL;
+
+       if (sk->sk_protocol != IPPROTO_TCP || sk->sk_state != TCP_LISTEN)
+               return -EINVAL;
+
+       if (!sock_net(sk)->ipv4.sysctl_tcp_syncookies)
+               return -ENOENT;
+
+       if (!th->syn || th->ack || th->fin || th->rst)
+               return -EINVAL;
+
+       if (unlikely(iph_len < sizeof(struct iphdr)))
+               return -EINVAL;
+
+       /* Both struct iphdr and struct ipv6hdr have the version field at the
+        * same offset so we can cast to the shorter header (struct iphdr).
+        */
+       switch (((struct iphdr *)iph)->version) {
+       case 4:
+               if (sk->sk_family == AF_INET6 && sk->sk_ipv6only)
+                       return -EINVAL;
+
+               mss = tcp_v4_get_syncookie(sk, iph, th, &cookie);
+               break;
+
+#if IS_BUILTIN(CONFIG_IPV6)
+       case 6:
+               if (unlikely(iph_len < sizeof(struct ipv6hdr)))
+                       return -EINVAL;
+
+               if (sk->sk_family != AF_INET6)
+                       return -EINVAL;
+
+               mss = tcp_v6_get_syncookie(sk, iph, th, &cookie);
+               break;
+#endif /* CONFIG_IPV6 */
+
+       default:
+               return -EPROTONOSUPPORT;
+       }
+       if (mss <= 0)
+               return -ENOENT;
+
+       return cookie | ((u64)mss << 32);
+#else
+       return -EOPNOTSUPP;
+#endif /* CONFIG_SYN_COOKIES */
+}
+
+static const struct bpf_func_proto bpf_tcp_gen_syncookie_proto = {
+       .func           = bpf_tcp_gen_syncookie,
+       .gpl_only       = true, /* __cookie_v*_init_sequence() is GPL */
+       .pkt_access     = true,
+       .ret_type       = RET_INTEGER,
+       .arg1_type      = ARG_PTR_TO_SOCK_COMMON,
+       .arg2_type      = ARG_PTR_TO_MEM,
+       .arg3_type      = ARG_CONST_SIZE,
+       .arg4_type      = ARG_PTR_TO_MEM,
+       .arg5_type      = ARG_CONST_SIZE,
+};
+
 #endif /* CONFIG_INET */
 
 bool bpf_helper_changes_pkt_data(void *func)
@@ -6144,6 +6213,8 @@ tc_cls_act_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
                return &bpf_tcp_check_syncookie_proto;
        case BPF_FUNC_skb_ecn_set_ce:
                return &bpf_skb_ecn_set_ce_proto;
+       case BPF_FUNC_tcp_gen_syncookie:
+               return &bpf_tcp_gen_syncookie_proto;
 #endif
        default:
                return bpf_base_func_proto(func_id);
@@ -6183,6 +6254,8 @@ xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
                return &bpf_xdp_skc_lookup_tcp_proto;
        case BPF_FUNC_tcp_check_syncookie:
                return &bpf_tcp_check_syncookie_proto;
+       case BPF_FUNC_tcp_gen_syncookie:
+               return &bpf_tcp_gen_syncookie_proto;
 #endif
        default:
                return bpf_base_func_proto(func_id);