From patchwork Tue Aug 24 21:34:34 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 502153 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EBC25C4320A for ; Tue, 24 Aug 2021 21:35:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id BA7C16135F for ; Tue, 24 Aug 2021 21:35:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235806AbhHXVgW (ORCPT ); Tue, 24 Aug 2021 17:36:22 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35362 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235316AbhHXVgL (ORCPT ); Tue, 24 Aug 2021 17:36:11 -0400 Received: from mail-ej1-x62c.google.com (mail-ej1-x62c.google.com [IPv6:2a00:1450:4864:20::62c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A5CE6C0613C1; Tue, 24 Aug 2021 14:35:26 -0700 (PDT) Received: by mail-ej1-x62c.google.com with SMTP id i21so5941127ejd.2; Tue, 24 Aug 2021 14:35:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=X5Apuv2cyt0MITmcIM7M4objA2kYP9T31JBKiEBK3jU=; b=GMGc9WI1IO6ZCLu7N2eROZXp2bwOHPI1Z40Dq19JmBcZ4hnTggyamsW84TjSjkglHJ ICgXi6qDJEfbHAPeka6WHJDl2xl7D3YZB7tDhhVR71VXx6m/xqT3vHCZhTrxexFA97rO lSFrp29uFN6gPhjVgwme4K1AGcUMUeROaQ9jobEk56BSlQV+AMygQZUR8w5fpZ6mdiJx 3DApKE24lGdmvxwGHCd5xXvAjRFOFF04MthFxSr6aaUXaiOh0gnTUwhVd66u3r3A09qX VQx8w2gvbA+3Qf1P9V7CRnHvQ7uGWdLBIgTXdyL0c2/Oq0vlZ+59oI1wZUsA6wBnKzLc S8dg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=X5Apuv2cyt0MITmcIM7M4objA2kYP9T31JBKiEBK3jU=; b=VX3/81h8TWRPIMybi5oowgbvK6tRYORXS6St/q1oDMpa7/2J1QoYaxxtyXaCHTplKO ixXIune04l9EduWD5vzTU7uOgSOeHzm0OAFQHigtPW71fGqw9wOUjH4t/fwok9FRuUHu ii9OGgHl5Pow5FOdOrkcX+H+1bLggAVeXuWzzE49T77wmj1DDf6m+mXmEERhpUdtDGE1 /8Swa1F8DAGSFdOlg/MWO4J6dTuXpqRhoCCxbZWdemhaZl7fR82n8RrVVamZD7jBsbUX NhP3wNpvOMQKO1UwJuXqZiWcjWLlquajgNR1bt6xEQrfXtOdqlSilSgGWpIgoJLrx94j CxAw== X-Gm-Message-State: AOAM53090GHi08GQl0MF5b3NfWdWG7fLcYyWpVmuJH9Ga0wgmnXoEV5k CsMG6YzEWjxgP6yVnBFyldo= X-Google-Smtp-Source: ABdhPJxnPyZtZkY6ekgkxjMPTKwMUt9r0RIr+J7EhR/LQgYRvKudMUQdE0fWlQvRR9lW6BHyz+0hbw== X-Received: by 2002:a17:906:c246:: with SMTP id bl6mr43553403ejb.80.1629840925089; Tue, 24 Aug 2021 14:35:25 -0700 (PDT) Received: from localhost.localdomain ([2a04:241e:502:1d80:ed0a:7326:fbac:b4c]) by smtp.gmail.com with ESMTPSA id d16sm12348357edu.8.2021.08.24.14.35.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 14:35:24 -0700 (PDT) From: Leonard Crestez To: Dmitry Safonov <0x7f454c46@gmail.com>, David Ahern , Shuah Khan Cc: Eric Dumazet , "David S. Miller" , Herbert Xu , Kuniyuki Iwashima , Hideaki YOSHIFUJI , Jakub Kicinski , Yuchung Cheng , Francesco Ruggeri , Mat Martineau , Christoph Paasch , Ivan Delalande , Priyaranjan Jha , Menglong Dong , netdev@vger.kernel.org, linux-crypto@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFCv3 01/15] tcp: authopt: Initial support and key management Date: Wed, 25 Aug 2021 00:34:34 +0300 Message-Id: <090898ac6f3345ec02999858c65c2ebb8cd274a8.1629840814.git.cdleonard@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This commit adds support to add and remove keys but does not use them further. Similar to tcp md5 a single pointer to a struct tcp_authopt_info* struct is added to struct tcp_sock, this avoids increasing memory usage. The data structures related to tcp_authopt are initialized on setsockopt and only freed on socket close. Signed-off-by: Leonard Crestez --- include/linux/tcp.h | 6 + include/net/tcp.h | 1 + include/net/tcp_authopt.h | 65 +++++++++++ include/uapi/linux/tcp.h | 79 ++++++++++++++ net/ipv4/Kconfig | 14 +++ net/ipv4/Makefile | 1 + net/ipv4/tcp.c | 27 +++++ net/ipv4/tcp_authopt.c | 223 ++++++++++++++++++++++++++++++++++++++ net/ipv4/tcp_ipv4.c | 2 + 9 files changed, 418 insertions(+) create mode 100644 include/net/tcp_authopt.h create mode 100644 net/ipv4/tcp_authopt.c diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 48d8a363319e..cfddfc720b00 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -140,10 +140,12 @@ struct tcp_request_sock { static inline struct tcp_request_sock *tcp_rsk(const struct request_sock *req) { return (struct tcp_request_sock *)req; } +struct tcp_authopt_info; + struct tcp_sock { /* inet_connection_sock has to be the first member of tcp_sock */ struct inet_connection_sock inet_conn; u16 tcp_header_len; /* Bytes of tcp header to send */ u16 gso_segs; /* Max number of segs per GSO packet */ @@ -403,10 +405,14 @@ struct tcp_sock { /* TCP MD5 Signature Option information */ struct tcp_md5sig_info __rcu *md5sig_info; #endif +#ifdef CONFIG_TCP_AUTHOPT + struct tcp_authopt_info __rcu *authopt_info; +#endif + /* TCP fastopen related information */ struct tcp_fastopen_request *fastopen_req; /* fastopen_rsk points to request_sock that resulted in this big * socket. Used to retransmit SYNACKs etc. */ diff --git a/include/net/tcp.h b/include/net/tcp.h index 3166dc15d7d6..bb76554e8fe5 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -182,10 +182,11 @@ void tcp_time_wait(struct sock *sk, int state, int timeo); #define TCPOPT_WINDOW 3 /* Window scaling */ #define TCPOPT_SACK_PERM 4 /* SACK Permitted */ #define TCPOPT_SACK 5 /* SACK Block */ #define TCPOPT_TIMESTAMP 8 /* Better RTT estimations/PAWS */ #define TCPOPT_MD5SIG 19 /* MD5 Signature (RFC2385) */ +#define TCPOPT_AUTHOPT 29 /* Auth Option (RFC5925) */ #define TCPOPT_MPTCP 30 /* Multipath TCP (RFC6824) */ #define TCPOPT_FASTOPEN 34 /* Fast open (RFC7413) */ #define TCPOPT_EXP 254 /* Experimental */ /* Magic number to be after the option value for sharing TCP * experimental options. See draft-ietf-tcpm-experimental-options-00.txt diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h new file mode 100644 index 000000000000..b4277112b506 --- /dev/null +++ b/include/net/tcp_authopt.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _LINUX_TCP_AUTHOPT_H +#define _LINUX_TCP_AUTHOPT_H + +#include + +/** + * struct tcp_authopt_key_info - Representation of a Master Key Tuple as per RFC5925 + * + * Key structure lifetime is only protected by RCU so readers needs to hold a + * single rcu_read_lock until they're done with the key. + */ +struct tcp_authopt_key_info { + struct hlist_node node; + struct rcu_head rcu; + /* Local identifier */ + u32 local_id; + u32 flags; + /* Wire identifiers */ + u8 send_id, recv_id; + u8 alg_id; + u8 keylen; + u8 key[TCP_AUTHOPT_MAXKEYLEN]; + struct sockaddr_storage addr; +}; + +/** + * struct tcp_authopt_info - Per-socket information regarding tcp_authopt + * + * This is lazy-initialized in order to avoid increasing memory usage for + * regular TCP sockets. Once created it is only destroyed on socket close. + */ +struct tcp_authopt_info { + /** @head: List of tcp_authopt_key_info */ + struct hlist_head head; + struct rcu_head rcu; + u32 flags; + u32 src_isn; + u32 dst_isn; +}; + +#ifdef CONFIG_TCP_AUTHOPT +void tcp_authopt_clear(struct sock *sk); +int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen); +int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *key); +int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen); +#else +static inline int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen) +{ + return -ENOPROTOOPT; +} +static inline int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *key) +{ + return -ENOPROTOOPT; +} +static inline void tcp_authopt_clear(struct sock *sk) +{ +} +static inline int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen) +{ + return -ENOPROTOOPT; +} +#endif + +#endif /* _LINUX_TCP_AUTHOPT_H */ diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h index 8fc09e8638b3..575162e7e281 100644 --- a/include/uapi/linux/tcp.h +++ b/include/uapi/linux/tcp.h @@ -126,10 +126,12 @@ enum { #define TCP_INQ 36 /* Notify bytes available to read as a cmsg on read */ #define TCP_CM_INQ TCP_INQ #define TCP_TX_DELAY 37 /* delay outgoing packets by XX usec */ +#define TCP_AUTHOPT 38 /* TCP Authentication Option (RFC5925) */ +#define TCP_AUTHOPT_KEY 39 /* TCP Authentication Option Key (RFC5925) */ #define TCP_REPAIR_ON 1 #define TCP_REPAIR_OFF 0 #define TCP_REPAIR_OFF_NO_WP -1 /* Turn off without window probes */ @@ -340,10 +342,87 @@ struct tcp_diag_md5sig { __u16 tcpm_keylen; __be32 tcpm_addr[4]; __u8 tcpm_key[TCP_MD5SIG_MAXKEYLEN]; }; +/** + * enum tcp_authopt_flag - flags for `tcp_authopt.flags` + */ +enum tcp_authopt_flag { + /** + * @TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED: + * Configure behavior of segments with TCP-AO coming from hosts for which no + * key is configured. The default recommended by RFC is to silently accept + * such connections. + */ + TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED = (1 << 2), +}; + +/** + * struct tcp_authopt - Per-socket options related to TCP Authentication Option + */ +struct tcp_authopt { + /** @flags: Combination of &enum tcp_authopt_flag */ + __u32 flags; +}; + +/** + * enum tcp_authopt_key_flag - flags for `tcp_authopt.flags` + * + * @TCP_AUTHOPT_KEY_DEL: Delete the key by local_id and ignore all other fields. + * @TCP_AUTHOPT_KEY_EXCLUDE_OPTS: Exclude TCP options from signature. + * @TCP_AUTHOPT_KEY_ADDR_BIND: Key only valid for `tcp_authopt.addr` + */ +enum tcp_authopt_key_flag { + TCP_AUTHOPT_KEY_DEL = (1 << 0), + TCP_AUTHOPT_KEY_EXCLUDE_OPTS = (1 << 1), + TCP_AUTHOPT_KEY_ADDR_BIND = (1 << 2), +}; + +/** + * enum tcp_authopt_alg - Algorithms for TCP Authentication Option + */ +enum tcp_authopt_alg { + TCP_AUTHOPT_ALG_HMAC_SHA_1_96 = 1, + TCP_AUTHOPT_ALG_AES_128_CMAC_96 = 2, +}; + +/* for TCP_AUTHOPT_KEY socket option */ +#define TCP_AUTHOPT_MAXKEYLEN 80 + +/** + * struct tcp_authopt_key - TCP Authentication KEY + * + * Key are identified by the combination of: + * - send_id + * - recv_id + * - addr (iff TCP_AUTHOPT_KEY_ADDR_BIND) + * + * RFC5925 requires that key ids must not overlap for the same TCP connection. + * This is not enforced by linux. + */ +struct tcp_authopt_key { + /** @flags: Combination of &enum tcp_authopt_key_flag */ + __u32 flags; + /** @send_id: keyid value for send */ + __u8 send_id; + /** @recv_id: keyid value for receive */ + __u8 recv_id; + /** @alg: One of &enum tcp_authopt_alg */ + __u8 alg; + /** @keylen: Length of the key buffer */ + __u8 keylen; + /** @key: Secret key */ + __u8 key[TCP_AUTHOPT_MAXKEYLEN]; + /** + * @addr: Key is only valid for this address + * + * Ignored unless TCP_AUTHOPT_KEY_ADDR_BIND flag is set + */ + struct __kernel_sockaddr_storage addr; +}; + /* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */ #define TCP_RECEIVE_ZEROCOPY_FLAG_TLB_CLEAN_HINT 0x1 struct tcp_zerocopy_receive { __u64 address; /* in: address of mapping */ diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig index 87983e70f03f..6459f4ea6f1d 100644 --- a/net/ipv4/Kconfig +++ b/net/ipv4/Kconfig @@ -740,5 +740,19 @@ config TCP_MD5SIG RFC2385 specifies a method of giving MD5 protection to TCP sessions. Its main (only?) use is to protect BGP sessions between core routers on the Internet. If unsure, say N. + +config TCP_AUTHOPT + bool "TCP: Authentication Option support (RFC5925)" + select CRYPTO + select CRYPTO_SHA1 + select CRYPTO_HMAC + select CRYPTO_AES + select CRYPTO_CMAC + help + RFC5925 specifies a new method of giving protection to TCP sessions. + Its intended use is to protect BGP sessions between core routers + on the Internet. It obsoletes TCP MD5 (RFC2385) but is incompatible. + + If unsure, say N. diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile index bbdd9c44f14e..d336f32ce177 100644 --- a/net/ipv4/Makefile +++ b/net/ipv4/Makefile @@ -59,10 +59,11 @@ obj-$(CONFIG_TCP_CONG_NV) += tcp_nv.o obj-$(CONFIG_TCP_CONG_VENO) += tcp_veno.o 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_TCP_AUTHOPT) += tcp_authopt.o obj-$(CONFIG_NET_SOCK_MSG) += tcp_bpf.o obj-$(CONFIG_BPF_SYSCALL) += udp_bpf.o obj-$(CONFIG_NETLABEL) += cipso_ipv4.o obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \ diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index f931def6302e..fd90e80afa2c 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -271,10 +271,11 @@ #include #include #include #include +#include #include #include #include #include @@ -3573,10 +3574,16 @@ static int do_tcp_setsockopt(struct sock *sk, int level, int optname, case TCP_MD5SIG: case TCP_MD5SIG_EXT: err = tp->af_specific->md5_parse(sk, optname, optval, optlen); break; #endif + case TCP_AUTHOPT: + err = tcp_set_authopt(sk, optval, optlen); + break; + case TCP_AUTHOPT_KEY: + err = tcp_set_authopt_key(sk, optval, optlen); + break; case TCP_USER_TIMEOUT: /* Cap the max time in ms TCP will retry or probe the window * before giving up and aborting (ETIMEDOUT) a connection. */ if (val < 0) @@ -4219,10 +4226,30 @@ static int do_tcp_getsockopt(struct sock *sk, int level, if (!err && copy_to_user(optval, &zc, len)) err = -EFAULT; return err; } #endif +#ifdef CONFIG_TCP_AUTHOPT + case TCP_AUTHOPT: { + struct tcp_authopt info; + + if (get_user(len, optlen)) + return -EFAULT; + + lock_sock(sk); + tcp_get_authopt_val(sk, &info); + release_sock(sk); + + len = min_t(unsigned int, len, sizeof(info)); + if (put_user(len, optlen)) + return -EFAULT; + if (copy_to_user(optval, &info, len)) + return -EFAULT; + return 0; + } +#endif + default: return -ENOPROTOOPT; } if (put_user(len, optlen)) diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c new file mode 100644 index 000000000000..f6dddc5775ff --- /dev/null +++ b/net/ipv4/tcp_authopt.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +/* checks that ipv4 or ipv6 addr matches. */ +static bool ipvx_addr_match(struct sockaddr_storage *a1, + struct sockaddr_storage *a2) +{ + if (a1->ss_family != a2->ss_family) + return false; + if (a1->ss_family == AF_INET && memcmp( + &((struct sockaddr_in *)a1)->sin_addr, + &((struct sockaddr_in *)a2)->sin_addr, + sizeof(struct in_addr))) + return false; + if (a1->ss_family == AF_INET6 && memcmp( + &((struct sockaddr_in6 *)a1)->sin6_addr, + &((struct sockaddr_in6 *)a2)->sin6_addr, + sizeof(struct in6_addr))) + return false; + return true; +} + +static bool tcp_authopt_key_match_exact(struct tcp_authopt_key_info *info, + struct tcp_authopt_key *key) +{ + if (info->send_id != key->send_id) + return false; + if (info->recv_id != key->recv_id) + return false; + if ((info->flags & TCP_AUTHOPT_KEY_ADDR_BIND) != (key->recv_id & TCP_AUTHOPT_KEY_ADDR_BIND)) + return false; + if (info->flags & TCP_AUTHOPT_KEY_ADDR_BIND) + if (!ipvx_addr_match(&info->addr, &key->addr)) + return false; + + return true; +} + +static struct tcp_authopt_key_info *tcp_authopt_key_lookup_exact(const struct sock *sk, + struct tcp_authopt_info *info, + struct tcp_authopt_key *ukey) +{ + struct tcp_authopt_key_info *key_info; + + hlist_for_each_entry_rcu(key_info, &info->head, node, lockdep_sock_is_held(sk)) + if (tcp_authopt_key_match_exact(key_info, ukey)) + return key_info; + + return NULL; +} + +static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk) +{ + struct tcp_sock *tp = tcp_sk(sk); + struct tcp_authopt_info *info; + + info = rcu_dereference_check(tp->authopt_info, lockdep_sock_is_held(sk)); + if (info) + return info; + + info = kmalloc(sizeof(*info), GFP_KERNEL | __GFP_ZERO); + if (!info) + return ERR_PTR(-ENOMEM); + + sk_nocaps_add(sk, NETIF_F_GSO_MASK); + INIT_HLIST_HEAD(&info->head); + rcu_assign_pointer(tp->authopt_info, info); + + return info; +} + +#define TCP_AUTHOPT_KNOWN_FLAGS ( \ + TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED) + +int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen) +{ + struct tcp_authopt opt; + struct tcp_authopt_info *info; + + sock_owned_by_me(sk); + + /* If userspace optlen is too short fill the rest with zeros */ + if (optlen > sizeof(opt)) + return -EINVAL; + memset(&opt, 0, sizeof(opt)); + if (copy_from_sockptr(&opt, optval, optlen)) + return -EFAULT; + + if (opt.flags & ~TCP_AUTHOPT_KNOWN_FLAGS) + return -EINVAL; + + info = __tcp_authopt_info_get_or_create(sk); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->flags = opt.flags & TCP_AUTHOPT_KNOWN_FLAGS; + + return 0; +} + +int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt) +{ + struct tcp_sock *tp = tcp_sk(sk); + struct tcp_authopt_info *info; + + sock_owned_by_me(sk); + + memset(opt, 0, sizeof(*opt)); + info = rcu_dereference_check(tp->authopt_info, lockdep_sock_is_held(sk)); + if (!info) + return -EINVAL; + + opt->flags = info->flags & TCP_AUTHOPT_KNOWN_FLAGS; + + return 0; +} + +static void tcp_authopt_key_del(struct sock *sk, + struct tcp_authopt_info *info, + struct tcp_authopt_key_info *key) +{ + hlist_del_rcu(&key->node); + atomic_sub(sizeof(*key), &sk->sk_omem_alloc); + kfree_rcu(key, rcu); +} + +/* free info and keys but don't touch tp->authopt_info */ +static void __tcp_authopt_info_free(struct sock *sk, struct tcp_authopt_info *info) +{ + struct hlist_node *n; + struct tcp_authopt_key_info *key; + + hlist_for_each_entry_safe(key, n, &info->head, node) + tcp_authopt_key_del(sk, info, key); + kfree_rcu(info, rcu); +} + +/* free everything and clear tcp_sock.authopt_info to NULL */ +void tcp_authopt_clear(struct sock *sk) +{ + struct tcp_authopt_info *info; + + info = rcu_dereference_protected(tcp_sk(sk)->authopt_info, lockdep_sock_is_held(sk)); + if (info) { + __tcp_authopt_info_free(sk, info); + tcp_sk(sk)->authopt_info = NULL; + } +} + +#define TCP_AUTHOPT_KEY_KNOWN_FLAGS ( \ + TCP_AUTHOPT_KEY_DEL | \ + TCP_AUTHOPT_KEY_EXCLUDE_OPTS | \ + TCP_AUTHOPT_KEY_ADDR_BIND) + +int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen) +{ + struct tcp_authopt_key opt; + struct tcp_authopt_info *info; + struct tcp_authopt_key_info *key_info; + + sock_owned_by_me(sk); + + /* If userspace optlen is too short fill the rest with zeros */ + if (optlen > sizeof(opt)) + return -EINVAL; + memset(&opt, 0, sizeof(opt)); + if (copy_from_sockptr(&opt, optval, optlen)) + return -EFAULT; + + if (opt.flags & ~TCP_AUTHOPT_KEY_KNOWN_FLAGS) + return -EINVAL; + + if (opt.keylen > TCP_AUTHOPT_MAXKEYLEN) + return -EINVAL; + + /* Delete is a special case: */ + if (opt.flags & TCP_AUTHOPT_KEY_DEL) { + info = rcu_dereference_check(tcp_sk(sk)->authopt_info, lockdep_sock_is_held(sk)); + if (!info) + return -ENOENT; + key_info = tcp_authopt_key_lookup_exact(sk, info, &opt); + if (!key_info) + return -ENOENT; + tcp_authopt_key_del(sk, info, key_info); + return 0; + } + + /* check key family */ + if (opt.flags & TCP_AUTHOPT_KEY_ADDR_BIND) { + if (sk->sk_family != opt.addr.ss_family) + return -EINVAL; + } + + /* Initialize tcp_authopt_info if not already set */ + info = __tcp_authopt_info_get_or_create(sk); + if (IS_ERR(info)) + return PTR_ERR(info); + + /* If an old key exists with exact ID then remove and replace. + * RCU-protected readers might observe both and pick any. + */ + key_info = tcp_authopt_key_lookup_exact(sk, info, &opt); + if (key_info) + tcp_authopt_key_del(sk, info, key_info); + key_info = sock_kmalloc(sk, sizeof(*key_info), GFP_KERNEL | __GFP_ZERO); + if (!key_info) + return -ENOMEM; + key_info->flags = opt.flags & TCP_AUTHOPT_KEY_KNOWN_FLAGS; + key_info->send_id = opt.send_id; + key_info->recv_id = opt.recv_id; + key_info->alg_id = opt.alg; + key_info->keylen = opt.keylen; + memcpy(key_info->key, opt.key, opt.keylen); + memcpy(&key_info->addr, &opt.addr, sizeof(key_info->addr)); + hlist_add_head_rcu(&key_info->node, &info->head); + + return 0; +} diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 2e62e0d6373a..1348615c7576 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -60,10 +60,11 @@ #include #include #include #include +#include #include #include #include #include #include @@ -2256,10 +2257,11 @@ void tcp_v4_destroy_sock(struct sock *sk) tcp_clear_md5_list(sk); kfree_rcu(rcu_dereference_protected(tp->md5sig_info, 1), rcu); tp->md5sig_info = NULL; } #endif + tcp_authopt_clear(sk); /* Clean up a referenced TCP bind bucket. */ if (inet_csk(sk)->icsk_bind_hash) inet_put_port(sk); From patchwork Tue Aug 24 21:34:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 502152 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 21326C43214 for ; Tue, 24 Aug 2021 21:35:49 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 0AFEE6135F for ; Tue, 24 Aug 2021 21:35:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237358AbhHXVgc (ORCPT ); Tue, 24 Aug 2021 17:36:32 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35404 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235483AbhHXVgT (ORCPT ); Tue, 24 Aug 2021 17:36:19 -0400 Received: from mail-ej1-x632.google.com (mail-ej1-x632.google.com [IPv6:2a00:1450:4864:20::632]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3E2BBC0617A8; Tue, 24 Aug 2021 14:35:34 -0700 (PDT) Received: by mail-ej1-x632.google.com with SMTP id me10so18846182ejb.11; Tue, 24 Aug 2021 14:35:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=rH4bYGauaxv2Wl+jiZaPd02GWdhR4IMZOZfUFE7drY0=; b=MWO7txWzOK+UA7qYaeB0ByfyroreBfP5IoTlKFGUWageT3HiILxZxtvz0OtmnuZq5N b/HUGHnhyElFkfuSmlPd6LhciErcOlh/4EWNqwiYBXY3eb91OXeUV4ZNyFUayypW2pTW KnDgNLk9/IZi7bygKl6p8WrpCqtioV+13geeytH4/U9/L61HJY0EdkKFRYlJV2PfhVG4 bYdX+aa0A3RWlP7ZBpHfG3TAFutkyT8xKCFlLBEN7f8OE0Q1DT7i86OYNIzps3iDY3Nh YphheMSppTlCHVU69/xxnANkk1WZ2V5NpXAnxFiuKtfXnV+ails3XLFvYtdOehhtv7ar 6vPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=rH4bYGauaxv2Wl+jiZaPd02GWdhR4IMZOZfUFE7drY0=; b=uBIYuWDrwYX/ejkWjIOorfKbFCMoP3mrOAmJl0xKXWqSMRv4eVW3phWJ48k4hfHurl I3vH1FPFSeGXWNkLpNJxMc8ueAWPwTOn7JKctHx7aE7II9sq5LD7zDAVJXLSIpCbhzJp UiYJl1K29+LZXfinaPb7tB0FRxLd+xJsedzx6MF7l+jopVpzMEfthFtrilimYXoYmaAS 1CeTZaEiLjz2DaNsJqJ3/VNIPWwp9UHLdHCUl1BiGeEYHbtTXg/TTTHIY9KyY5jvuqHh hIwR4vwpa9kC07MxnyitNr5s9ulXYspyCBu8MGIG+G7vbA+kj8w84K3kQ0rbJ+rq/kGh 9Okg== X-Gm-Message-State: AOAM530qGOoCPtItt1VZOd8NCaQ4E5Wy3H76KwchziiE4mqMJgQ7sPMW uKu1iCqatCr0tN3Oxm5TLeA= X-Google-Smtp-Source: ABdhPJznRja0rsYkh10d5kdpNeqHjeMPeBMFgYdTO5pZTJuljEJfr1aDNsVRZQGwleqza+aX8ra4+A== X-Received: by 2002:a17:906:378f:: with SMTP id n15mr16338909ejc.467.1629840932724; Tue, 24 Aug 2021 14:35:32 -0700 (PDT) Received: from localhost.localdomain ([2a04:241e:502:1d80:ed0a:7326:fbac:b4c]) by smtp.gmail.com with ESMTPSA id d16sm12348357edu.8.2021.08.24.14.35.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 14:35:32 -0700 (PDT) From: Leonard Crestez To: Dmitry Safonov <0x7f454c46@gmail.com>, David Ahern , Shuah Khan Cc: Eric Dumazet , "David S. Miller" , Herbert Xu , Kuniyuki Iwashima , Hideaki YOSHIFUJI , Jakub Kicinski , Yuchung Cheng , Francesco Ruggeri , Mat Martineau , Christoph Paasch , Ivan Delalande , Priyaranjan Jha , Menglong Dong , netdev@vger.kernel.org, linux-crypto@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFCv3 05/15] tcp: authopt: Add crypto initialization Date: Wed, 25 Aug 2021 00:34:38 +0300 Message-Id: X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org The crypto_shash API is used in order to compute packet signatures. The API comes with several unfortunate limitations: 1) Allocating a crypto_shash can sleep and must be done in user context. 2) Packet signatures must be computed in softirq context 3) Packet signatures use dynamic "traffic keys" which require exclusive access to crypto_shash for crypto_setkey. The solution is to allocate one crypto_shash for each possible cpu for each algorithm at setsockopt time. The per-cpu tfm is then borrowed from softirq context, signatures are computed and the tfm is returned. The pool for each algorithm is reference counted, initialized at setsockopt time and released in tcp_authopt_key_info's rcu callback Signed-off-by: Leonard Crestez --- include/net/tcp_authopt.h | 3 + net/ipv4/tcp_authopt.c | 177 +++++++++++++++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 2 deletions(-) diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h index b4277112b506..c9ee2059b442 100644 --- a/include/net/tcp_authopt.h +++ b/include/net/tcp_authopt.h @@ -2,10 +2,12 @@ #ifndef _LINUX_TCP_AUTHOPT_H #define _LINUX_TCP_AUTHOPT_H #include +struct tcp_authopt_alg_imp; + /** * struct tcp_authopt_key_info - Representation of a Master Key Tuple as per RFC5925 * * Key structure lifetime is only protected by RCU so readers needs to hold a * single rcu_read_lock until they're done with the key. @@ -20,10 +22,11 @@ struct tcp_authopt_key_info { u8 send_id, recv_id; u8 alg_id; u8 keylen; u8 key[TCP_AUTHOPT_MAXKEYLEN]; struct sockaddr_storage addr; + struct tcp_authopt_alg_imp *alg; }; /** * struct tcp_authopt_info - Per-socket information regarding tcp_authopt * diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c index f6dddc5775ff..ce560bd88903 100644 --- a/net/ipv4/tcp_authopt.c +++ b/net/ipv4/tcp_authopt.c @@ -4,10 +4,161 @@ #include #include #include #include +/* All current algorithms have a mac length of 12 but crypto API digestsize can be larger */ +#define TCP_AUTHOPT_MAXMACBUF 20 +#define TCP_AUTHOPT_MAX_TRAFFIC_KEY_LEN 20 + +struct tcp_authopt_alg_imp { + /* Name of algorithm in crypto-api */ + const char *alg_name; + /* One of the TCP_AUTHOPT_ALG_* constants from uapi */ + u8 alg_id; + /* Length of traffic key */ + u8 traffic_key_len; + /* Length of mac in TCP option */ + u8 maclen; + + /* shared crypto_shash */ + spinlock_t lock; + int ref_cnt; + struct crypto_shash *tfm; +}; + +static struct tcp_authopt_alg_imp tcp_authopt_alg_list[] = { + { + .alg_id = TCP_AUTHOPT_ALG_HMAC_SHA_1_96, + .alg_name = "hmac(sha1)", + .traffic_key_len = 20, + .maclen = 12, + .lock = __SPIN_LOCK_UNLOCKED(tcp_authopt_alg_list[0].lock), + }, + { + .alg_id = TCP_AUTHOPT_ALG_AES_128_CMAC_96, + .alg_name = "cmac(aes)", + .traffic_key_len = 16, + .maclen = 12, + .lock = __SPIN_LOCK_UNLOCKED(tcp_authopt_alg_list[1].lock), + }, +}; + +/* get a pointer to the tcp_authopt_alg instance or NULL if id invalid */ +static inline struct tcp_authopt_alg_imp *tcp_authopt_alg_get(int alg_num) +{ + if (alg_num <= 0 || alg_num > 2) + return NULL; + return &tcp_authopt_alg_list[alg_num - 1]; +} + +/* Mark an algorithm as in-use from user context */ +static int tcp_authopt_alg_require(struct tcp_authopt_alg_imp *alg) +{ + struct crypto_shash *tfm = NULL; + bool need_init = false; + + might_sleep(); + + /* If we're the first user then we need to initialize shash but we might lose the race. */ + spin_lock_bh(&alg->lock); + WARN_ON(alg->ref_cnt < 0); + if (alg->ref_cnt == 0) + need_init = true; + else + ++alg->ref_cnt; + spin_unlock_bh(&alg->lock); + + /* Already initialized */ + if (!need_init) + return 0; + + tfm = crypto_alloc_shash(alg->alg_name, 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + spin_lock_bh(&alg->lock); + if (alg->ref_cnt == 0) + /* race won */ + alg->tfm = tfm; + else + /* race lost, free tfm later */ + need_init = false; + ++alg->ref_cnt; + spin_unlock_bh(&alg->lock); + + if (!need_init) + crypto_free_shash(tfm); + else + pr_info("initialized tcp-ao %s", alg->alg_name); + + return 0; +} + +static void tcp_authopt_alg_release(struct tcp_authopt_alg_imp *alg) +{ + struct crypto_shash *tfm_to_free = NULL; + + spin_lock_bh(&alg->lock); + --alg->ref_cnt; + WARN_ON(alg->ref_cnt < 0); + if (alg->ref_cnt == 0) { + tfm_to_free = alg->tfm; + alg->tfm = NULL; + } + spin_unlock_bh(&alg->lock); + + if (tfm_to_free) { + pr_info("released tcp-ao %s", alg->alg_name); + crypto_free_shash(tfm_to_free); + } +} + +/* increase reference count on an algorithm that is already in use */ +static void tcp_authopt_alg_incref(struct tcp_authopt_alg_imp *alg) +{ + spin_lock_bh(&alg->lock); + WARN_ON(alg->ref_cnt <= 0); + ++alg->ref_cnt; + spin_unlock_bh(&alg->lock); +} + +static struct crypto_shash *tcp_authopt_alg_get_tfm(struct tcp_authopt_alg_imp *alg) +{ + spin_lock_bh(&alg->lock); + WARN_ON(alg->ref_cnt < 0); + return alg->tfm; +} + +static void tcp_authopt_alg_put_tfm(struct tcp_authopt_alg_imp *alg, struct crypto_shash *tfm) +{ + WARN_ON(tfm != alg->tfm); + spin_unlock_bh(&alg->lock); +} + +static struct crypto_shash *tcp_authopt_get_kdf_shash(struct tcp_authopt_key_info *key) +{ + return tcp_authopt_alg_get_tfm(key->alg); +} + +static void tcp_authopt_put_kdf_shash(struct tcp_authopt_key_info *key, + struct crypto_shash *tfm) +{ + return tcp_authopt_alg_put_tfm(key->alg, tfm); +} + +static struct crypto_shash *tcp_authopt_get_mac_shash(struct tcp_authopt_key_info *key) +{ + return tcp_authopt_alg_get_tfm(key->alg); +} + +static void tcp_authopt_put_mac_shash(struct tcp_authopt_key_info *key, + struct crypto_shash *tfm) +{ + return tcp_authopt_alg_put_tfm(key->alg, tfm); +} + /* checks that ipv4 or ipv6 addr matches. */ static bool ipvx_addr_match(struct sockaddr_storage *a1, struct sockaddr_storage *a2) { if (a1->ss_family != a2->ss_family) @@ -118,17 +269,25 @@ int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt) opt->flags = info->flags & TCP_AUTHOPT_KNOWN_FLAGS; return 0; } +static void tcp_authopt_key_free_rcu(struct rcu_head *rcu) +{ + struct tcp_authopt_key_info *key = container_of(rcu, struct tcp_authopt_key_info, rcu); + + tcp_authopt_alg_release(key->alg); + kfree(key); +} + static void tcp_authopt_key_del(struct sock *sk, struct tcp_authopt_info *info, struct tcp_authopt_key_info *key) { hlist_del_rcu(&key->node); atomic_sub(sizeof(*key), &sk->sk_omem_alloc); - kfree_rcu(key, rcu); + call_rcu(&key->rcu, tcp_authopt_key_free_rcu); } /* free info and keys but don't touch tp->authopt_info */ static void __tcp_authopt_info_free(struct sock *sk, struct tcp_authopt_info *info) { @@ -160,10 +319,12 @@ void tcp_authopt_clear(struct sock *sk) int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen) { struct tcp_authopt_key opt; struct tcp_authopt_info *info; struct tcp_authopt_key_info *key_info; + struct tcp_authopt_alg_imp *alg; + int err; sock_owned_by_me(sk); /* If userspace optlen is too short fill the rest with zeros */ if (optlen > sizeof(opt)) @@ -199,23 +360,35 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen) /* Initialize tcp_authopt_info if not already set */ info = __tcp_authopt_info_get_or_create(sk); if (IS_ERR(info)) return PTR_ERR(info); + /* check the algorithm */ + alg = tcp_authopt_alg_get(opt.alg); + if (!alg) + return -EINVAL; + WARN_ON(alg->alg_id != opt.alg); + err = tcp_authopt_alg_require(alg); + if (err) + return err; + /* If an old key exists with exact ID then remove and replace. * RCU-protected readers might observe both and pick any. */ key_info = tcp_authopt_key_lookup_exact(sk, info, &opt); if (key_info) tcp_authopt_key_del(sk, info, key_info); key_info = sock_kmalloc(sk, sizeof(*key_info), GFP_KERNEL | __GFP_ZERO); - if (!key_info) + if (!key_info) { + tcp_authopt_alg_release(alg); return -ENOMEM; + } key_info->flags = opt.flags & TCP_AUTHOPT_KEY_KNOWN_FLAGS; key_info->send_id = opt.send_id; key_info->recv_id = opt.recv_id; key_info->alg_id = opt.alg; + key_info->alg = alg; key_info->keylen = opt.keylen; memcpy(key_info->key, opt.key, opt.keylen); memcpy(&key_info->addr, &opt.addr, sizeof(key_info->addr)); hlist_add_head_rcu(&key_info->node, &info->head); From patchwork Tue Aug 24 21:34:39 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 502151 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C2A9EC43216 for ; Tue, 24 Aug 2021 21:35:57 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id AAB3A61165 for ; Tue, 24 Aug 2021 21:35:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238609AbhHXVgk (ORCPT ); Tue, 24 Aug 2021 17:36:40 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35398 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235320AbhHXVgZ (ORCPT ); Tue, 24 Aug 2021 17:36:25 -0400 Received: from mail-ej1-x62d.google.com (mail-ej1-x62d.google.com [IPv6:2a00:1450:4864:20::62d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 14F6DC0613A3; Tue, 24 Aug 2021 14:35:36 -0700 (PDT) Received: by mail-ej1-x62d.google.com with SMTP id ia27so25007328ejc.10; Tue, 24 Aug 2021 14:35:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=UpoKD8cnt+TMNUUMUTbuPjNheKlkJCRA/8wZWTruXJo=; b=A8n0O9M0FeKcNtb/mOpYiH1mUACWp7ZUIuysYx19Ugrh/PbXCXg6D21+xcjNt8DrC/ zsriVTTVimmlQGTofEfgxkwuRMbLpgSk30oENNhyEo3hiFGQKZri+5fZ83kNRL2LQXWm bpXnthmHB5/e5dRMkfB0/p6k7/LEeLtZsgR0eSxi52eL7vTqn4ORh9PU6n7/GmJDruS5 h+fWOytZT2oA13S8FNVjoDQJYqyEwJ/wOLRf8KnKUfMgzYWU2cSOg76AxD3b/msdNwyT N8Qgn3BBB4GlAz7ugxampq2GJFyg+VRmPEf9a1zvFniDuE8zrCyipVanFPwayGURWWIh lTbw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=UpoKD8cnt+TMNUUMUTbuPjNheKlkJCRA/8wZWTruXJo=; b=jZEYiWu8nlfaFHO6nknB54QvAoY9CAMMVEe+68vFvboZ94ZXF4ZNcdwO9ffOyrO863 yKO6bns5tISCYHq2hH9WyQ8JTEgmvLpJc9fAexQFl1+hLCLF5erdg3UI0be6YQpkytUy BXF9X8tMLREr1MGpD8B8zXvB9vtUApZKc3IcahSVyUu+jJU6Z6C2WWKsMsnnZsqfxJos fnSmveIDWuRKlUejgz4jxYQz8T+tAqMb+a26SdeFB8gw0VJ+B0Bx09Ot7Nt0266afi+U 7IInRxPqPjahL9T3t+kuEXw2yZ9ywKKnE2EnOKOGina2glCcHfAORYY/6oGS2ORZ7x5g 9XOA== X-Gm-Message-State: AOAM5312orxeJ1IZVMRMH8SwSmhfvoe6iswfSaxCKuTLoHhpujQaBDsM 2Su+iKIoTtQbpip2eosw+Ww= X-Google-Smtp-Source: ABdhPJyWjNuLuE4uGZYYl5evVz3HYD7lTKPK+PIIgex8c4M3CsbQ8ruWNkdHMcrSPYixZR0InE2FNw== X-Received: by 2002:a17:906:e0c:: with SMTP id l12mr42826684eji.301.1629840934605; Tue, 24 Aug 2021 14:35:34 -0700 (PDT) Received: from localhost.localdomain ([2a04:241e:502:1d80:ed0a:7326:fbac:b4c]) by smtp.gmail.com with ESMTPSA id d16sm12348357edu.8.2021.08.24.14.35.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 14:35:34 -0700 (PDT) From: Leonard Crestez To: Dmitry Safonov <0x7f454c46@gmail.com>, David Ahern , Shuah Khan Cc: Eric Dumazet , "David S. Miller" , Herbert Xu , Kuniyuki Iwashima , Hideaki YOSHIFUJI , Jakub Kicinski , Yuchung Cheng , Francesco Ruggeri , Mat Martineau , Christoph Paasch , Ivan Delalande , Priyaranjan Jha , Menglong Dong , netdev@vger.kernel.org, linux-crypto@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFCv3 06/15] tcp: authopt: Compute packet signatures Date: Wed, 25 Aug 2021 00:34:39 +0300 Message-Id: <3e8956d3895a05ca1e7672531cf88b5445b456f3.1629840814.git.cdleonard@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Computing tcp authopt packet signatures is a two step process: * traffic key is computed based on tcp 4-tuple, initial sequence numbers and the secret key. * packet mac is computed based on traffic key and content of individual packets. The traffic key could be cached for established sockets but it is not. A single code path exists for ipv4/ipv6 and input/output. This keeps the code short but slightly slower due to lots of conditionals. On output we read remote IP address from socket members on output, we can't use skb network header because it's computed after TCP options. On input we read remote IP address from skb network headers, we can't use socket binding members because those are not available for SYN. Signed-off-by: Leonard Crestez --- net/ipv4/tcp_authopt.c | 467 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c index ce560bd88903..2a3463ad6896 100644 --- a/net/ipv4/tcp_authopt.c +++ b/net/ipv4/tcp_authopt.c @@ -392,5 +392,472 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen) memcpy(&key_info->addr, &opt.addr, sizeof(key_info->addr)); hlist_add_head_rcu(&key_info->node, &info->head); return 0; } + +/* feed traffic key into shash */ +static int tcp_authopt_shash_traffic_key(struct shash_desc *desc, + struct sock *sk, + struct sk_buff *skb, + bool input, + bool ipv6) +{ + struct tcphdr *th = tcp_hdr(skb); + int err; + __be32 sisn, disn; + __be16 digestbits = htons(crypto_shash_digestsize(desc->tfm) * 8); + + // RFC5926 section 3.1.1.1 + err = crypto_shash_update(desc, "\x01TCP-AO", 7); + if (err) + return err; + + /* Addresses from packet on input and from socket on output + * This is because on output MAC is computed before prepending IP header + */ + if (input) { + if (ipv6) + err = crypto_shash_update(desc, (u8 *)&ipv6_hdr(skb)->saddr, 32); + else + err = crypto_shash_update(desc, (u8 *)&ip_hdr(skb)->saddr, 8); + if (err) + return err; + } else { + if (ipv6) { + struct in6_addr *saddr; + struct in6_addr *daddr; + + saddr = &sk->sk_v6_rcv_saddr; + daddr = &sk->sk_v6_daddr; + err = crypto_shash_update(desc, (u8 *)&sk->sk_v6_rcv_saddr, 16); + if (err) + return err; + err = crypto_shash_update(desc, (u8 *)&sk->sk_v6_daddr, 16); + if (err) + return err; + } else { + err = crypto_shash_update(desc, (u8 *)&sk->sk_rcv_saddr, 4); + if (err) + return err; + err = crypto_shash_update(desc, (u8 *)&sk->sk_daddr, 4); + if (err) + return err; + } + } + + /* TCP ports from header */ + err = crypto_shash_update(desc, (u8 *)&th->source, 4); + if (err) + return err; + + /* special cases for SYN and SYN/ACK */ + if (th->syn && !th->ack) { + sisn = th->seq; + disn = 0; + } else if (th->syn && th->ack) { + sisn = th->seq; + disn = htonl(ntohl(th->ack_seq) - 1); + } else { + struct tcp_authopt_info *authopt_info; + + /* Fetching authopt_info like this means it's possible that authopt_info + * was deleted while we were hashing. If that happens we drop the packet + * which should be fine. + * + * A better solution might be to always pass info as a parameter, or + * compute traffic_key for established sockets separately. + */ + rcu_read_lock(); + authopt_info = rcu_dereference(tcp_sk(sk)->authopt_info); + if (!authopt_info) { + rcu_read_unlock(); + return -EINVAL; + } + /* Initial sequence numbers for ESTABLISHED connections from info */ + if (input) { + sisn = htonl(authopt_info->dst_isn); + disn = htonl(authopt_info->src_isn); + } else { + sisn = htonl(authopt_info->src_isn); + disn = htonl(authopt_info->dst_isn); + } + rcu_read_unlock(); + } + + err = crypto_shash_update(desc, (u8 *)&sisn, 4); + if (err) + return err; + err = crypto_shash_update(desc, (u8 *)&disn, 4); + if (err) + return err; + + err = crypto_shash_update(desc, (u8 *)&digestbits, 2); + if (err) + return err; + + return 0; +} + +/* Convert a variable-length key to a 16-byte fixed-length key for AES-CMAC + * This is described in RFC5926 section 3.1.1.2 + */ +static int aes_setkey_derived(struct crypto_shash *tfm, u8 *key, size_t keylen) +{ + static const u8 zeros[16] = {0}; + u8 derived_key[16]; + int err; + + if (WARN_ON(crypto_shash_digestsize(tfm) != 16)) + return -EINVAL; + err = crypto_shash_setkey(tfm, zeros, sizeof(zeros)); + if (err) + return err; + err = crypto_shash_tfm_digest(tfm, key, keylen, derived_key); + if (err) + return err; + return crypto_shash_setkey(tfm, derived_key, sizeof(derived_key)); +} + +static int tcp_authopt_get_traffic_key(struct sock *sk, + struct sk_buff *skb, + struct tcp_authopt_key_info *key, + bool input, + bool ipv6, + u8 *traffic_key) +{ + SHASH_DESC_ON_STACK(desc, kdf_tfm); + struct crypto_shash *kdf_tfm; + int err; + + kdf_tfm = tcp_authopt_get_kdf_shash(key); + if (IS_ERR(kdf_tfm)) + return PTR_ERR(kdf_tfm); + if (WARN_ON(crypto_shash_digestsize(kdf_tfm) != key->alg->traffic_key_len)) { + err = -EINVAL; + goto out; + } + + if (key->alg_id == TCP_AUTHOPT_ALG_AES_128_CMAC_96 && key->keylen != 16) { + err = aes_setkey_derived(kdf_tfm, key->key, key->keylen); + if (err) + goto out; + } else { + err = crypto_shash_setkey(kdf_tfm, key->key, key->keylen); + if (err) + goto out; + } + + desc->tfm = kdf_tfm; + err = crypto_shash_init(desc); + if (err) + goto out; + + err = tcp_authopt_shash_traffic_key(desc, sk, skb, input, ipv6); + if (err) + goto out; + + err = crypto_shash_final(desc, traffic_key); + if (err) + goto out; + //printk("traffic_key: %*phN\n", 20, traffic_key); + +out: + tcp_authopt_put_kdf_shash(key, kdf_tfm); + return err; +} + +static int crypto_shash_update_zero(struct shash_desc *desc, int len) +{ + u8 zero = 0; + int i, err; + + for (i = 0; i < len; ++i) { + err = crypto_shash_update(desc, &zero, 1); + if (err) + return err; + } + + return 0; +} + +static int tcp_authopt_hash_tcp4_pseudoheader(struct shash_desc *desc, + __be32 saddr, + __be32 daddr, + int nbytes) +{ + struct tcp4_pseudohdr phdr = { + .saddr = saddr, + .daddr = daddr, + .pad = 0, + .protocol = IPPROTO_TCP, + .len = htons(nbytes) + }; + return crypto_shash_update(desc, (u8 *)&phdr, sizeof(phdr)); +} + +static int tcp_authopt_hash_tcp6_pseudoheader(struct shash_desc *desc, + struct in6_addr *saddr, + struct in6_addr *daddr, + u32 plen) +{ + int err; + u32 buf[2]; + + buf[0] = htonl(plen); + buf[1] = htonl(IPPROTO_TCP); + + err = crypto_shash_update(desc, (u8 *)saddr, sizeof(*saddr)); + if (err) + return err; + err = crypto_shash_update(desc, (u8 *)daddr, sizeof(*daddr)); + if (err) + return err; + return crypto_shash_update(desc, (u8 *)&buf, sizeof(buf)); +} + +/* TCP authopt as found in header */ +struct tcphdr_authopt { + u8 num; + u8 len; + u8 keyid; + u8 rnextkeyid; + u8 mac[0]; +}; + +/* Find TCP_AUTHOPT in header. + * + * Returns pointer to TCP_AUTHOPT or NULL if not found. + */ +static u8 *tcp_authopt_find_option(struct tcphdr *th) +{ + int length = (th->doff << 2) - sizeof(*th); + u8 *ptr = (u8 *)(th + 1); + + while (length >= 2) { + int opcode = *ptr++; + int opsize; + + switch (opcode) { + case TCPOPT_EOL: + return NULL; + case TCPOPT_NOP: + length--; + continue; + default: + if (length < 2) + return NULL; + opsize = *ptr++; + if (opsize < 2) + return NULL; + if (opsize > length) + return NULL; + if (opcode == TCPOPT_AUTHOPT) + return ptr - 2; + } + ptr += opsize - 2; + length -= opsize; + } + return NULL; +} + +/** Hash tcphdr options. + * If include_options is false then only the TCPOPT_AUTHOPT option itself is hashed + * Maybe we could skip option parsing by assuming the AUTHOPT header is at hash_location-4? + */ +static int tcp_authopt_hash_opts(struct shash_desc *desc, + struct tcphdr *th, + bool include_options) +{ + int err; + /* start of options */ + u8 *tcp_opts = (u8 *)(th + 1); + /* end of options */ + u8 *tcp_data = ((u8 *)th) + th->doff * 4; + /* pointer to TCPOPT_AUTHOPT */ + u8 *authopt_ptr = tcp_authopt_find_option(th); + u8 authopt_len; + + if (!authopt_ptr) + return -EINVAL; + authopt_len = *(authopt_ptr + 1); + + if (include_options) { + err = crypto_shash_update(desc, tcp_opts, authopt_ptr - tcp_opts + 4); + if (err) + return err; + err = crypto_shash_update_zero(desc, authopt_len - 4); + if (err) + return err; + err = crypto_shash_update(desc, + authopt_ptr + authopt_len, + tcp_data - (authopt_ptr + authopt_len)); + if (err) + return err; + } else { + err = crypto_shash_update(desc, authopt_ptr, 4); + if (err) + return err; + err = crypto_shash_update_zero(desc, authopt_len - 4); + if (err) + return err; + } + + return 0; +} + +static int skb_shash_frags(struct shash_desc *desc, + struct sk_buff *skb) +{ + struct sk_buff *frag_iter; + int err, i; + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_frag_t *f = &skb_shinfo(skb)->frags[i]; + u32 p_off, p_len, copied; + struct page *p; + u8 *vaddr; + + skb_frag_foreach_page(f, skb_frag_off(f), skb_frag_size(f), + p, p_off, p_len, copied) { + vaddr = kmap_atomic(p); + err = crypto_shash_update(desc, vaddr + p_off, p_len); + kunmap_atomic(vaddr); + if (err) + return err; + } + } + + skb_walk_frags(skb, frag_iter) { + err = skb_shash_frags(desc, frag_iter); + if (err) + return err; + } + + return 0; +} + +static int tcp_authopt_hash_packet(struct crypto_shash *tfm, + struct sock *sk, + struct sk_buff *skb, + bool input, + bool ipv6, + bool include_options, + u8 *macbuf) +{ + struct tcphdr *th = tcp_hdr(skb); + SHASH_DESC_ON_STACK(desc, tfm); + int err; + + /* NOTE: SNE unimplemented */ + __be32 sne = 0; + + desc->tfm = tfm; + err = crypto_shash_init(desc); + if (err) + return err; + + err = crypto_shash_update(desc, (u8 *)&sne, 4); + if (err) + return err; + + if (ipv6) { + struct in6_addr *saddr; + struct in6_addr *daddr; + + if (input) { + saddr = &ipv6_hdr(skb)->saddr; + daddr = &ipv6_hdr(skb)->daddr; + } else { + saddr = &sk->sk_v6_rcv_saddr; + daddr = &sk->sk_v6_daddr; + } + err = tcp_authopt_hash_tcp6_pseudoheader(desc, saddr, daddr, skb->len); + if (err) + return err; + } else { + __be32 saddr; + __be32 daddr; + + if (input) { + saddr = ip_hdr(skb)->saddr; + daddr = ip_hdr(skb)->daddr; + } else { + saddr = sk->sk_rcv_saddr; + daddr = sk->sk_daddr; + } + err = tcp_authopt_hash_tcp4_pseudoheader(desc, saddr, daddr, skb->len); + if (err) + return err; + } + + // TCP header with checksum set to zero + { + struct tcphdr hashed_th = *th; + + hashed_th.check = 0; + err = crypto_shash_update(desc, (u8 *)&hashed_th, sizeof(hashed_th)); + if (err) + return err; + } + + // TCP options + err = tcp_authopt_hash_opts(desc, th, include_options); + if (err) + return err; + + // Rest of SKB->data + err = crypto_shash_update(desc, (u8 *)th + th->doff * 4, skb_headlen(skb) - th->doff * 4); + if (err) + return err; + + err = skb_shash_frags(desc, skb); + if (err) + return err; + + return crypto_shash_final(desc, macbuf); +} + +int __tcp_authopt_calc_mac(struct sock *sk, + struct sk_buff *skb, + struct tcp_authopt_key_info *key, + bool input, + char *macbuf) +{ + struct crypto_shash *mac_tfm; + u8 traffic_key[TCP_AUTHOPT_MAX_TRAFFIC_KEY_LEN]; + int err; + bool ipv6 = (sk->sk_family != AF_INET); + + if (sk->sk_family != AF_INET && sk->sk_family != AF_INET6) + return -EINVAL; + if (WARN_ON(key->alg->traffic_key_len > sizeof(traffic_key))) + return -ENOBUFS; + + err = tcp_authopt_get_traffic_key(sk, skb, key, input, ipv6, traffic_key); + if (err) + return err; + + mac_tfm = tcp_authopt_get_mac_shash(key); + if (IS_ERR(mac_tfm)) + return PTR_ERR(mac_tfm); + if (crypto_shash_digestsize(mac_tfm) > TCP_AUTHOPT_MAXMACBUF) { + err = -EINVAL; + goto out; + } + err = crypto_shash_setkey(mac_tfm, traffic_key, key->alg->traffic_key_len); + if (err) + goto out; + + err = tcp_authopt_hash_packet(mac_tfm, + sk, + skb, + input, + ipv6, + !(key->flags & TCP_AUTHOPT_KEY_EXCLUDE_OPTS), + macbuf); + //printk("mac: %*phN\n", key->maclen, macbuf); + +out: + tcp_authopt_put_mac_shash(key, mac_tfm); + return err; +} From patchwork Tue Aug 24 21:34:41 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 502150 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id CC07CC4338F for ; Tue, 24 Aug 2021 21:36:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B4FDD61165 for ; Tue, 24 Aug 2021 21:36:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238770AbhHXVgp (ORCPT ); Tue, 24 Aug 2021 17:36:45 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35438 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235291AbhHXVg0 (ORCPT ); Tue, 24 Aug 2021 17:36:26 -0400 Received: from mail-ej1-x632.google.com (mail-ej1-x632.google.com [IPv6:2a00:1450:4864:20::632]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 855F4C0612A4; Tue, 24 Aug 2021 14:35:40 -0700 (PDT) Received: by mail-ej1-x632.google.com with SMTP id x11so47312413ejv.0; Tue, 24 Aug 2021 14:35:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=mtZSbw8HLITma1TU3JWQnbsxubjtqK24FcLaQAPzwRs=; b=ZdrSNVVHF3uTYO38fDMTP53g2Y7aOkllymRPI0qy6M8aXAmxZjCYrq4pNqRIG8f5cp 3vMIaFThiVxjahvM9Gp+SngoIUjdaJJk9Ih3dz0VE8m729XLsaGmbpKmcdZThDg4+ytp Aks7maD69H65CYB9D7g/Fs9/6s9JhPntehP4R72PKSbN9KTdyv4+leamJ2sbAEuOnvTm vJBdxLvZUL3XysTJLPvuCij1l3yG3cDNGLYyGdjfzdtpfN8s5kj6mqDyfCMu/jfoVPt5 tIkzz93DNVjxoy1ZmOSzaL8dvZK0wrcarHKOo1ytBCQABWaWJnw4hDbZY9uGVn3PNPs2 uCQg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=mtZSbw8HLITma1TU3JWQnbsxubjtqK24FcLaQAPzwRs=; b=gNQKPgj7GqD+wQEq50gtKx99qt2sgGLxIpNzOrkL3n9DIfomKNWzDnll0SqM63dG9K nhWk6m2Fr1YY8ZzKUoC21hMrdnWhW22n4Sr6BuvPNLm5CSSuNeBQEqp3TPgb2kdFS4vp xJfo8Ib/B6sDh/19a661iCKkvmFrGZK4e3/58kpBogEVdgYw9Q99SJu/2ieIF9fmfriQ u2KqJLsB4/aWNhTu5MeQ5kOAYXMFt356H0XwxAiPvatEh7In4sGOklIsZwYvpZa4z85H UxRr56lYYFm7rWRkzB3OQ5haoaitJY830yXIOg0Sx5IELMzOI92ahSwUjlZzPXqHyJwv Wgbg== X-Gm-Message-State: AOAM5325HYNO7aiboCJKNP5BXSTfdvxgWL0PRjC9B1ozW+9jepNuyeeN xQqeN0Jte4KzQSevYcK237k= X-Google-Smtp-Source: ABdhPJwS9pUz2dqkn6uYWVXxT1CkSObiFDV6M7mI0mucqQUvAonZw6gcWhidi+IgkGZAM3NyFJmNhw== X-Received: by 2002:a17:906:1385:: with SMTP id f5mr28493098ejc.134.1629840939179; Tue, 24 Aug 2021 14:35:39 -0700 (PDT) Received: from localhost.localdomain ([2a04:241e:502:1d80:ed0a:7326:fbac:b4c]) by smtp.gmail.com with ESMTPSA id d16sm12348357edu.8.2021.08.24.14.35.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 14:35:38 -0700 (PDT) From: Leonard Crestez To: Dmitry Safonov <0x7f454c46@gmail.com>, David Ahern , Shuah Khan Cc: Eric Dumazet , "David S. Miller" , Herbert Xu , Kuniyuki Iwashima , Hideaki YOSHIFUJI , Jakub Kicinski , Yuchung Cheng , Francesco Ruggeri , Mat Martineau , Christoph Paasch , Ivan Delalande , Priyaranjan Jha , Menglong Dong , netdev@vger.kernel.org, linux-crypto@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFCv3 08/15] tcp: authopt: Add snmp counters Date: Wed, 25 Aug 2021 00:34:41 +0300 Message-Id: <46371eff47ae9917c76f2719d984ba2cd23f9a00.1629840814.git.cdleonard@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Add LINUX_MIB_TCPAUTHOPTFAILURE and increment on failure. This can be use by userspace to count the number of failed authentications. All types of authentication failures are reported under a single counter. Signed-off-by: Leonard Crestez --- include/uapi/linux/snmp.h | 1 + net/ipv4/proc.c | 1 + net/ipv4/tcp_authopt.c | 3 +++ 3 files changed, 5 insertions(+) diff --git a/include/uapi/linux/snmp.h b/include/uapi/linux/snmp.h index 904909d020e2..1d96030889a1 100644 --- a/include/uapi/linux/snmp.h +++ b/include/uapi/linux/snmp.h @@ -290,10 +290,11 @@ enum LINUX_MIB_TCPDUPLICATEDATAREHASH, /* TCPDuplicateDataRehash */ LINUX_MIB_TCPDSACKRECVSEGS, /* TCPDSACKRecvSegs */ LINUX_MIB_TCPDSACKIGNOREDDUBIOUS, /* TCPDSACKIgnoredDubious */ LINUX_MIB_TCPMIGRATEREQSUCCESS, /* TCPMigrateReqSuccess */ LINUX_MIB_TCPMIGRATEREQFAILURE, /* TCPMigrateReqFailure */ + LINUX_MIB_TCPAUTHOPTFAILURE, /* TCPAuthOptFailure */ __LINUX_MIB_MAX }; /* linux Xfrm mib definitions */ enum diff --git a/net/ipv4/proc.c b/net/ipv4/proc.c index b0d3a09dc84e..61dd06f8389c 100644 --- a/net/ipv4/proc.c +++ b/net/ipv4/proc.c @@ -295,10 +295,11 @@ static const struct snmp_mib snmp4_net_list[] = { SNMP_MIB_ITEM("TcpDuplicateDataRehash", LINUX_MIB_TCPDUPLICATEDATAREHASH), SNMP_MIB_ITEM("TCPDSACKRecvSegs", LINUX_MIB_TCPDSACKRECVSEGS), SNMP_MIB_ITEM("TCPDSACKIgnoredDubious", LINUX_MIB_TCPDSACKIGNOREDDUBIOUS), SNMP_MIB_ITEM("TCPMigrateReqSuccess", LINUX_MIB_TCPMIGRATEREQSUCCESS), SNMP_MIB_ITEM("TCPMigrateReqFailure", LINUX_MIB_TCPMIGRATEREQFAILURE), + SNMP_MIB_ITEM("TCPAuthOptFailure", LINUX_MIB_TCPAUTHOPTFAILURE), SNMP_MIB_SENTINEL }; static void icmpmsg_put_line(struct seq_file *seq, unsigned long *vals, unsigned short *type, int count) diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c index af777244d098..08ca77f01c46 100644 --- a/net/ipv4/tcp_authopt.c +++ b/net/ipv4/tcp_authopt.c @@ -1071,10 +1071,11 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb, struct tcp /* nothing found or expected */ if (!opt && !key) return 0; if (!opt && key) { + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE); net_info_ratelimited("TCP Authentication Missing\n"); return -EINVAL; } if (opt && !key) { /* RFC5925 Section 7.3: @@ -1082,10 +1083,11 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb, struct tcp * of segments with TCP-AO but that do not match an MKT. The initial * default of this configuration SHOULD be to silently accept such * connections. */ if (info->flags & TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED) { + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE); net_info_ratelimited("TCP Authentication Unexpected: Rejected\n"); return -EINVAL; } else { net_info_ratelimited("TCP Authentication Unexpected: Accepted\n"); return 0; @@ -1099,10 +1101,11 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb, struct tcp err = __tcp_authopt_calc_mac(sk, skb, key, true, macbuf); if (err) return err; if (memcmp(macbuf, opt->mac, key->maclen)) { + NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE); net_info_ratelimited("TCP Authentication Failed\n"); return -EINVAL; } return 0; From patchwork Tue Aug 24 21:34:42 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 502148 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3216FC19F37 for ; Tue, 24 Aug 2021 21:36:11 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2007461165 for ; Tue, 24 Aug 2021 21:36:11 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S239004AbhHXVgy (ORCPT ); Tue, 24 Aug 2021 17:36:54 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35428 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235659AbhHXVg1 (ORCPT ); Tue, 24 Aug 2021 17:36:27 -0400 Received: from mail-ej1-x62a.google.com (mail-ej1-x62a.google.com [IPv6:2a00:1450:4864:20::62a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6FCEEC0613CF; Tue, 24 Aug 2021 14:35:42 -0700 (PDT) Received: by mail-ej1-x62a.google.com with SMTP id x11so47312584ejv.0; Tue, 24 Aug 2021 14:35:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=zNzXEuIS7lPdfXUfFlF+z8/ZgMYTzkoaohsw19PRcTc=; b=H5cn49RLakZObp4CT/LlsMcJOCdYbmURMxRjQVvXO3kQh+5s2su/da8a57QVkOkb0B S6QS3gBlA/g4hZDT/UBNLLc+BlTuAEDLxWO33P2X3Y0PynKeqbzMvLRQk//FUGrSVR8r gR9Yz2c0EhBHVSNwPK0VQew5YteTL+gBhL+H53dfgQiXHbKvUM7b+KawEyiddbhNU9L5 ZxH0gZb9utlBoOmHqs+WXNkl54FNWZ8D93r/9mKpyY2eAXx/34OGveeXspnA/Z61x5Rk B7i+ekePKBGZrfgjhnQbQVHuEBawoavmkWe5cdoQuJ0fTk+jzQGA4BJCqNDF+25I7vUL 8Uvw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=zNzXEuIS7lPdfXUfFlF+z8/ZgMYTzkoaohsw19PRcTc=; b=gf3lqKmMGLmOR1aF3PEOjBLHqvuYrph7tr7NL6IcM6jqA/aq6O3xuVbdC8AVKdRzQ+ OcKwl9aD8ilSRFYrsRR3SfeSU1qPgn+gMgvHykajQAThxem3RfzvC/OBBoXHb4T801fk hjjf3XNXMkWCJdTGTC+kfV3kXe9CLcX0LGca07grMMXToaahe4WmrRvtq0k0snfxquuS ctw14wHQck0rQufPTYK4NRNVBtmKYR2bipeo2BFUm3fi9cZTc2dg65Y1s40ueqgsIzyN 9A38fCXkxPCvJdOyrZBGYJHn7VYyjlxBWD0TwW8lwpVXDYlWVgpturuF2Rs52WIuydrA YaAg== X-Gm-Message-State: AOAM533L/D81oirWzd8bW9inwOo9JJnn/M1uWRywNdqPmX2w8WYyqzY/ LmnFIKyR0TVk0F2044Iggyw= X-Google-Smtp-Source: ABdhPJwM3FAsoxHxtxsieIz6+wy2DnugvtBgY5W5St453yNFF4Fg1+ABS/LGLnVijaFJ4do+QJyx8A== X-Received: by 2002:a17:907:5096:: with SMTP id fv22mr6307510ejc.95.1629840941025; Tue, 24 Aug 2021 14:35:41 -0700 (PDT) Received: from localhost.localdomain ([2a04:241e:502:1d80:ed0a:7326:fbac:b4c]) by smtp.gmail.com with ESMTPSA id d16sm12348357edu.8.2021.08.24.14.35.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 14:35:40 -0700 (PDT) From: Leonard Crestez To: Dmitry Safonov <0x7f454c46@gmail.com>, David Ahern , Shuah Khan Cc: Eric Dumazet , "David S. Miller" , Herbert Xu , Kuniyuki Iwashima , Hideaki YOSHIFUJI , Jakub Kicinski , Yuchung Cheng , Francesco Ruggeri , Mat Martineau , Christoph Paasch , Ivan Delalande , Priyaranjan Jha , Menglong Dong , netdev@vger.kernel.org, linux-crypto@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFCv3 09/15] selftests: tcp_authopt: Test key address binding Date: Wed, 25 Aug 2021 00:34:42 +0300 Message-Id: <09c7cff43f832d0aba8dfad67f56066aeeca8475.1629840814.git.cdleonard@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org By default TCP-AO keys apply to all possible peers but it's possible to have different keys for different remote hosts. This patch adds initial tests for the behavior behind the TCP_AUTHOPT_KEY_BIND_ADDR flag. Server rejection is tested via client timeout so this can be slightly slow. Signed-off-by: Leonard Crestez --- .../tcp_authopt_test/netns_fixture.py | 63 +++++++ .../tcp_authopt/tcp_authopt_test/server.py | 82 ++++++++++ .../tcp_authopt/tcp_authopt_test/test_bind.py | 143 ++++++++++++++++ .../tcp_authopt/tcp_authopt_test/utils.py | 154 ++++++++++++++++++ 4 files changed, 442 insertions(+) create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/netns_fixture.py create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/server.py create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_bind.py create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/utils.py diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/netns_fixture.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/netns_fixture.py new file mode 100644 index 000000000000..20bb12c2aae2 --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/netns_fixture.py @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 +import subprocess +import socket +from ipaddress import IPv4Address +from ipaddress import IPv6Address + + +class NamespaceFixture: + """Create a pair of namespaces connected by one veth pair + + Each end of the pair has multiple addresses but everything is in the same subnet + """ + + ns1_name = "tcp_authopt_test_1" + ns2_name = "tcp_authopt_test_2" + + @classmethod + def get_ipv4_addr(cls, ns=1, index=1) -> IPv4Address: + return IPv4Address("10.10.0.0") + (ns << 8) + index + + @classmethod + def get_ipv6_addr(cls, ns=1, index=1) -> IPv6Address: + return IPv6Address("fd00::") + (ns << 16) + index + + @classmethod + def get_addr(cls, address_family=socket.AF_INET, ns=1, index=1): + if address_family == socket.AF_INET: + return cls.get_ipv4_addr(ns, index) + elif address_family == socket.AF_INET6: + return cls.get_ipv6_addr(ns, index) + else: + raise ValueError(f"Bad address_family={address_family}") + + def __init__(self, **kw): + for k, v in kw.items(): + setattr(self, k, v) + + def __enter__(self): + script = f""" +set -e -x +ip netns del {self.ns1_name} || true +ip netns del {self.ns2_name} || true +ip netns add {self.ns1_name} +ip netns add {self.ns2_name} +ip link add veth0 netns {self.ns1_name} type veth peer name veth0 netns {self.ns2_name} +ip netns exec {self.ns1_name} ip link set veth0 up +ip netns exec {self.ns2_name} ip link set veth0 up +""" + for index in [1, 2, 3]: + script += f"ip -n {self.ns1_name} addr add {self.get_ipv4_addr(1, index)}/16 dev veth0\n" + script += f"ip -n {self.ns2_name} addr add {self.get_ipv4_addr(2, index)}/16 dev veth0\n" + script += f"ip -n {self.ns1_name} addr add {self.get_ipv6_addr(1, index)}/64 dev veth0 nodad\n" + script += f"ip -n {self.ns2_name} addr add {self.get_ipv6_addr(2, index)}/64 dev veth0 nodad\n" + subprocess.run(script, shell=True, check=True) + return self + + def __exit__(self, *a): + script = f""" +set -e -x +ip netns del {self.ns1_name} || true +ip netns del {self.ns2_name} || true +""" + subprocess.run(script, shell=True, check=True) diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/server.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/server.py new file mode 100644 index 000000000000..c4cce8f5862a --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/server.py @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: GPL-2.0 +import logging +import os +import selectors +from contextlib import ExitStack +from threading import Thread + +logger = logging.getLogger(__name__) + + +class SimpleServerThread(Thread): + """Simple server thread for testing TCP sockets + + All data is read in 1000 bytes chunks and either echoed back or discarded. + """ + + def __init__(self, socket, mode="recv"): + self.listen_socket = socket + self.server_socket = [] + self.mode = mode + super().__init__() + + def _read(self, conn, events): + # logger.debug("events=%r", events) + data = conn.recv(1000) + # logger.debug("len(data)=%r", len(data)) + if len(data) == 0: + # logger.info("closing %r", conn) + conn.close() + self.sel.unregister(conn) + else: + if self.mode == "echo": + conn.sendall(data) + elif self.mode == "recv": + pass + else: + raise ValueError(f"Unknown mode {self.mode}") + + def _stop_pipe_read(self, conn, events): + self.should_loop = False + + def start(self) -> None: + self.exit_stack = ExitStack() + self._stop_pipe_rfd, self._stop_pipe_wfd = os.pipe() + self.exit_stack.callback(lambda: os.close(self._stop_pipe_rfd)) + self.exit_stack.callback(lambda: os.close(self._stop_pipe_wfd)) + return super().start() + + def _accept(self, conn, events): + assert conn == self.listen_socket + conn, _addr = self.listen_socket.accept() + conn = self.exit_stack.enter_context(conn) + conn.setblocking(False) + self.sel.register(conn, selectors.EVENT_READ, self._read) + self.server_socket.append(conn) + + def run(self): + self.should_loop = True + self.sel = self.exit_stack.enter_context(selectors.DefaultSelector()) + self.sel.register( + self._stop_pipe_rfd, selectors.EVENT_READ, self._stop_pipe_read + ) + self.sel.register(self.listen_socket, selectors.EVENT_READ, self._accept) + # logger.debug("loop init") + while self.should_loop: + for key, events in self.sel.select(timeout=1): + callback = key.data + callback(key.fileobj, events) + # logger.debug("loop done") + + def stop(self): + """Try to stop nicely""" + os.write(self._stop_pipe_wfd, b"Q") + self.join() + self.exit_stack.close() + + def __enter__(self): + self.start() + return self + + def __exit__(self, *args): + self.stop() diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_bind.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_bind.py new file mode 100644 index 000000000000..980954098e97 --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_bind.py @@ -0,0 +1,143 @@ +# SPDX-License-Identifier: GPL-2.0 +"""Test TCP-AO keys can be bound to specific remote addresses""" +from contextlib import ExitStack +import socket +import pytest +from .netns_fixture import NamespaceFixture +from .utils import create_listen_socket +from .server import SimpleServerThread +from . import linux_tcp_authopt +from .linux_tcp_authopt import ( + tcp_authopt, + set_tcp_authopt, + set_tcp_authopt_key, + tcp_authopt_key, +) +from .utils import netns_context, DEFAULT_TCP_SERVER_PORT, check_socket_echo +from .conftest import skipif_missing_tcp_authopt + +pytestmark = skipif_missing_tcp_authopt + + +@pytest.mark.parametrize("address_family", [socket.AF_INET, socket.AF_INET6]) +def test_addr_server_bind(exit_stack: ExitStack, address_family): + """ "Server only accept client2, check client1 fails""" + nsfixture = exit_stack.enter_context(NamespaceFixture()) + server_addr = str(nsfixture.get_addr(address_family, 1, 1)) + client_addr = str(nsfixture.get_addr(address_family, 2, 1)) + client_addr2 = str(nsfixture.get_addr(address_family, 2, 2)) + + # create server: + listen_socket = exit_stack.push( + create_listen_socket(family=address_family, ns=nsfixture.ns1_name) + ) + exit_stack.enter_context(SimpleServerThread(listen_socket, mode="echo")) + + # set keys: + server_key = tcp_authopt_key( + alg=linux_tcp_authopt.TCP_AUTHOPT_ALG_HMAC_SHA_1_96, + key="hello", + flags=linux_tcp_authopt.TCP_AUTHOPT_KEY_BIND_ADDR, + addr=client_addr2, + ) + set_tcp_authopt( + listen_socket, + tcp_authopt(flags=linux_tcp_authopt.TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED), + ) + set_tcp_authopt_key(listen_socket, server_key) + + # create client socket: + def create_client_socket(): + with netns_context(nsfixture.ns2_name): + client_socket = socket.socket(address_family, socket.SOCK_STREAM) + client_key = tcp_authopt_key( + alg=linux_tcp_authopt.TCP_AUTHOPT_ALG_HMAC_SHA_1_96, + key="hello", + ) + set_tcp_authopt_key(client_socket, client_key) + return client_socket + + # addr match: + # with create_client_socket() as client_socket2: + # client_socket2.bind((client_addr2, 0)) + # client_socket2.settimeout(1.0) + # client_socket2.connect((server_addr, TCP_SERVER_PORT)) + + # addr mismatch: + with create_client_socket() as client_socket1: + client_socket1.bind((client_addr, 0)) + with pytest.raises(socket.timeout): + client_socket1.settimeout(1.0) + client_socket1.connect((server_addr, DEFAULT_TCP_SERVER_PORT)) + + +@pytest.mark.parametrize("address_family", [socket.AF_INET, socket.AF_INET6]) +def test_addr_client_bind(exit_stack: ExitStack, address_family): + """ "Client configures different keys with same id but different addresses""" + nsfixture = exit_stack.enter_context(NamespaceFixture()) + server_addr1 = str(nsfixture.get_addr(address_family, 1, 1)) + server_addr2 = str(nsfixture.get_addr(address_family, 1, 2)) + client_addr = str(nsfixture.get_addr(address_family, 2, 1)) + + # create servers: + listen_socket1 = exit_stack.enter_context( + create_listen_socket( + family=address_family, ns=nsfixture.ns1_name, bind_addr=server_addr1 + ) + ) + listen_socket2 = exit_stack.enter_context( + create_listen_socket( + family=address_family, ns=nsfixture.ns1_name, bind_addr=server_addr2 + ) + ) + exit_stack.enter_context(SimpleServerThread(listen_socket1, mode="echo")) + exit_stack.enter_context(SimpleServerThread(listen_socket2, mode="echo")) + + # set keys: + set_tcp_authopt_key( + listen_socket1, + tcp_authopt_key( + alg=linux_tcp_authopt.TCP_AUTHOPT_ALG_HMAC_SHA_1_96, + key="11111", + ), + ) + set_tcp_authopt_key( + listen_socket2, + tcp_authopt_key( + alg=linux_tcp_authopt.TCP_AUTHOPT_ALG_HMAC_SHA_1_96, + key="22222", + ), + ) + + # create client socket: + def create_client_socket(): + with netns_context(nsfixture.ns2_name): + client_socket = socket.socket(address_family, socket.SOCK_STREAM) + set_tcp_authopt_key( + client_socket, + tcp_authopt_key( + alg=linux_tcp_authopt.TCP_AUTHOPT_ALG_HMAC_SHA_1_96, + key="11111", + flags=linux_tcp_authopt.TCP_AUTHOPT_KEY_BIND_ADDR, + addr=server_addr1, + ), + ) + set_tcp_authopt_key( + client_socket, + tcp_authopt_key( + alg=linux_tcp_authopt.TCP_AUTHOPT_ALG_HMAC_SHA_1_96, + key="22222", + flags=linux_tcp_authopt.TCP_AUTHOPT_KEY_BIND_ADDR, + addr=server_addr2, + ), + ) + client_socket.settimeout(1.0) + client_socket.bind((client_addr, 0)) + return client_socket + + with create_client_socket() as client_socket1: + client_socket1.connect((server_addr1, DEFAULT_TCP_SERVER_PORT)) + check_socket_echo(client_socket1) + with create_client_socket() as client_socket2: + client_socket2.connect((server_addr2, DEFAULT_TCP_SERVER_PORT)) + check_socket_echo(client_socket2) diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/utils.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/utils.py new file mode 100644 index 000000000000..22bd3f0a142a --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/utils.py @@ -0,0 +1,154 @@ +# SPDX-License-Identifier: GPL-2.0 +import json +import random +import subprocess +import threading +import typing +import socket +from dataclasses import dataclass +from contextlib import nullcontext + +from nsenter import Namespace +from scapy.sendrecv import AsyncSniffer + + +# TCP port does not impact Authentication Option so define a single default +DEFAULT_TCP_SERVER_PORT = 17971 + + +class SimpleWaitEvent(threading.Event): + @property + def value(self) -> bool: + return self.is_set() + + @value.setter + def value(self, value: bool): + if value: + self.set() + else: + self.clear() + + def wait(self, timeout=None): + """Like Event.wait except raise on timeout""" + super().wait(timeout) + if not self.is_set(): + raise TimeoutError(f"Timed out timeout={timeout!r}") + + +def recvall(sock, todo): + """Receive exactly todo bytes unless EOF""" + data = bytes() + while True: + chunk = sock.recv(todo) + if not len(chunk): + return data + data += chunk + todo -= len(chunk) + if todo == 0: + return data + assert todo > 0 + + +def randbytes(count) -> bytes: + """Return a random byte array""" + return bytes([random.randint(0, 255) for index in range(count)]) + + +def check_socket_echo(sock, size=1024): + """Send random bytes and check they are received""" + send_buf = randbytes(size) + sock.sendall(send_buf) + recv_buf = recvall(sock, size) + assert send_buf == recv_buf + + +def nstat_json(command_prefix: str = ""): + """Parse nstat output into a python dict""" + runres = subprocess.run( + f"{command_prefix}nstat -a --zeros --json", + shell=True, + check=True, + stdout=subprocess.PIPE, + encoding="utf-8", + ) + return json.loads(runres.stdout) + + +def netns_context(ns: str = ""): + """Create context manager for a certain optional netns + + If the ns argument is empty then just return a `nullcontext` + """ + if ns: + return Namespace("/var/run/netns/" + ns, "net") + else: + return nullcontext() + + +def create_listen_socket( + ns: str = "", + family=socket.AF_INET, + reuseaddr=True, + listen_depth=10, + bind_addr="", + bind_port=DEFAULT_TCP_SERVER_PORT, +): + with netns_context(ns): + listen_socket = socket.socket(family, socket.SOCK_STREAM) + if reuseaddr: + listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listen_socket.bind((str(bind_addr), bind_port)) + listen_socket.listen(listen_depth) + return listen_socket + + +@dataclass +class tcphdr_authopt: + """Representation of a TCP auth option as it appears in a TCP packet""" + + keyid: int + rnextkeyid: int + mac: bytes + + @classmethod + def unpack(cls, buf) -> "tcphdr_authopt": + return cls(buf[0], buf[1], buf[2:]) + + def __repr__(self): + return f"tcphdr_authopt({self.keyid}, {self.rnextkeyid}, bytes.fromhex({self.mac.hex(' ')!r})" + + +def scapy_tcp_get_authopt_val(tcp) -> typing.Optional[tcphdr_authopt]: + for optnum, optval in tcp.options: + if optnum == 29: + return tcphdr_authopt.unpack(optval) + return None + + +def scapy_sniffer_start_block(sniffer: AsyncSniffer, timeout=1): + """Like AsyncSniffer.start except block until sniffing starts + + This ensures no lost packets and no delays + """ + if sniffer.kwargs.get("started_callback"): + raise ValueError("sniffer must not already have a started_callback") + + e = SimpleWaitEvent() + sniffer.kwargs["started_callback"] = e.set + sniffer.start() + e.wait(timeout=timeout) + + +def scapy_sniffer_stop(sniffer: AsyncSniffer): + """Like AsyncSniffer.stop except no error is raising if not running""" + if sniffer is not None and sniffer.running: + sniffer.stop() + + +class AsyncSnifferContext(AsyncSniffer): + def __enter__(self): + scapy_sniffer_start_block(self) + return self + + def __exit__(self, *a): + scapy_sniffer_stop(self) From patchwork Tue Aug 24 21:34:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 502149 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT, WEIRD_QUOTING autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5CDD9C4320A for ; Tue, 24 Aug 2021 21:36:06 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3FCB961165 for ; Tue, 24 Aug 2021 21:36:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238833AbhHXVgt (ORCPT ); Tue, 24 Aug 2021 17:36:49 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35462 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237149AbhHXVga (ORCPT ); Tue, 24 Aug 2021 17:36:30 -0400 Received: from mail-ej1-x633.google.com (mail-ej1-x633.google.com [IPv6:2a00:1450:4864:20::633]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 201DBC0613D9; Tue, 24 Aug 2021 14:35:45 -0700 (PDT) Received: by mail-ej1-x633.google.com with SMTP id x11so47312827ejv.0; Tue, 24 Aug 2021 14:35:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=IeHOYpgSN/r/aILc2O4sIwLLNyeKU8mTdl0ifnTRw7U=; b=Li4OJISU0JyyBoJHCIThLFde9YoZzWVNCr9KdeIZhO2qqRKVYl/UQTexcmBwisKpQR 0VIxxnnuCYmAiZ6OegwioFtZ2fTd2uiV5RktyB4+Ilr8fkgUj0wRzcGAJmySaDM6qQFB rFa1TUWCoKePQ+jqX9hCdOQ4esmyluTHquE7uF85YR/JaAVh9aKQkxD13px5sNva8uLe 6fBouoXgwBbIv8ZMfz5zGHGLTqmy3jtT2gsTLlM9amKRvj7v2uz3Y+tI6IFqkmhenh0l 7BqAytEvTQ6rm4DeQf7Uk/o1Z4TF1JteN15XJq33ZAYXHybxdubsqdlkPR1XxHJ1GeGA nRoA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=IeHOYpgSN/r/aILc2O4sIwLLNyeKU8mTdl0ifnTRw7U=; b=f5Xc3uEQBsF5VA/DKIC00FkUQrG1ADOyJThO9OsFyWMQnAQmsNyzo1HI2228kn518Y MKBhiUjcraaW+H8ESefOnUhZybbfBXY+XksOAAaGhFHVHL24TRfNeoRS0aRccR7Wh+79 pLWN0BnJK/Hfc3EZ13yuzcuo1/stq+WYahFhjlEJKMK2IbGS+kKEolKUbqmvzQO3nUUS f0j1JA6JCywzDfFksW/U4QGNlCAFpgqslPLkUlFu3MdvnNpn0mwzE+Srs/U5m6sCLOAD G4Snlxi27rZgfDpMDjPSBhilla7uqN0l98jvV5VR0M7qupw+v9w+DmpeBodwaAmSTNxI 3ZgQ== X-Gm-Message-State: AOAM531cHH7piDGD9ZP3BRR6C7WOd/lRnFwUgyLln7AZ9/HyS0zzpw2b clVzm+vf/5WZApTjRPYlLxo= X-Google-Smtp-Source: ABdhPJxlNPyQMAA4qbKzhdotUtgCi9DBkjMaisGznnxM/9cuPq7r3Nyaz0gfsGMukVtsRj3VW8EvRA== X-Received: by 2002:a17:906:4346:: with SMTP id z6mr42161581ejm.403.1629840943232; Tue, 24 Aug 2021 14:35:43 -0700 (PDT) Received: from localhost.localdomain ([2a04:241e:502:1d80:ed0a:7326:fbac:b4c]) by smtp.gmail.com with ESMTPSA id d16sm12348357edu.8.2021.08.24.14.35.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 14:35:42 -0700 (PDT) From: Leonard Crestez To: Dmitry Safonov <0x7f454c46@gmail.com>, David Ahern , Shuah Khan Cc: Eric Dumazet , "David S. Miller" , Herbert Xu , Kuniyuki Iwashima , Hideaki YOSHIFUJI , Jakub Kicinski , Yuchung Cheng , Francesco Ruggeri , Mat Martineau , Christoph Paasch , Ivan Delalande , Priyaranjan Jha , Menglong Dong , netdev@vger.kernel.org, linux-crypto@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFCv3 10/15] selftests: tcp_authopt: Capture and verify packets Date: Wed, 25 Aug 2021 00:34:43 +0300 Message-Id: <11486ba2d081cb733b8c1e34acd5bd7f26932209.1629840814.git.cdleonard@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Tools like tcpdump and wireshark can parse the TCP Authentication Option but there is not yet support to verify correct signatures. This patch implements TCP-AO signature verification using scapy and the python cryptography package and applies it to captures of linux traffic in multiple scenarios (ipv4, ipv6 etc). The python code is verified itself with a subset of IETF test vectors from this page: https://datatracker.ietf.org/doc/html/draft-touch-tcpm-ao-test-vectors-02 Signed-off-by: Leonard Crestez --- .../full_tcp_sniff_session.py | 53 +++ .../tcp_authopt_test/tcp_authopt_alg.py | 276 ++++++++++++++ .../tcp_authopt_test/test_vectors.py | 359 ++++++++++++++++++ .../tcp_authopt_test/test_verify_capture.py | 123 ++++++ .../tcp_authopt/tcp_authopt_test/validator.py | 158 ++++++++ 5 files changed, 969 insertions(+) create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/full_tcp_sniff_session.py create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/tcp_authopt_alg.py create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_vectors.py create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_verify_capture.py create mode 100644 tools/testing/selftests/tcp_authopt/tcp_authopt_test/validator.py diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/full_tcp_sniff_session.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/full_tcp_sniff_session.py new file mode 100644 index 000000000000..d709e83c8700 --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/full_tcp_sniff_session.py @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-2.0 +import scapy.sessions +from scapy.layers.inet import TCP + +from .utils import SimpleWaitEvent + + +class FullTCPSniffSession(scapy.sessions.DefaultSession): + """Implementation of a scapy sniff session that can wait for a full TCP capture + + Allows another thread to wait for a complete FIN handshake without polling or sleep. + """ + + found_syn = False + found_synack = False + found_fin = False + found_client_fin = False + found_server_fin = False + + def __init__(self, server_port=None, **kw): + super().__init__(**kw) + self.server_port = server_port + self._close_event = SimpleWaitEvent() + + def on_packet_received(self, p): + super().on_packet_received(p) + if not p or not TCP in p: + return + th = p[TCP] + # logger.debug("sport=%d dport=%d flags=%s", th.sport, th.dport, th.flags) + if th.flags.S and not th.flags.A: + if th.dport == self.server_port or self.server_port is None: + self.found_syn = True + if th.flags.S and th.flags.A: + if th.sport == self.server_port or self.server_port is None: + self.found_synack = True + if th.flags.F: + if self.server_port is None: + self.found_fin = True + self._close_event.set() + elif self.server_port == th.dport: + self.found_client_fin = True + self.found_fin = True + if self.found_server_fin and self.found_client_fin: + self._close_event.set() + elif self.server_port == th.sport: + self.found_server_fin = True + self.found_fin = True + if self.found_server_fin and self.found_client_fin: + self._close_event.set() + + def wait_close(self, timeout=10): + self._close_event.wait(timeout=timeout) diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/tcp_authopt_alg.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/tcp_authopt_alg.py new file mode 100644 index 000000000000..093cb4716184 --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/tcp_authopt_alg.py @@ -0,0 +1,276 @@ +# SPDX-License-Identifier: GPL-2.0 +"""Packet-processing utilities implementing RFC5925 and RFC2926""" + +import logging +from dataclasses import dataclass +from ipaddress import IPv4Address, IPv6Address +from scapy.layers.inet import IP, TCP +from scapy.layers.inet6 import IPv6 +from scapy.packet import Packet +import socket +import struct +import typing +import hmac + +logger = logging.getLogger(__name__) + + +def kdf_sha1(master_key: bytes, context: bytes) -> bytes: + """RFC5926 section 3.1.1.1""" + input = b"\x01" + b"TCP-AO" + context + b"\x00\xa0" + return hmac.digest(master_key, input, "SHA1") + + +def mac_sha1(traffic_key: bytes, message: bytes) -> bytes: + """RFC5926 section 3.2.1""" + return hmac.digest(traffic_key, message, "SHA1")[:12] + + +def cmac_aes_digest(key: bytes, msg: bytes) -> bytes: + from cryptography.hazmat.primitives import cmac + from cryptography.hazmat.primitives.ciphers import algorithms + from cryptography.hazmat.backends import default_backend + + backend = default_backend() + c = cmac.CMAC(algorithms.AES(key), backend=backend) + c.update(bytes(msg)) + return c.finalize() + + +def kdf_cmac_aes(master_key: bytes, context: bytes) -> bytes: + if len(master_key) == 16: + key = master_key + else: + key = cmac_aes_digest(b"\x00" * 16, master_key) + return cmac_aes_digest(key, b"\x01" + b"TCP-AO" + context + b"\x00\x80") + + +def mac_cmac_aes(traffic_key: bytes, message: bytes) -> bytes: + return cmac_aes_digest(traffic_key, message)[:12] + + +class TcpAuthOptAlg: + def kdf(self, master_key: bytes, context: bytes) -> bytes: + raise NotImplementedError() + + def mac(self, traffic_key: bytes, message: bytes) -> bytes: + raise NotImplementedError() + + +class TcpAuthOptAlg_HMAC_SHA1(TcpAuthOptAlg): + def kdf(self, master_key: bytes, context: bytes) -> bytes: + return kdf_sha1(master_key, context) + + def mac(self, traffic_key: bytes, message: bytes) -> bytes: + return mac_sha1(traffic_key, message) + + +class TcpAuthOptAlg_CMAC_AES(TcpAuthOptAlg): + def kdf(self, master_key: bytes, context: bytes) -> bytes: + return kdf_cmac_aes(master_key, context) + + def mac(self, traffic_key: bytes, message: bytes) -> bytes: + return mac_cmac_aes(traffic_key, message) + + +def get_alg(name) -> TcpAuthOptAlg: + if name.upper() == "HMAC-SHA-1-96": + return TcpAuthOptAlg_HMAC_SHA1() + elif name.upper() == "AES-128-CMAC-96": + return TcpAuthOptAlg_CMAC_AES() + else: + raise ValueError(f"Bad TCP AuthOpt algorithms {name}") + + +IPvXAddress = typing.Union[IPv4Address, IPv6Address] + + +def get_scapy_ipvx_src(p: Packet) -> IPvXAddress: + if IP in p: + return IPv4Address(p[IP].src) + elif IPv6 in p: + return IPv6Address(p[IPv6].src) + else: + raise Exception("Neither IP nor IPv6 found on packet") + + +def get_scapy_ipvx_dst(p: Packet) -> IPvXAddress: + if IP in p: + return IPv4Address(p[IP].dst) + elif IPv6 in p: + return IPv6Address(p[IPv6].dst) + else: + raise Exception("Neither IP nor IPv6 found on packet") + + +def build_context( + saddr: IPvXAddress, daddr: IPvXAddress, sport, dport, src_isn, dst_isn +) -> bytes: + """Build context bytes as specified by RFC5925 section 5.2""" + return ( + saddr.packed + + daddr.packed + + struct.pack( + "!HHII", + sport, + dport, + src_isn, + dst_isn, + ) + ) + + +def build_context_from_scapy(p: Packet, src_isn: int, dst_isn: int) -> bytes: + """Build context based on a scapy Packet and src/dst initial-sequence numbers""" + return build_context( + get_scapy_ipvx_src(p), + get_scapy_ipvx_dst(p), + p[TCP].sport, + p[TCP].dport, + src_isn, + dst_isn, + ) + + +def build_context_from_scapy_syn(p: Packet) -> bytes: + """Build context for a scapy SYN packet""" + return build_context_from_scapy(p, p[TCP].seq, 0) + + +def build_context_from_scapy_synack(p: Packet) -> bytes: + """Build context for a scapy SYN/ACK packet""" + return build_context_from_scapy(p, p[TCP].seq, p[TCP].ack - 1) + + +def build_message_from_scapy(p: Packet, include_options=True, sne=0) -> bytearray: + """Build message bytes as described by RFC5925 section 5.1""" + result = bytearray() + result += struct.pack("!I", sne) + # ip pseudo-header: + if IP in p: + result += struct.pack( + "!4s4sHH", + IPv4Address(p[IP].src).packed, + IPv4Address(p[IP].dst).packed, + socket.IPPROTO_TCP, + p[TCP].dataofs * 4 + len(p[TCP].payload), + ) + assert p[TCP].dataofs * 4 + len(p[TCP].payload) + p[IP].ihl * 4 == p[IP].len + elif IPv6 in p: + result += struct.pack( + "!16s16sII", + IPv6Address(p[IPv6].src).packed, + IPv6Address(p[IPv6].dst).packed, + p[IPv6].plen, + socket.IPPROTO_TCP, + ) + assert p[TCP].dataofs * 4 + len(p[TCP].payload) == p[IPv6].plen + else: + raise Exception("Neither IP nor IPv6 found on packet") + + # tcp header with checksum set to zero + th_bytes = bytes(p[TCP]) + result += th_bytes[:16] + result += b"\x00\x00" + result += th_bytes[18:20] + + # Even if include_options=False the TCP-AO option itself is still included + # with the MAC set to all-zeros. This means we need to parse TCP options. + pos = 20 + tcphdr_optend = p[TCP].dataofs * 4 + # logger.info("th_bytes: %s", th_bytes.hex(' ')) + assert len(th_bytes) >= tcphdr_optend + while pos < tcphdr_optend: + optnum = th_bytes[pos] + pos += 1 + if optnum == 0 or optnum == 1: + if include_options: + result += bytes([optnum]) + continue + + optlen = th_bytes[pos] + pos += 1 + if pos + optlen - 2 > tcphdr_optend: + logger.info( + "bad tcp option %d optlen %d beyond end-of-header", optnum, optlen + ) + break + if optlen < 2: + logger.info("bad tcp option %d optlen %d less than two", optnum, optlen) + break + if optnum == 29: + if optlen < 4: + logger.info("bad tcp option %d optlen %d", optnum, optlen) + break + result += bytes([optnum, optlen]) + result += th_bytes[pos : pos + 2] + result += (optlen - 4) * b"\x00" + elif include_options: + result += bytes([optnum, optlen]) + result += th_bytes[pos : pos + optlen - 2] + pos += optlen - 2 + result += bytes(p[TCP].payload) + return result + + +@dataclass +class TCPAuthContext: + """Context used to TCP Authentication option as defined in RFC5925 5.2""" + + saddr: IPvXAddress = None + daddr: IPvXAddress = None + sport: int = 0 + dport: int = 0 + sisn: int = 0 + disn: int = 0 + + def pack(self, syn=False, rev=False) -> bytes: + if rev: + return build_context( + self.daddr, + self.saddr, + self.dport, + self.sport, + self.disn if not syn else 0, + self.sisn, + ) + else: + return build_context( + self.saddr, + self.daddr, + self.sport, + self.dport, + self.sisn, + self.disn if not syn else 0, + ) + + def rev(self) -> "TCPAuthContext": + """Reverse""" + return TCPAuthContext( + saddr=self.daddr, + daddr=self.saddr, + sport=self.dport, + dport=self.sport, + sisn=self.disn, + disn=self.sisn, + ) + + def init_from_syn_packet(self, p): + """Init from a SYN packet (and set dist to zero)""" + assert p[TCP].flags.S and not p[TCP].flags.A and p[TCP].ack == 0 + self.saddr = get_scapy_ipvx_src(p) + self.daddr = get_scapy_ipvx_dst(p) + self.sport = p[TCP].sport + self.dport = p[TCP].dport + self.sisn = p[TCP].seq + self.disn = 0 + + def update_from_synack_packet(self, p): + """Update disn and check everything else matches""" + assert p[TCP].flags.S and p[TCP].flags.A + assert self.saddr == get_scapy_ipvx_dst(p) + assert self.daddr == get_scapy_ipvx_src(p) + assert self.sport == p[TCP].dport + assert self.dport == p[TCP].sport + assert self.sisn == p[TCP].ack - 1 + self.disn = p[TCP].seq diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_vectors.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_vectors.py new file mode 100644 index 000000000000..f622bcf0dcbc --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_vectors.py @@ -0,0 +1,359 @@ +# SPDX-License-Identifier: GPL-2.0 +import logging +from ipaddress import IPv4Address, IPv6Address +from scapy.layers.inet import IP, TCP +from scapy.layers.inet6 import IPv6 +from .tcp_authopt_alg import get_alg, build_context_from_scapy, build_message_from_scapy +from .utils import scapy_tcp_get_authopt_val, tcphdr_authopt +import socket + +logger = logging.getLogger(__name__) + + +class TestIETFVectors: + """Test python implementation of TCP-AO algorithms + + Data is a subset of IETF test vectors: + https://datatracker.ietf.org/doc/html/draft-touch-tcpm-ao-test-vectors-02 + """ + + master_key = b"testvector" + client_keyid = 61 + server_keyid = 84 + client_ipv4 = IPv4Address("10.11.12.13") + client_ipv6 = IPv6Address("FD00::1") + server_ipv4 = IPv4Address("172.27.28.29") + server_ipv6 = IPv6Address("FD00::2") + + client_isn_41x = 0xFBFBAB5A + server_isn_41x = 0x11C14261 + client_isn_42x = 0xCB0EFBEE + server_isn_42x = 0xACD5B5E1 + client_isn_61x = 0x176A833F + server_isn_61x = 0x3F51994B + client_isn_62x = 0x020C1E69 + server_isn_62x = 0xEBA3734D + + def check( + self, + packet_hex: str, + traffic_key_hex: str, + mac_hex: str, + src_isn, + dst_isn, + include_options=True, + alg_name="HMAC-SHA-1-96", + sne=0, + ): + packet_bytes = bytes.fromhex(packet_hex) + + # sanity check for ip version + ipv = packet_bytes[0] >> 4 + if ipv == 4: + p = IP(bytes.fromhex(packet_hex)) + assert p[IP].proto == socket.IPPROTO_TCP + elif ipv == 6: + p = IPv6(bytes.fromhex(packet_hex)) + assert p[IPv6].nh == socket.IPPROTO_TCP + else: + raise ValueError(f"bad ipv={ipv}") + + # sanity check for seq/ack in SYN/ACK packets + if p[TCP].flags.S and p[TCP].flags.A is False: + assert p[TCP].seq == src_isn + assert p[TCP].ack == 0 + if p[TCP].flags.S and p[TCP].flags.A: + assert p[TCP].seq == src_isn + assert p[TCP].ack == dst_isn + 1 + + # check traffic key + alg = get_alg(alg_name) + context_bytes = build_context_from_scapy(p, src_isn, dst_isn) + traffic_key = alg.kdf(self.master_key, context_bytes) + assert traffic_key.hex(" ") == traffic_key_hex + + # check mac + message_bytes = build_message_from_scapy( + p, include_options=include_options, sne=sne + ) + mac = alg.mac(traffic_key, message_bytes) + assert mac.hex(" ") == mac_hex + + # check option bytes in header + opt = scapy_tcp_get_authopt_val(p[TCP]) + assert opt is not None + assert opt.keyid in [self.client_keyid, self.server_keyid] + assert opt.rnextkeyid in [self.client_keyid, self.server_keyid] + assert opt.mac.hex(" ") == mac_hex + + def test_4_1_1(self): + self.check( + """ + 45 e0 00 4c dd 0f 40 00 ff 06 bf 6b 0a 0b 0c 0d + ac 1b 1c 1d e9 d7 00 b3 fb fb ab 5a 00 00 00 00 + e0 02 ff ff ca c4 00 00 02 04 05 b4 01 03 03 08 + 04 02 08 0a 00 15 5a b7 00 00 00 00 1d 10 3d 54 + 2e e4 37 c6 f8 ed e6 d7 c4 d6 02 e7 + """, + "6d 63 ef 1b 02 fe 15 09 d4 b1 40 27 07 fd 7b 04 16 ab b7 4f", + "2e e4 37 c6 f8 ed e6 d7 c4 d6 02 e7", + self.client_isn_41x, + 0, + ) + + def test_4_1_2(self): + self.check( + """ + 45 e0 00 4c 65 06 40 00 ff 06 37 75 ac 1b 1c 1d + 0a 0b 0c 0d 00 b3 e9 d7 11 c1 42 61 fb fb ab 5b + e0 12 ff ff 37 76 00 00 02 04 05 b4 01 03 03 08 + 04 02 08 0a 84 a5 0b eb 00 15 5a b7 1d 10 54 3d + ee ab 0f e2 4c 30 10 81 51 16 b3 be + """, + "d9 e2 17 e4 83 4a 80 ca 2f 3f d8 de 2e 41 b8 e6 79 7f ea 96", + "ee ab 0f e2 4c 30 10 81 51 16 b3 be", + self.server_isn_41x, + self.client_isn_41x, + ) + + def test_4_1_3(self): + self.check( + """ + 45 e0 00 87 36 a1 40 00 ff 06 65 9f 0a 0b 0c 0d + ac 1b 1c 1d e9 d7 00 b3 fb fb ab 5b 11 c1 42 62 + c0 18 01 04 a1 62 00 00 01 01 08 0a 00 15 5a c1 + 84 a5 0b eb 1d 10 3d 54 70 64 cf 99 8c c6 c3 15 + c2 c2 e2 bf ff ff ff ff ff ff ff ff ff ff ff ff + ff ff ff ff 00 43 01 04 da bf 00 b4 0a 0b 0c 0d + 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 + 00 02 02 42 00 02 06 41 04 00 00 da bf 02 08 40 + 06 00 64 00 01 01 00 + """, + "d2 e5 9c 65 ff c7 b1 a3 93 47 65 64 63 b7 0e dc 24 a1 3d 71", + "70 64 cf 99 8c c6 c3 15 c2 c2 e2 bf", + self.client_isn_41x, + self.server_isn_41x, + ) + + def test_4_1_4(self): + self.check( + """ + 45 e0 00 87 1f a9 40 00 ff 06 7c 97 ac 1b 1c 1d + 0a 0b 0c 0d 00 b3 e9 d7 11 c1 42 62 fb fb ab 9e + c0 18 01 00 40 0c 00 00 01 01 08 0a 84 a5 0b f5 + 00 15 5a c1 1d 10 54 3d a6 3f 0e cb bb 2e 63 5c + 95 4d ea c7 ff ff ff ff ff ff ff ff ff ff ff ff + ff ff ff ff 00 43 01 04 da c0 00 b4 ac 1b 1c 1d + 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 + 00 02 02 42 00 02 06 41 04 00 00 da c0 02 08 40 + 06 00 64 00 01 01 00 + """, + "d9 e2 17 e4 83 4a 80 ca 2f 3f d8 de 2e 41 b8 e6 79 7f ea 96", + "a6 3f 0e cb bb 2e 63 5c 95 4d ea c7", + self.server_isn_41x, + self.client_isn_41x, + ) + + def test_4_2_1(self): + self.check( + """ + 45 e0 00 4c 53 99 40 00 ff 06 48 e2 0a 0b 0c 0d + ac 1b 1c 1d ff 12 00 b3 cb 0e fb ee 00 00 00 00 + e0 02 ff ff 54 1f 00 00 02 04 05 b4 01 03 03 08 + 04 02 08 0a 00 02 4c ce 00 00 00 00 1d 10 3d 54 + 80 af 3c fe b8 53 68 93 7b 8f 9e c2 + """, + "30 ea a1 56 0c f0 be 57 da b5 c0 45 22 9f b1 0a 42 3c d7 ea", + "80 af 3c fe b8 53 68 93 7b 8f 9e c2", + self.client_isn_42x, + 0, + include_options=False, + ) + + def test_4_2_2(self): + self.check( + """ + 45 e0 00 4c 32 84 40 00 ff 06 69 f7 ac 1b 1c 1d + 0a 0b 0c 0d 00 b3 ff 12 ac d5 b5 e1 cb 0e fb ef + e0 12 ff ff 38 8e 00 00 02 04 05 b4 01 03 03 08 + 04 02 08 0a 57 67 72 f3 00 02 4c ce 1d 10 54 3d + 09 30 6f 9a ce a6 3a 8c 68 cb 9a 70 + """, + "b5 b2 89 6b b3 66 4e 81 76 b0 ed c6 e7 99 52 41 01 a8 30 7f", + "09 30 6f 9a ce a6 3a 8c 68 cb 9a 70", + self.server_isn_42x, + self.client_isn_42x, + include_options=False, + ) + + def test_4_2_3(self): + self.check( + """ + 45 e0 00 87 a8 f5 40 00 ff 06 f3 4a 0a 0b 0c 0d + ac 1b 1c 1d ff 12 00 b3 cb 0e fb ef ac d5 b5 e2 + c0 18 01 04 6c 45 00 00 01 01 08 0a 00 02 4c ce + 57 67 72 f3 1d 10 3d 54 71 06 08 cc 69 6c 03 a2 + 71 c9 3a a5 ff ff ff ff ff ff ff ff ff ff ff ff + ff ff ff ff 00 43 01 04 da bf 00 b4 0a 0b 0c 0d + 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 + 00 02 02 42 00 02 06 41 04 00 00 da bf 02 08 40 + 06 00 64 00 01 01 00 + """, + "f3 db 17 93 d7 91 0e cd 80 6c 34 f1 55 ea 1f 00 34 59 53 e3", + "71 06 08 cc 69 6c 03 a2 71 c9 3a a5", + self.client_isn_42x, + self.server_isn_42x, + include_options=False, + ) + + def test_4_2_4(self): + self.check( + """ + 45 e0 00 87 54 37 40 00 ff 06 48 09 ac 1b 1c 1d + 0a 0b 0c 0d 00 b3 ff 12 ac d5 b5 e2 cb 0e fc 32 + c0 18 01 00 46 b6 00 00 01 01 08 0a 57 67 72 f3 + 00 02 4c ce 1d 10 54 3d 97 76 6e 48 ac 26 2d e9 + ae 61 b4 f9 ff ff ff ff ff ff ff ff ff ff ff ff + ff ff ff ff 00 43 01 04 da c0 00 b4 ac 1b 1c 1d + 26 02 06 01 04 00 01 00 01 02 02 80 00 02 02 02 + 00 02 02 42 00 02 06 41 04 00 00 da c0 02 08 40 + 06 00 64 00 01 01 00 + """, + "b5 b2 89 6b b3 66 4e 81 76 b0 ed c6 e7 99 52 41 01 a8 30 7f", + "97 76 6e 48 ac 26 2d e9 ae 61 b4 f9", + self.server_isn_42x, + self.client_isn_42x, + include_options=False, + ) + + def test_5_1_1(self): + self.check( + """ + 45 e0 00 4c 7b 9f 40 00 ff 06 20 dc 0a 0b 0c 0d + ac 1b 1c 1d c4 fa 00 b3 78 7a 1d df 00 00 00 00 + e0 02 ff ff 5a 0f 00 00 02 04 05 b4 01 03 03 08 + 04 02 08 0a 00 01 7e d0 00 00 00 00 1d 10 3d 54 + e4 77 e9 9c 80 40 76 54 98 e5 50 91 + """, + "f5 b8 b3 d5 f3 4f db b6 eb 8d 4a b9 66 0e 60 e3", + "e4 77 e9 9c 80 40 76 54 98 e5 50 91", + 0x787A1DDF, + 0, + include_options=True, + alg_name="AES-128-CMAC-96", + ) + + def test_6_1_1(self): + self.check( + """ + 6e 08 91 dc 00 38 06 40 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 01 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 02 f7 e4 00 b3 17 6a 83 3f + 00 00 00 00 e0 02 ff ff 47 21 00 00 02 04 05 a0 + 01 03 03 08 04 02 08 0a 00 41 d0 87 00 00 00 00 + 1d 10 3d 54 90 33 ec 3d 73 34 b6 4c 5e dd 03 9f + """, + "62 5e c0 9d 57 58 36 ed c9 b6 42 84 18 bb f0 69 89 a3 61 bb", + "90 33 ec 3d 73 34 b6 4c 5e dd 03 9f", + self.client_isn_61x, + 0, + include_options=True, + ) + + def test_6_1_2(self): + self.check( + """ + 6e 01 00 9e 00 38 06 40 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 01 00 b3 f7 e4 3f 51 99 4b + 17 6a 83 40 e0 12 ff ff bf ec 00 00 02 04 05 a0 + 01 03 03 08 04 02 08 0a bd 33 12 9b 00 41 d0 87 + 1d 10 54 3d f1 cb a3 46 c3 52 61 63 f7 1f 1f 55 + """, + "e4 a3 7a da 2a 0a fc a8 71 14 34 91 3f e1 38 c7 71 eb cb 4a", + "f1 cb a3 46 c3 52 61 63 f7 1f 1f 55", + self.server_isn_61x, + self.client_isn_61x, + include_options=True, + ) + + def test_6_2_2(self): + self.check( + """ + 6e 0a 7e 1f 00 38 06 40 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 01 00 b3 c6 cd eb a3 73 4d + 02 0c 1e 6a e0 12 ff ff 77 4d 00 00 02 04 05 a0 + 01 03 03 08 04 02 08 0a 5e c9 9b 70 00 9d b9 5b + 1d 10 54 3d 3c 54 6b ad 97 43 f1 2d f8 b8 01 0d + """, + "40 51 08 94 7f 99 65 75 e7 bd bc 26 d4 02 16 a2 c7 fa 91 bd", + "3c 54 6b ad 97 43 f1 2d f8 b8 01 0d", + self.server_isn_62x, + self.client_isn_62x, + include_options=False, + ) + + def test_6_2_4(self): + self.check( + """ + 6e 0a 7e 1f 00 73 06 40 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 01 00 b3 c6 cd eb a3 73 4e + 02 0c 1e ad c0 18 01 00 71 6a 00 00 01 01 08 0a + 5e c9 9b 7a 00 9d b9 65 1d 10 54 3d 55 9a 81 94 + 45 b4 fd e9 8d 9e 13 17 ff ff ff ff ff ff ff ff + ff ff ff ff ff ff ff ff 00 43 01 04 fd e8 00 b4 + 01 01 01 7a 26 02 06 01 04 00 01 00 01 02 02 80 + 00 02 02 02 00 02 02 42 00 02 06 41 04 00 00 fd + e8 02 08 40 06 00 64 00 01 01 00 + """, + "40 51 08 94 7f 99 65 75 e7 bd bc 26 d4 02 16 a2 c7 fa 91 bd", + "55 9a 81 94 45 b4 fd e9 8d 9e 13 17", + self.server_isn_62x, + self.client_isn_62x, + include_options=False, + ) + + server_isn_71x = 0xA6744ECB + client_isn_71x = 0x193CCCEC + + def test_7_1_2(self): + self.check( + """ + 6e 06 15 20 00 38 06 40 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 01 00 b3 f8 5a a6 74 4e cb + 19 3c cc ed e0 12 ff ff ea bb 00 00 02 04 05 a0 + 01 03 03 08 04 02 08 0a 71 da ab c8 13 e4 ab 99 + 1d 10 54 3d dc 28 43 a8 4e 78 a6 bc fd c5 ed 80 + """, + "cf 1b 1e 22 5e 06 a6 36 16 76 4a 06 7b 46 f4 b1", + "dc 28 43 a8 4e 78 a6 bc fd c5 ed 80", + self.server_isn_71x, + self.client_isn_71x, + alg_name="AES-128-CMAC-96", + include_options=True, + ) + + def test_7_1_4(self): + self.check( + """ + 6e 06 15 20 00 73 06 40 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 02 fd 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 01 00 b3 f8 5a a6 74 4e cc + 19 3c cd 30 c0 18 01 00 52 f4 00 00 01 01 08 0a + 71 da ab d3 13 e4 ab a3 1d 10 54 3d c1 06 9b 7d + fd 3d 69 3a 6d f3 f2 89 ff ff ff ff ff ff ff ff + ff ff ff ff ff ff ff ff 00 43 01 04 fd e8 00 b4 + 01 01 01 7a 26 02 06 01 04 00 01 00 01 02 02 80 + 00 02 02 02 00 02 02 42 00 02 06 41 04 00 00 fd + e8 02 08 40 06 00 64 00 01 01 00 + """, + "cf 1b 1e 22 5e 06 a6 36 16 76 4a 06 7b 46 f4 b1", + "c1 06 9b 7d fd 3d 69 3a 6d f3 f2 89", + self.server_isn_71x, + self.client_isn_71x, + alg_name="AES-128-CMAC-96", + include_options=True, + ) diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_verify_capture.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_verify_capture.py new file mode 100644 index 000000000000..1bc0f05197b8 --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/test_verify_capture.py @@ -0,0 +1,123 @@ +# SPDX-License-Identifier: GPL-2.0 +"""Capture packets with TCP-AO and verify signatures""" + +import logging +import os +import socket + +import pytest + +from . import linux_tcp_authopt, tcp_authopt_alg +from .full_tcp_sniff_session import FullTCPSniffSession +from .linux_tcp_authopt import ( + set_tcp_authopt_key, + tcp_authopt_key, +) +from .server import SimpleServerThread +from .utils import ( + DEFAULT_TCP_SERVER_PORT, + AsyncSnifferContext, + check_socket_echo, + create_listen_socket, + nstat_json, +) +from .validator import TcpAuthValidator, TcpAuthValidatorKey +from .conftest import skipif_missing_tcp_authopt + +logger = logging.getLogger(__name__) +pytestmark = skipif_missing_tcp_authopt + + +def can_capture(): + # This is too restrictive: + return os.geteuid() == 0 + + +skipif_cant_capture = pytest.mark.skipif( + not can_capture(), reason="run as root to capture packets" +) + + +def get_alg_id(alg_name) -> int: + if alg_name == "HMAC-SHA-1-96": + return linux_tcp_authopt.TCP_AUTHOPT_ALG_HMAC_SHA_1_96 + elif alg_name == "AES-128-CMAC-96": + return linux_tcp_authopt.TCP_AUTHOPT_ALG_AES_128_CMAC_96 + else: + raise ValueError() + + +@skipif_cant_capture +@pytest.mark.parametrize( + "address_family,alg_name,include_options", + [ + (socket.AF_INET, "HMAC-SHA-1-96", True), + (socket.AF_INET, "AES-128-CMAC-96", True), + (socket.AF_INET, "AES-128-CMAC-96", False), + (socket.AF_INET6, "HMAC-SHA-1-96", True), + (socket.AF_INET6, "HMAC-SHA-1-96", False), + (socket.AF_INET6, "AES-128-CMAC-96", True), + ], +) +def test_verify_capture(exit_stack, address_family, alg_name, include_options): + master_key = b"testvector" + alg_id = get_alg_id(alg_name) + + session = FullTCPSniffSession(server_port=DEFAULT_TCP_SERVER_PORT) + sniffer = exit_stack.enter_context( + AsyncSnifferContext( + filter=f"tcp port {DEFAULT_TCP_SERVER_PORT}", + iface="lo", + session=session, + ) + ) + + listen_socket = create_listen_socket(family=address_family) + listen_socket = exit_stack.enter_context(listen_socket) + exit_stack.enter_context(SimpleServerThread(listen_socket, mode="echo")) + + client_socket = socket.socket(address_family, socket.SOCK_STREAM) + client_socket = exit_stack.push(client_socket) + + set_tcp_authopt_key( + listen_socket, + tcp_authopt_key(alg=alg_id, key=master_key, include_options=include_options), + ) + set_tcp_authopt_key( + client_socket, + tcp_authopt_key(alg=alg_id, key=master_key, include_options=include_options), + ) + + # even if one signature is incorrect keep processing the capture + old_nstat = nstat_json() + valkey = TcpAuthValidatorKey( + key=master_key, alg_name=alg_name, include_options=include_options + ) + validator = TcpAuthValidator(keys=[valkey]) + + try: + client_socket.settimeout(1.0) + client_socket.connect(("localhost", DEFAULT_TCP_SERVER_PORT)) + for _ in range(5): + check_socket_echo(client_socket) + except socket.timeout: + logger.warning("socket timeout", exc_info=True) + pass + client_socket.close() + session.wait_close() + sniffer.stop() + + logger.info("capture: %r", sniffer.results) + for p in sniffer.results: + validator.handle_packet(p) + + assert not validator.any_fail + assert not validator.any_unsigned + # Fails because of duplicate packets: + # assert not validator.any_incomplete + new_nstat = nstat_json() + assert ( + 0 + == new_nstat["kernel"]["TcpExtTCPAuthOptFailure"] + - old_nstat["kernel"]["TcpExtTCPAuthOptFailure"] + ) diff --git a/tools/testing/selftests/tcp_authopt/tcp_authopt_test/validator.py b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/validator.py new file mode 100644 index 000000000000..5f0e83421af5 --- /dev/null +++ b/tools/testing/selftests/tcp_authopt/tcp_authopt_test/validator.py @@ -0,0 +1,158 @@ +# SPDX-License-Identifier: GPL-2.0 +from tcp_authopt_test.utils import scapy_tcp_get_authopt_val +import typing +import logging + +from dataclasses import dataclass +from scapy.packet import Packet +from scapy.layers.inet import TCP +from . import tcp_authopt_alg +from .tcp_authopt_alg import IPvXAddress, TCPAuthContext +from .tcp_authopt_alg import get_scapy_ipvx_src +from .tcp_authopt_alg import get_scapy_ipvx_dst + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class TCPSocketPair: + """TCP connection identifier""" + + saddr: IPvXAddress = None + daddr: IPvXAddress = None + sport: int = 0 + dport: int = 0 + + def rev(self) -> "TCPSocketPair": + return TCPSocketPair(self.daddr, self.saddr, self.dport, self.sport) + + +@dataclass +class TcpAuthValidatorKey: + key: bytes + alg_name: str + include_options: bool = True + keyid: typing.Optional[int] = None + sport: typing.Optional[int] = None + dport: typing.Optional[int] = None + + def match_packet(self, p: Packet): + if not TCP in p: + return False + authopt = scapy_tcp_get_authopt_val(p[TCP]) + if authopt is None: + return False + if self.keyid is not None and authopt.keyid != self.keyid: + return False + if self.sport is not None and p[TCP].sport != self.sport: + return False + if self.dport is not None and p[TCP].dport != self.dport: + return False + return True + + def get_alg_imp(self): + return tcp_authopt_alg.get_alg(self.alg_name) + + +def is_init_syn(p: Packet) -> bool: + return p[TCP].flags.S and not p[TCP].flags.A + + +class TcpAuthValidator: + """Validate TCP auth sessions inside a capture""" + + keys: typing.List[TcpAuthValidatorKey] + conn_dict: typing.Dict[TCPSocketPair, TCPAuthContext] + any_incomplete: bool = False + any_unsigned: bool = False + any_fail: bool = False + + def __init__(self, keys=None): + self.keys = keys or [] + self.conn_dict = {} + + def get_key_for_packet(self, p): + for k in self.keys: + if k.match_packet(p): + return k + return None + + def handle_packet(self, p: Packet): + if TCP not in p: + logger.debug("skip non-TCP packet") + return + key = self.get_key_for_packet(p) + if not key: + self.any_unsigned = True + logger.debug("skip packet not matching any known keys: %r", p) + return + authopt = scapy_tcp_get_authopt_val(p[TCP]) + if not authopt: + self.any_unsigned = True + logger.debug("skip packet without tcp authopt: %r", p) + return + captured_mac = authopt.mac + + saddr = get_scapy_ipvx_src(p) + daddr = get_scapy_ipvx_dst(p) + + conn_key = TCPSocketPair(saddr, daddr, p[TCP].sport, p[TCP].dport) + if p[TCP].flags.S: + conn = self.conn_dict.get(conn_key, None) + if conn is not None: + logger.warning("overwrite %r", conn) + self.any_incomplete = True + conn = TCPAuthContext() + conn.saddr = saddr + conn.daddr = daddr + conn.sport = p[TCP].sport + conn.dport = p[TCP].dport + self.conn_dict[conn_key] = conn + + if p[TCP].flags.A == False: + # SYN + conn.sisn = p[TCP].seq + conn.disn = 0 + logger.info("Initialized for SYN: %r", conn) + else: + # SYN/ACK + conn.sisn = p[TCP].seq + conn.disn = p[TCP].ack - 1 + logger.info("Initialized for SYNACK: %r", conn) + + # Update opposite connection with dst_isn + rconn_key = conn_key.rev() + rconn = self.conn_dict.get(rconn_key, None) + if rconn is None: + logger.warning("missing SYN for SYNACK: %s", rconn_key) + self.any_incomplete = True + else: + assert rconn.sisn == conn.disn + assert rconn.disn == 0 or rconn.disn == conn.sisn + rconn.disn = conn.sisn + rconn.update_from_synack_packet(p) + logger.info("Updated peer for SYNACK: %r", rconn) + else: + conn = self.conn_dict.get(conn_key, None) + if conn is None: + logger.warning("missing TCP syn for %r", conn_key) + self.any_incomplete = True + return + # logger.debug("conn %r found for packet %r", conn, p) + + context_bytes = conn.pack(syn=is_init_syn(p)) + alg = key.get_alg_imp() + traffic_key = alg.kdf(key.key, context_bytes) + message_bytes = tcp_authopt_alg.build_message_from_scapy( + p, include_options=key.include_options + ) + computed_mac = alg.mac(traffic_key, message_bytes) + if computed_mac == captured_mac: + logger.debug("ok - mac %s", computed_mac.hex()) + else: + self.any_fail = True + logger.error( + "not ok - captured %s computed %s", + captured_mac.hex(), + computed_mac.hex(), + ) From patchwork Tue Aug 24 21:34:46 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 502147 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 09168C4320E for ; Tue, 24 Aug 2021 21:36:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E77D16135F for ; Tue, 24 Aug 2021 21:36:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238585AbhHXVhH (ORCPT ); Tue, 24 Aug 2021 17:37:07 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35446 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237947AbhHXVgf (ORCPT ); Tue, 24 Aug 2021 17:36:35 -0400 Received: from mail-ed1-x530.google.com (mail-ed1-x530.google.com [IPv6:2a00:1450:4864:20::530]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4AAC9C0613A4; Tue, 24 Aug 2021 14:35:50 -0700 (PDT) Received: by mail-ed1-x530.google.com with SMTP id g22so3367016edy.12; Tue, 24 Aug 2021 14:35:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=WC9CLL+GQfxX/OVHxKvhiDhxNKgqk2CwgSEf4KWoZuk=; b=J9qFeUgvYUSBE32GcDCZZHdpI20xKXMGMzihyKPxyGGfp+SVzobHOwoal3WNOL7sIS uVwgxz9ShIWQNox9pWTZhm8F9ygbzltebL9MhkEZhiEy4fUyDOqPR15R76xlltUwUPp6 gXpTH8XEwJ0X5suIf/imw61T7zUwbkIzSZlzbQDX+JBLXzk81sw11bZiiiDPYrgUVIQC SUB1P7BNRZ3b7hpw0R3y1/syO3DIT8Q46vtqYOCx03jv7qTJ3SIPc9GyzcSgg5ZV+Xtn LRUAlrAA8H5SKgarmYSh8/CO+JlRR4em0E1QpgMfF3f9FfkiT1WNTXxFNilobyjrCC9/ e5zA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=WC9CLL+GQfxX/OVHxKvhiDhxNKgqk2CwgSEf4KWoZuk=; b=oPEDmLShMtMVdKedBUNLBUQnUGk9tJC5UXOxSB5ta2XVMHnGFMV3lPlKs3PkTB78QZ ibjkvXZtUJAxvSUtUQ+bdD1AteE8oxBKAPtYzUP2tVxxyEnOgncVLslj6S+j56FGM8Ua nxYfPlO9X18DkSIf7n6l/JaIhqMPXJDGOgc9fmoY9gLgdQbvzfELwKA3Q6tnAZaarx5F TmA6cWBbPPz6b5iwQvjX64uSycd9qSUDTchCp/vMyFctBb/Nw1lLp02FSmx5YmeT+XoC rKDz0d5/2DMiFYHtXFaotm7cLd/JxfhlgIQ2mFSSLpOONO+Vx860lOwYDdQ9i+9bmz/w 9EPA== X-Gm-Message-State: AOAM533ho27f8lr8H05LjkDgTNSiiVZ3RZzGlaUltTw6RtOEGAwbgD3Y FMbo213lXfiXeOJUdEv9FwI= X-Google-Smtp-Source: ABdhPJwfUHTMvcgVULz8cVgFtpyKuP4qM2asoKjPBh0JWU38ul86QZuzUjd/o7SGK0gclaS3vvRkwA== X-Received: by 2002:a05:6402:278b:: with SMTP id b11mr44150896ede.339.1629840948848; Tue, 24 Aug 2021 14:35:48 -0700 (PDT) Received: from localhost.localdomain ([2a04:241e:502:1d80:ed0a:7326:fbac:b4c]) by smtp.gmail.com with ESMTPSA id d16sm12348357edu.8.2021.08.24.14.35.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 14:35:48 -0700 (PDT) From: Leonard Crestez To: Dmitry Safonov <0x7f454c46@gmail.com>, David Ahern , Shuah Khan Cc: Eric Dumazet , "David S. Miller" , Herbert Xu , Kuniyuki Iwashima , Hideaki YOSHIFUJI , Jakub Kicinski , Yuchung Cheng , Francesco Ruggeri , Mat Martineau , Christoph Paasch , Ivan Delalande , Priyaranjan Jha , Menglong Dong , netdev@vger.kernel.org, linux-crypto@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFCv3 13/15] selftests: Add -t tcp_authopt option for fcnal-test.sh Date: Wed, 25 Aug 2021 00:34:46 +0300 Message-Id: <485a15f11d134e489952d48b45891163d9c1a59c.1629840814.git.cdleonard@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This script is otherwise very slow to run! Signed-off-by: Leonard Crestez --- tools/testing/selftests/net/fcnal-test.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/testing/selftests/net/fcnal-test.sh b/tools/testing/selftests/net/fcnal-test.sh index ca3b90f6fecb..3fa812789ac2 100755 --- a/tools/testing/selftests/net/fcnal-test.sh +++ b/tools/testing/selftests/net/fcnal-test.sh @@ -1328,10 +1328,21 @@ ipv4_tcp() log_subsection "With VRF" setup "yes" ipv4_tcp_vrf } + +only_tcp_authopt() +{ + log_section "TCP Authentication" + setup + set_sysctl net.ipv4.tcp_l3mdev_accept=0 + log_subsection "IPv4 no VRF" + ipv4_tcp_authopt +} + + ################################################################################ # IPv4 UDP ipv4_udp_novrf() { @@ -4018,10 +4029,11 @@ do ipv6_bind|bind6) ipv6_addr_bind;; ipv6_runtime) ipv6_runtime;; ipv6_netfilter) ipv6_netfilter;; use_cases) use_cases;; + tcp_authopt) only_tcp_authopt;; # setup namespaces and config, but do not run any tests setup) setup; exit 0;; vrf_setup) setup "yes"; exit 0;; From patchwork Tue Aug 24 21:34:47 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leonard Crestez X-Patchwork-Id: 502146 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 51FFBC4320A for ; Tue, 24 Aug 2021 21:36:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3C826613AB for ; Tue, 24 Aug 2021 21:36:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238094AbhHXVhT (ORCPT ); Tue, 24 Aug 2021 17:37:19 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35468 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S238497AbhHXVgh (ORCPT ); Tue, 24 Aug 2021 17:36:37 -0400 Received: from mail-ej1-x636.google.com (mail-ej1-x636.google.com [IPv6:2a00:1450:4864:20::636]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 30D53C06129E; Tue, 24 Aug 2021 14:35:52 -0700 (PDT) Received: by mail-ej1-x636.google.com with SMTP id me10so18847624ejb.11; Tue, 24 Aug 2021 14:35:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=fle/cTZNggWURF3rB6Z0CqaoPxK7NUDk4k8IoxtpQ5I=; b=Zw9EP6Q0NrWos5kbL1Ea/yL0Hj9Qt5SQjSsdM5C+OdhyAwJE32sLDOAa6+faOkg3zg sZ1L6vYvVAB2eIlX+v+IdBYW29M2tgFM3/TVwBAS04YCMVKogvTOMPbIlAuvQ1VQtWSf iBk9XlxCF1zcJPrgfQxRFbOcCn5sYa+6jxYQGetguu7w6DdJgTx9dAKbC48H5Qg4V3Vf 33OZwrLIdZgUcjh2+Qm4iPi9DGlKKG/RMXHSy7/kko+dW/ubSVeKt6cFSK1ioo2g5Hyq 2f65usG6qiaJ1ecuhELjM28+GfJnr1hD5sfAiVraCpSunEe+TYEDdNk6tRERMep96WAy uAkQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=fle/cTZNggWURF3rB6Z0CqaoPxK7NUDk4k8IoxtpQ5I=; b=c3iwA+JgD5e9BvH94zwBMOB5sm7hXU0v0tl3FwhVFPLWoZ6SEGETDamd4dgh9e85Tz 6dOlCGMpAweCVeekCsdRY4O5ermSRBNmF8Tl/Y9GvmnC5BZpg74Zn0w+TxkyCQjWMP7a bzUp6WKcjIhFMNp1/UVRziJLahk6b13t8CelU9KvKtMOd+9YFSkAjLmmZyZiSSbdd/hj vncW9qbP/aOXibjpGi2avgYg7qkyD6H50VOpzJZ9pPkd+ZlXGAEc1i2uQY9pijome4pn D1UG4Gw41AE6cQPJI43NZhi8HPrDJ6yw/dcKBweEAQbQLsnBUJePG4Oq9QbIFLloLMCu IhLw== X-Gm-Message-State: AOAM530SkyKaTB2RvfkU3ekFfauTjX8hqTAMkG+4oVRuWbdnRLDRzOGT W0iU0DGTVkzIRFM7NZL4Bs7d3KzDV8CGUNby X-Google-Smtp-Source: ABdhPJyzcrwsEfNjhQobNuk9Qw5CDuHfEHWru/T8WjKEQBURNjaabdnPiuHl8fYd6TtVg34BfLP8nQ== X-Received: by 2002:a17:907:2083:: with SMTP id pv3mr14439471ejb.402.1629840950714; Tue, 24 Aug 2021 14:35:50 -0700 (PDT) Received: from localhost.localdomain ([2a04:241e:502:1d80:ed0a:7326:fbac:b4c]) by smtp.gmail.com with ESMTPSA id d16sm12348357edu.8.2021.08.24.14.35.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Aug 2021 14:35:50 -0700 (PDT) From: Leonard Crestez To: Dmitry Safonov <0x7f454c46@gmail.com>, David Ahern , Shuah Khan Cc: Eric Dumazet , "David S. Miller" , Herbert Xu , Kuniyuki Iwashima , Hideaki YOSHIFUJI , Jakub Kicinski , Yuchung Cheng , Francesco Ruggeri , Mat Martineau , Christoph Paasch , Ivan Delalande , Priyaranjan Jha , Menglong Dong , netdev@vger.kernel.org, linux-crypto@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFCv3 14/15] tcp: authopt: Add key selection controls Date: Wed, 25 Aug 2021 00:34:47 +0300 Message-Id: <36ba357a7f43a883fe6f470a4b65d16a85553e52.1629840814.git.cdleonard@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org The RFC requires that TCP can report the keyid and rnextkeyid values being sent or received, implement this via getsockopt values. The RFC also requires that user can select the sending key and that the sending key is automatically switched based on rnextkeyid. These requirements can conflict so we implement both and add a flag which specifies if user or peer request takes priority. Also add an option to control rnextkeyid explicitly from userspace. Signed-off-by: Leonard Crestez --- Documentation/networking/tcp_authopt.rst | 25 ++++++++++ include/net/tcp_authopt.h | 10 ++++ include/uapi/linux/tcp.h | 31 ++++++++++++ net/ipv4/tcp_authopt.c | 60 +++++++++++++++++++++++- 4 files changed, 124 insertions(+), 2 deletions(-) diff --git a/Documentation/networking/tcp_authopt.rst b/Documentation/networking/tcp_authopt.rst index 484f66f41ad5..cded87a70d05 100644 --- a/Documentation/networking/tcp_authopt.rst +++ b/Documentation/networking/tcp_authopt.rst @@ -35,10 +35,35 @@ Keys can be bound to remote addresses in a way that is similar to TCP_MD5. RFC5925 requires that key ids do not overlap when tcp identifiers (addr/port) overlap. This is not enforced by linux, configuring ambiguous keys will result in packet drops and lost connections. +Key selection +------------- + +On getsockopt(TCP_AUTHOPT) information is provided about keyid/rnextkeyid in +the last send packet and about the keyid/rnextkeyd in the last valid received +packet. + +By default the sending keyid is selected to match the "rnextkeyid" value sent +by the remote side. If that keyid is not available (or for new connections) a +random matching key is selected. + +If the `TCP_AUTHOPT_LOCK_KEYID` is set then the sending key is selected by the +`tcp_authopt.send_local_id` field and rnextkeyid is ignored. If no key with +local_id == send_local_id is configured then a random matching key is +selected. + +The current sending key is cached in the socket and will not change unless +requested by remote rnextkeyid or by setsockopt. + +The rnextkeyid value sent on the wire is usually the recv_id of the current +key used for sending. If the TCP_AUTHOPT_LOCK_RNEXTKEY flag is set in +`tcp_authopt.flags` the value of `tcp_authopt.send_rnextkeyid` is send +instead. This can be used to implement smooth rollover: the peer will switch +its keyid to the received rnextkeyid when it is available. + ABI Reference ============= .. kernel-doc:: include/uapi/linux/tcp.h :identifiers: tcp_authopt tcp_authopt_flag tcp_authopt_key tcp_authopt_key_flag tcp_authopt_alg diff --git a/include/net/tcp_authopt.h b/include/net/tcp_authopt.h index 61db268f36f8..92d0c2333272 100644 --- a/include/net/tcp_authopt.h +++ b/include/net/tcp_authopt.h @@ -36,11 +36,21 @@ struct tcp_authopt_key_info { */ struct tcp_authopt_info { /** @head: List of tcp_authopt_key_info */ struct hlist_head head; struct rcu_head rcu; + /** + * @send_keyid - Current key used for sending, cached. + * + * Once a key is found it only changes by user or remote request. + */ + struct tcp_authopt_key_info *send_key; u32 flags; + u8 send_keyid; + u8 send_rnextkeyid; + u8 recv_keyid; + u8 recv_rnextkeyid; u32 src_isn; u32 dst_isn; }; #ifdef CONFIG_TCP_AUTHOPT diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h index 575162e7e281..43df8e3cd4cc 100644 --- a/include/uapi/linux/tcp.h +++ b/include/uapi/linux/tcp.h @@ -346,10 +346,24 @@ struct tcp_diag_md5sig { /** * enum tcp_authopt_flag - flags for `tcp_authopt.flags` */ enum tcp_authopt_flag { + /** + * @TCP_AUTHOPT_FLAG_LOCK_KEYID: keyid controlled by sockopt + * + * If this is set `tcp_authopt.send_keyid` is used to determined sending + * key. Otherwise a key with send_id == recv_rnextkeyid is preferred. + */ + TCP_AUTHOPT_FLAG_LOCK_KEYID = (1 << 0), + /** + * @TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID: Override rnextkeyid from userspace + * + * If this is set then `tcp_authopt.send_rnextkeyid` is sent on outbound + * packets. Other the recv_id of the current sending key is sent. + */ + TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID = (1 << 1), /** * @TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED: * Configure behavior of segments with TCP-AO coming from hosts for which no * key is configured. The default recommended by RFC is to silently accept * such connections. @@ -361,10 +375,27 @@ enum tcp_authopt_flag { * struct tcp_authopt - Per-socket options related to TCP Authentication Option */ struct tcp_authopt { /** @flags: Combination of &enum tcp_authopt_flag */ __u32 flags; + /** + * @send_keyid: `tcp_authopt_key.send_id` of preferred send key + * + * This is only used if `TCP_AUTHOPT_FLAG_LOCK_KEYID` is set. + */ + __u8 send_keyid; + /** + * @send_rnextkeyid: The rnextkeyid to send in packets + * + * This is controlled by the user iff TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID is + * set. Otherwise rnextkeyid is the recv_id of the current key. + */ + __u8 send_rnextkeyid; + /** @recv_keyid: A recently-received keyid value. Only for getsockopt. */ + __u8 recv_keyid; + /** @recv_rnextkeyid: A recently-received rnextkeyid value. Only for getsockopt. */ + __u8 recv_rnextkeyid; }; /** * enum tcp_authopt_key_flag - flags for `tcp_authopt.flags` * diff --git a/net/ipv4/tcp_authopt.c b/net/ipv4/tcp_authopt.c index 08ca77f01c46..1a80df739fd2 100644 --- a/net/ipv4/tcp_authopt.c +++ b/net/ipv4/tcp_authopt.c @@ -255,17 +255,44 @@ struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct tcp_authopt_info *in */ struct tcp_authopt_key_info *tcp_authopt_select_key(const struct sock *sk, const struct sock *addr_sk, u8 *rnextkeyid) { + struct tcp_authopt_key_info *key, *new_key; struct tcp_authopt_info *info; info = rcu_dereference(tcp_sk(sk)->authopt_info); if (!info) return NULL; - return tcp_authopt_lookup_send(info, addr_sk, -1); + key = info->send_key; + if (info->flags & TCP_AUTHOPT_FLAG_LOCK_KEYID) { + int send_keyid = info->send_keyid; + + if (!key || key->send_id != send_keyid) + new_key = tcp_authopt_lookup_send(info, addr_sk, send_keyid); + } else { + if (!key || key->send_id != info->recv_rnextkeyid) + new_key = tcp_authopt_lookup_send(info, addr_sk, info->recv_rnextkeyid); + } + if (!key && !new_key) + new_key = tcp_authopt_lookup_send(info, addr_sk, -1); + + // Change current key. + if (key != new_key && new_key) { + key = new_key; + info->send_key = key; + } + + if (key) { + if (info->flags & TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID) + *rnextkeyid = info->send_rnextkeyid; + else + *rnextkeyid = info->send_rnextkeyid = key->recv_id; + } + + return key; } static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); @@ -285,10 +312,12 @@ static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk return info; } #define TCP_AUTHOPT_KNOWN_FLAGS ( \ + TCP_AUTHOPT_FLAG_LOCK_KEYID | \ + TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID | \ TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED) int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen) { struct tcp_authopt opt; @@ -309,10 +338,14 @@ int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen) info = __tcp_authopt_info_get_or_create(sk); if (IS_ERR(info)) return PTR_ERR(info); info->flags = opt.flags & TCP_AUTHOPT_KNOWN_FLAGS; + if (opt.flags & TCP_AUTHOPT_FLAG_LOCK_KEYID) + info->send_keyid = opt.send_keyid; + if (opt.flags & TCP_AUTHOPT_FLAG_LOCK_RNEXTKEYID) + info->send_rnextkeyid = opt.send_rnextkeyid; return 0; } int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt) @@ -326,10 +359,21 @@ int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt) info = rcu_dereference_check(tp->authopt_info, lockdep_sock_is_held(sk)); if (!info) return -EINVAL; opt->flags = info->flags & TCP_AUTHOPT_KNOWN_FLAGS; + /* These keyids might be undefined, for example before connect. + * Reporting zero is not strictly correct because there are no reserved + * values. + */ + if (info->send_key) + opt->send_keyid = info->send_key->send_id; + else + opt->send_keyid = 0; + opt->send_rnextkeyid = info->send_rnextkeyid; + opt->recv_keyid = info->recv_keyid; + opt->recv_rnextkeyid = info->recv_rnextkeyid; return 0; } static void tcp_authopt_key_free_rcu(struct rcu_head *rcu) @@ -343,10 +387,12 @@ static void tcp_authopt_key_free_rcu(struct rcu_head *rcu) static void tcp_authopt_key_del(struct sock *sk, struct tcp_authopt_info *info, struct tcp_authopt_key_info *key) { hlist_del_rcu(&key->node); + if (info->send_key == key) + info->send_key = NULL; atomic_sub(sizeof(*key), &sk->sk_omem_alloc); call_rcu(&key->rcu, tcp_authopt_key_free_rcu); } /* free info and keys but don't touch tp->authopt_info */ @@ -496,10 +542,13 @@ int __tcp_authopt_openreq(struct sock *newsk, const struct sock *oldsk, struct r return -ENOMEM; sk_nocaps_add(newsk, NETIF_F_GSO_MASK); new_info->src_isn = tcp_rsk(req)->snt_isn; new_info->dst_isn = tcp_rsk(req)->rcv_isn; + new_info->send_keyid = old_info->send_keyid; + new_info->send_rnextkeyid = old_info->send_rnextkeyid; + new_info->flags = old_info->flags; INIT_HLIST_HEAD(&new_info->head); err = tcp_authopt_clone_keys(newsk, oldsk, new_info, old_info); if (err) { __tcp_authopt_info_free(newsk, new_info); return err; @@ -1088,11 +1137,11 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb, struct tcp NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE); net_info_ratelimited("TCP Authentication Unexpected: Rejected\n"); return -EINVAL; } else { net_info_ratelimited("TCP Authentication Unexpected: Accepted\n"); - return 0; + goto accept; } } /* bad inbound key len */ if (key->maclen + 4 != opt->len) @@ -1106,7 +1155,14 @@ int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb, struct tcp NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTHOPTFAILURE); net_info_ratelimited("TCP Authentication Failed\n"); return -EINVAL; } +accept: + /* Doing this for all valid packets will results in keyids temporarily + * flipping back and forth if packets are reordered or retransmitted. + */ + info->recv_keyid = opt->keyid; + info->recv_rnextkeyid = opt->rnextkeyid; + return 0; }