Skip to contentSkip to conversation

tcp: Initial support for RFC5925 auth option

RFC Superseded Leonard Crestez 4 participants 27 messages Aug 9, 2021 – Aug 12, 2021

Subsystems (6)

v2 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v2 cover tcp: Initial support for RFC5925 auth option

This is similar to TCP MD5 in functionality but it's sufficiently different that userspace interface and wire formats are incompatible. Compared to TCP-MD5 more algorithms are supported and multiple keys can be used on the same connection but there is still no negotiation mechanism. Expected use-case is protecting long-duration BGP/LDP connections between routers using pre-shared keys.

This version is mostly functional though more testing is required. Many obvious optimizations were skipped in favor of adding functionality. Here are several flaws:

* RST and TIMEWAIT are mostly unhandled
* A lock might be required for tcp_authopt
* Sequence Number Extension not implemented
* User is responsible for ensuring keys do not overlap (could be fine)
* Traffic key is not cached (reducing performance)

Test suite used during development is here: https://github.com/cdleonard/tcp-authopt-test
Tests are written in python using pytest and scapy and check the API in detail and validate packet captures.

Limited kselftest support for tcp_authopt in nettest/fcnal-test.sh is also included in this series. Those tests are slow and cover very little.

Changes for yabgp are here:
https://github.com/cdleonard/yabgp/commits/tcp_authopt
The patched version of yabgp can establish a BGP session protected by TCP Authentication Option with a Cisco IOS-XR router.

Changes since RFC:
* Split into per-topic commits for ease of review. The intermediate commits compile with a few "unused function" warnings and don't do anything useful by themselves.
* Add ABI documention including kernel-doc on uapi
* Fix lockdep warnings from crypto by creating pools with one shash for each cpu
* Accept short options to setsockopt by padding with zeros; this approach allows increasing the size of the structs in the future.
* Support for aes-128-cmac-96
* Support for binding addresses to keys in a way similar to old tcp_md5
* Add support for retrieving received keyid/rnextkeyid and controling the keyid/rnextkeyid being sent.

The key control support is not based on the requirements of any particular app (a routing daemon) but rather the recommendations of RFC5925. Several vendors implement key chain management similar to RFC8177 but this belongs in userspace.

Previously: https://lore.kernel.org/netdev/01383a8751e97ef826ef2adf93bfde3a08195a43.1626693859.git.cdleonard@gmail.com/

Leonard Crestez (9):
tcp: authopt: Initial support and key management
docs: Add user documentation for tcp_authopt
tcp: authopt: Add crypto initialization
tcp: authopt: Compute packet signatures
tcp: authopt: Hook into tcp core
tcp: authopt: Add key selection controls
tcp: authopt: Add snmp counters
selftests: Initial TCP-AO support for nettest
selftests: Initial TCP-AO support for fcnal-test

 Documentation/networking/index.rst        |    1 +
 Documentation/networking/tcp_authopt.rst  |   69 ++
 include/linux/tcp.h                       |    6 +
 include/net/tcp.h                         |    1 +
 include/net/tcp_authopt.h                 |  121 +++
 include/uapi/linux/snmp.h                 |    1 +
 include/uapi/linux/tcp.h                  |  103 ++
 net/ipv4/Kconfig                          |   14 +
 net/ipv4/Makefile                         |    1 +
 net/ipv4/proc.c                           |    1 +
 net/ipv4/tcp.c                            |   27 +
 net/ipv4/tcp_authopt.c                    | 1091 +++++++++++++++++++++
 net/ipv4/tcp_input.c                      |   17 +
 net/ipv4/tcp_ipv4.c                       |    5 +
 net/ipv4/tcp_minisocks.c                  |    2 +
 net/ipv4/tcp_output.c                     |   56 +-
 net/ipv6/tcp_ipv6.c                       |    4 +
 tools/testing/selftests/net/fcnal-test.sh |   22 +
 tools/testing/selftests/net/nettest.c     |   43 +-
 19 files changed, 1583 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/networking/tcp_authopt.rst
 create mode 100644 include/net/tcp_authopt.h
 create mode 100644 net/ipv4/tcp_authopt.c

base-commit: 2a2b6e3640c43a808dcb5226963e2cc0669294b1

Signature · 1 line
2.25.1
#1 lore raw

RFC v2 1/9 tcp: authopt: Initial support and key management

This commit add support to add and remove keys but does not use them further.

Similar to tcp md5 a single point to a struct tcp_authopt_info* struct is added to struct tcp_sock in order to avoid increasing memory usage for everybody. The data structures related to tcp_authopt are initialized on setsockopt and only removed on socket close.

---
 include/linux/tcp.h       |   6 ++
 include/net/tcp.h         |   1 +
 include/net/tcp_authopt.h |  55 ++++++++++++
 include/uapi/linux/tcp.h  |  72 ++++++++++++++++
 net/ipv4/Kconfig          |  14 ++++
 net/ipv4/Makefile         |   1 +
 net/ipv4/tcp.c            |  27 ++++++
 net/ipv4/tcp_authopt.c    | 172 ++++++++++++++++++++++++++++++++++++++
 net/ipv4/tcp_ipv4.c       |   2 +
 9 files changed, 350 insertions(+)
 create mode 100644 include/net/tcp_authopt.h
 create mode 100644 net/ipv4/tcp_authopt.c
include/linux/tcp.h +6 -0
index 48d8a363319e..cfddfc720b00 100644
@@ -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.
 	 */
include/net/tcp.h +1 -0
index 3166dc15d7d6..bb76554e8fe5 100644
@@ -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
include/net/tcp_authopt.h new file +55 -0

55 lines not on this page · raw message

include/uapi/linux/tcp.h +72 -0

92 lines not on this page · raw message

net/ipv4/Kconfig +14 -0
index 87983e70f03f..6459f4ea6f1d 100644
@@ -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.
net/ipv4/Makefile +1 -0
index bbdd9c44f14e..d336f32ce177 100644
@@ -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 \
net/ipv4/tcp.c +27 -0
index f931def6302e..fd90e80afa2c 100644
@@ -271,10 +271,11 @@
 
 #include <net/icmp.h>
 #include <net/inet_common.h>
 #include <net/tcp.h>
 #include <net/mptcp.h>
+#include <net/tcp_authopt.h>
 #include <net/xfrm.h>
 #include <net/ip.h>
 #include <net/sock.h>
 
 #include <linux/uaccess.h>
@@ -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))
net/ipv4/tcp_authopt.c new file +172 -0

172 lines not on this page · raw message

net/ipv4/tcp_ipv4.c +2 -0
index 2e62e0d6373a..1348615c7576 100644
@@ -60,10 +60,11 @@
 
 #include <net/net_namespace.h>
 #include <net/icmp.h>
 #include <net/inet_hashtables.h>
 #include <net/tcp.h>
+#include <net/tcp_authopt.h>
 #include <net/transp_v6.h>
 #include <net/ipv6.h>
 #include <net/inet_common.h>
 #include <net/timewait_sock.h>
 #include <net/xfrm.h>
@@ -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);
 
Signature · 1 line
2.25.1
#2 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross #…

Hi Leonard,

[FYI, it's a private test report for your RFC patch.] [auto build test WARNING on 2a2b6e3640c43a808dcb5226963e2cc0669294b1]

url:    https://github.com/0day-ci/linux/commits/Leonard-Crestez/tcp-Initial-support-for-RFC5925-auth-option/20210810-053824
base:   2a2b6e3640c43a808dcb5226963e2cc0669294b1
config: riscv-allyesconfig (attached as .config)
compiler: riscv64-linux-gcc (GCC) 10.3.0

reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# https://github.com/0day-ci/linux/commit/005169774b59da76d502cc81cfd0bcfb4f6af7b3
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Leonard-Crestez/tcp-Initial-support-for-RFC5925-auth-option/20210810-053824 git checkout 005169774b59da76d502cc81cfd0bcfb4f6af7b3
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-10.3.0 make.cross ARCH=riscv

If you fix the issue, kindly add following tag as appropriate

All warnings (new ones prefixed by >>):

>> net/ipv4/tcp_authopt.c:9:30: warning: no previous prototype for '__tcp_authopt_key_info_lookup' [-Wmissing-prototypes]
       9 | struct tcp_authopt_key_info *__tcp_authopt_key_info_lookup(const struct sock *sk,
         |                              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   net/ipv4/tcp_authopt.c: In function 'tcp_set_authopt':
   net/ipv4/tcp_authopt.c:60:62: error: expected ';' before ')' token
      60 |  info->flags = opt.flags & TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED);
         |                                                              ^
         |                                                              ;
   net/ipv4/tcp_authopt.c:60:62: error: expected statement before ')' token
   net/ipv4/tcp_authopt.c: At top level:
>> net/ipv4/tcp_authopt.c:90:6: warning: no previous prototype for '__tcp_authopt_info_free' [-Wmissing-prototypes]

90 | void __tcp_authopt_info_free(struct sock *sk, struct tcp_authopt_info *info)
| ^~~~~~~~~~~~~~~~~~~~~~~

vim +/__tcp_authopt_key_info_lookup +9 net/ipv4/tcp_authopt.c

     8	
   > 9	struct tcp_authopt_key_info *__tcp_authopt_key_info_lookup(const struct sock *sk,
    10								   struct tcp_authopt_info *info,
    11								   int key_id)
    12	{
    13		struct tcp_authopt_key_info *key;
    14	
    15		hlist_for_each_entry_rcu(key, &info->head, node, lockdep_sock_is_held(sk))
    16			if (key->local_id == key_id)
    17				return key;
    18	
    19		return NULL;
    20	}
    21	
    22	static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk)
    23	{
    24		struct tcp_sock *tp = tcp_sk(sk);
    25		struct tcp_authopt_info *info;
    26	
    27		info = rcu_dereference_check(tp->authopt_info, lockdep_sock_is_held(sk));
    28		if (info)
    29			return info;
    30	
    31		info = kmalloc(sizeof(*info), GFP_KERNEL | __GFP_ZERO);
    32		if (!info)
    33			return ERR_PTR(-ENOMEM);
    34	
    35		sk_nocaps_add(sk, NETIF_F_GSO_MASK);
    36		INIT_HLIST_HEAD(&info->head);
    37		rcu_assign_pointer(tp->authopt_info, info);
    38	
    39		return info;
    40	}
    41	
    42	int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
    43	{
    44		struct tcp_authopt opt;
    45		struct tcp_authopt_info *info;
    46	
    47		WARN_ON(!lockdep_sock_is_held(sk));
    48	
    49		/* If userspace optlen is too short fill the rest with zeros */
    50		if (optlen > sizeof(opt))
    51			return -EINVAL;
    52		memset(&opt, 0, sizeof(opt));
    53		if (copy_from_sockptr(&opt, optval, optlen))
    54			return -EFAULT;
    55	
    56		info = __tcp_authopt_info_get_or_create(sk);
    57		if (IS_ERR(info))
    58			return PTR_ERR(info);
    59	
    60		info->flags = opt.flags & TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED);
    61	
    62		return 0;
    63	}
    64	
    65	int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *opt)
    66	{
    67		struct tcp_sock *tp = tcp_sk(sk);
    68		struct tcp_authopt_info *info;
    69	
    70		WARN_ON(!lockdep_sock_is_held(sk));
    71		memset(opt, 0, sizeof(*opt));
    72		info = rcu_dereference_check(tp->authopt_info, lockdep_sock_is_held(sk));
    73		if (!info)
    74			return -EINVAL;
    75		opt->flags = info->flags & TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED;
    76	
    77		return 0;
    78	}
    79	
    80	static void tcp_authopt_key_del(struct sock *sk,
    81					struct tcp_authopt_info *info,
    82					struct tcp_authopt_key_info *key)
    83	{
    84		hlist_del_rcu(&key->node);
    85		atomic_sub(sizeof(*key), &sk->sk_omem_alloc);
    86		kfree_rcu(key, rcu);
    87	}
    88	
    89	/* free info and keys but don't touch tp->authopt_info */
  > 90	void __tcp_authopt_info_free(struct sock *sk, struct tcp_authopt_info *info)
    91	{
    92		struct hlist_node *n;
    93		struct tcp_authopt_key_info *key;
    94	
    95		hlist_for_each_entry_safe(key, n, &info->head, node)
    96			tcp_authopt_key_del(sk, info, key);
    97		kfree_rcu(info, rcu);
    98	}
    99	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

#3 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

In reply to Leonard Crestez

Hi Leonard,

On Tue, 10 Aug 2021 at 02:50, Leonard Crestez <[email protected]> wrote:
[..]

include/net/tcp_authopt.h line 11

Quoted 5 lines
include/net/tcp_authopt.h new file +5 -0
+/* Representation of a Master Key Tuple as per RFC5925 */
+struct tcp_authopt_key_info {
+       struct hlist_node node;
+       /* Local identifier */
+       u32 local_id;

There is no local_id in RFC5925, what's that?
An MKT is identified by (send_id, recv_id), together with (src_addr/src_port, dst_addr/dst_port).
Why introducing something new to already complicated RFC?

include/net/tcp_authopt.h line 18

Quoted 7 lines
include/net/tcp_authopt.h new file +7 -0
+       u32 flags;
+       /* Wire identifiers */
+       u8 send_id, recv_id;
+       u8 alg_id;
+       u8 keylen;
+       u8 key[TCP_AUTHOPT_MAXKEYLEN];
+       struct rcu_head rcu;

This is unaligned and will add padding.
I wonder if it's also worth saving some bytes by something like struct tcp_ao_key *key;

With

struct tcp_ao_key {
      u8 keylen;
      u8 key[0];
};

Hmm?

include/net/tcp_authopt.h line 29

Quoted 11 lines
include/net/tcp_authopt.h new file +11 -0
+       struct sockaddr_storage addr;
+};
+
+/* Per-socket information regarding tcp_authopt */
+struct tcp_authopt_info {
+       /* List of tcp_authopt_key_info */
+       struct hlist_head head;
+       u32 flags;
+       u32 src_isn;
+       u32 dst_isn;
+       struct rcu_head rcu;

Ditto, adds padding on 64-bit.

[..]

include/uapi/linux/tcp.h enum { · line 132

Quoted 9 lines

+++ b/include/uapi/linux/tcp.h

include/uapi/linux/tcp.h +2 -0
@@ -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 (RFC2385) */
+#define TCP_AUTHOPT_KEY                39      /* TCP Authentication Option update key (RFC2385) */

RFC2385 is md5 one.
Also, should there be TCP_AUTHOPT_ADD_KEY, TCP_AUTHOPT_DELTE_KEY instead of using flags inside setsocketopt()? (no hard feelings)

[..]

Quoted 10 lines
include/uapi/linux/tcp.h +10 -0
+/**
+ * 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.
+        */

include/uapi/linux/tcp.h struct tcp_diag_md5sig { · line 366

Quoted 10 lines
include/uapi/linux/tcp.h +10 -0
+       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;
+};

I'm not sure what's the use of enum here, probably:
#define TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED BIT(2)

[..]

Quoted 12 lines
include/uapi/linux/tcp.h +12 -0
+struct tcp_authopt_key {
+       /** @flags: Combination of &enum tcp_authopt_key_flag */
+       __u32   flags;
+       /** @local_id: Local identifier */
+       __u32   local_id;
+       /** @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 */

include/uapi/linux/tcp.h struct tcp_diag_md5sig { · line 415

Quoted 10 lines
include/uapi/linux/tcp.h +10 -0
+       __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;
+};

It'll be an ABI if this is accepted. As it is - it can't support RFC5925 fully. Extending syscall ABI is painful. I think, even the initial ABI *must* support all possible features of the RFC.
In other words, there must be src_addr, src_port, dst_addr and dst_port. I can see from docs you've written you don't want to support a mix of different addr/port MKTs, so you can return -EINVAL or -ENOTSUPP for any value but zero.
This will create an ABI that can be later extended to fully support RFC.

The same is about key: I don't think you need to define/use tcp_authopt_alg. Just use algo name - that way TCP-AO will automatically be able to use any algo supported by crypto engine.
See how xfrm does it, e.g.:
struct xfrm_algo_auth {
char alg_name[64];
unsigned int alg_key_len; /* in bits */
unsigned int alg_trunc_len; /* in bits */
char alg_key[0];
};

So you can let a user chose maclen instead of hard-coding it. Much more extendable than what you propose.

[..]

Quoted 8 lines
net/ipv4/tcp.c +8 -0
+#ifdef CONFIG_TCP_AUTHOPT
+       case TCP_AUTHOPT: {
+               struct tcp_authopt info;
+
+               if (get_user(len, optlen))
+                       return -EFAULT;
+
+               lock_sock(sk);

net/ipv4/tcp.c static int do_tcp_getsockopt(struct sock *sk, int level, · line 4248

Quoted 10 lines
net/ipv4/tcp.c +10 -0
+               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;
+       }

I'm pretty sure it's not a good choice to write partly tcp_authopt. And a user has no way to check what's the correct len on this kernel. Instead of len = min_t(unsigned int, len, sizeof(info)), it should be if (len != sizeof(info))

    return -EINVAL;

[..]

net/ipv4/tcp_authopt.c line 47

Quoted 6 lines
net/ipv4/tcp_authopt.c new file +6 -0
+int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
+{
+       struct tcp_authopt opt;
+       struct tcp_authopt_info *info;
+
+       WARN_ON(!lockdep_sock_is_held(sk));

sock_owned_by_me(sk)

net/ipv4/tcp_authopt.c line 52

Quoted 5 lines
net/ipv4/tcp_authopt.c new file +5 -0
+
+       /* If userspace optlen is too short fill the rest with zeros */
+       if (optlen > sizeof(opt))
+               return -EINVAL;
+       memset(&opt, 0, sizeof(opt));

it's 4 bytes, why not just (optlen != sizeof(opt))?

[..]

net/ipv4/tcp_authopt.c line 70

Quoted 6 lines
net/ipv4/tcp_authopt.c new file +6 -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;
+
+       WARN_ON(!lockdep_sock_is_held(sk));

sock_owned_by_me(sk)

[..]

net/ipv4/tcp_authopt.c line 123

Quoted 12 lines
net/ipv4/tcp_authopt.c new file +12 -0
+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;
+
+       /* 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;

Again, not a good practice to zero-extend struct. Enforce proper size with -EINVAL.

[..]

net/ipv4/tcp_authopt.c line 152

Quoted 10 lines
net/ipv4/tcp_authopt.c new file +10 -0
+       /* 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 key family */
+       if (opt.flags & TCP_AUTHOPT_KEY_ADDR_BIND) {
+               if (sk->sk_family != opt.addr.ss_family)
+                       return -EINVAL;
+       }

Probably, can be in the reverse order, so that
__tcp_authopt_info_get_or_create()
won't allocate memory.

net/ipv4/tcp_authopt.c line 160

Quoted 7 lines
net/ipv4/tcp_authopt.c new file +7 -0
+       /* If an old value exists for same local_id it is deleted */
+       key_info = __tcp_authopt_key_info_lookup(sk, info, opt.local_id);
+       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;

1. You don't need sock_kmalloc() together with tcp_authopt_key_del(). It just frees the memory and allocates it back straight away - no
sense in doing that.
2. I think RFC says you must not allow a user to change an existing key:

Quoted 2 lines

MKT parameters are not changed. Instead, new MKTs can be installed, and a connection can change which MKT it uses.

IIUC, it means that one can't just change an existing MKT, but one can remove and later
add MKT with the same (send_id, recv_id, src_addr/port, dst_addr/port).

So, a reasonable thing to do:
if (key_info)
return -EEXIST.

Thanks,
Dmitry

#4 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

Quoted 4 lines · Dmitry Safonov

Hi Leonard,

On Tue, 10 Aug 2021 at 02:50, Leonard Crestez <[email protected]> wrote:
[..]

Quoted 10 lines

include/net/tcp_authopt.h line 11

Quoted 5 lines
include/net/tcp_authopt.h new file +5 -0
+/* Representation of a Master Key Tuple as per RFC5925 */
+struct tcp_authopt_key_info {
+       struct hlist_node node;
+       /* Local identifier */
+       u32 local_id;

There is no local_id in RFC5925, what's that?
An MKT is identified by (send_id, recv_id), together with (src_addr/src_port, dst_addr/dst_port).
Why introducing something new to already complicated RFC?

It was there to simplify user interface and initial implementation.

But it seems that BGP listeners already needs to support multiple keychains for different peers so identifying the key by (send_id, recv_id, binding) is easier for userspace to work with. Otherwise they need to create their own local_id mapping internally.

Quoted 9 lines

include/net/tcp_authopt.h line 18

Quoted 7 lines
include/net/tcp_authopt.h new file +7 -0
+       u32 flags;
+       /* Wire identifiers */
+       u8 send_id, recv_id;
+       u8 alg_id;
+       u8 keylen;
+       u8 key[TCP_AUTHOPT_MAXKEYLEN];
+       struct rcu_head rcu;

This is unaligned and will add padding.

Not clear padding matters. Moving rcu_head higher would avoid it, is that what you're suggesting.

Quoted 10 lines

I wonder if it's also worth saving some bytes by something like struct tcp_ao_key *key;

With

struct tcp_ao_key {
       u8 keylen;
       u8 key[0];
};

Hmm?

This increases memory management complexity for very minor gains. Very few tcp_authopt_key will ever be created.

Quoted 21 lines

include/net/tcp_authopt.h line 29

Quoted 11 lines
include/net/tcp_authopt.h new file +11 -0
+       struct sockaddr_storage addr;
+};
+
+/* Per-socket information regarding tcp_authopt */
+struct tcp_authopt_info {
+       /* List of tcp_authopt_key_info */
+       struct hlist_head head;
+       u32 flags;
+       u32 src_isn;
+       u32 dst_isn;
+       struct rcu_head rcu;

Ditto, adds padding on 64-bit.

[..]

Quoted 6 lines

+++ b/include/uapi/linux/tcp.h

include/uapi/linux/tcp.h +0 -0
@@ -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
 
Quoted 7 lines

include/uapi/linux/tcp.h enum { · line 132

Quoted 3 lines
include/uapi/linux/tcp.h +2 -0
  #define TCP_TX_DELAY           37      /* delay outgoing packets by XX usec */
+#define TCP_AUTHOPT                    38      /* TCP Authentication Option (RFC2385) */
+#define TCP_AUTHOPT_KEY                39      /* TCP Authentication Option update key (RFC2385) */

RFC2385 is md5 one.
Also, should there be TCP_AUTHOPT_ADD_KEY, TCP_AUTHOPT_DELTE_KEY instead of using flags inside setsocketopt()? (no hard feelings)

Fixed RFC reference.

TCP_AUTHOPT_DELETE_KEY could be clearer, I just wanted to avoid bloating the sockopt space. But there's not any scarcity.

For reference tcp_md5 handles key deletion based on keylen == 0. This seems wrong to me, an empty key is in fact valid though not realistic.

If local_id is removed in favor of "full match on id and binding" then the delete sockopt would still need most of a full struct tcp_authopt_key anyway.

Quoted 15 lines

[..]

Quoted 14 lines
include/uapi/linux/tcp.h +14 -0
+/**
+ * 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),
+};
+
+/**
Quoted 8 lines

include/uapi/linux/tcp.h struct tcp_diag_md5sig { · line 366

Quoted 6 lines

+ * struct tcp_authopt - Per-socket options related to TCP Authentication Option
+ */

include/uapi/linux/tcp.h +4 -0
+struct tcp_authopt {
+       /** @flags: Combination of &enum tcp_authopt_flag */
+       __u32   flags;
+};

I'm not sure what's the use of enum here, probably: > #define TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED BIT(2)

This is an enum because it looks nice in kernel-doc. I couldn't find a way to attach docs to a macro and include it somewhere else. BTW, the enum gains more members later.

As for BIT() it doesn't see to be allowed in uapi and there were recent changes removing such usage.

Quoted 24 lines

[..]

Quoted 12 lines
include/uapi/linux/tcp.h +12 -0
+struct tcp_authopt_key {
+       /** @flags: Combination of &enum tcp_authopt_key_flag */
+       __u32   flags;
+       /** @local_id: Local identifier */
+       __u32   local_id;
+       /** @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 */

include/uapi/linux/tcp.h struct tcp_diag_md5sig { · line 415

Quoted 10 lines
include/uapi/linux/tcp.h +10 -0
+       __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;
+};
Quoted 8 lines

It'll be an ABI if this is accepted. As it is - it can't support RFC5925 fully. Extending syscall ABI is painful. I think, even the initial ABI *must* support all possible features of the RFC.
In other words, there must be src_addr, src_port, dst_addr and dst_port. I can see from docs you've written you don't want to support a mix of different addr/port MKTs, so you can return -EINVAL or -ENOTSUPP for any value but zero.
This will create an ABI that can be later extended to fully support RFC.

RFC states that MKT connection identifiers can be specified using ranges and wildcards and the details are up to the implementation. Keys are *NOT* just bound to a classical TCP 4-tuple.

* src_addr and src_port is implicit in socket binding. Maybe in theory src_addr they could apply for a server socket bound to 0.0.0.0:PORT but userspace can just open more sockets.
* dst_port is not supported by MD5 and I can't think of any useful usecase. This is either well known (179 for BGP) or auto-allocated.
* tcp_md5 was recently enhanced allow a "prefixlen" for addr and "l3mdev" ifindex binding.

This last point shows that the binding features people require can't be easily predicted in advance so it's better to allow the rules to be extended.

Quoted 11 lines

The same is about key: I don't think you need to define/use tcp_authopt_alg. Just use algo name - that way TCP-AO will automatically be able to use any algo supported by crypto engine.
See how xfrm does it, e.g.:
struct xfrm_algo_auth {
char alg_name[64];
unsigned int alg_key_len; /* in bits */
unsigned int alg_trunc_len; /* in bits */
char alg_key[0];
};

Quoted 2 lines

So you can let a user chose maclen instead of hard-coding it. Much more extendable than what you propose.

This complicates ABI and implementation with features that are not needed. I'd much rather only expose an enum of real-world tcp-ao algorithms.

Quoted 24 lines

[..]

Quoted 8 lines
net/ipv4/tcp.c +8 -0
+#ifdef CONFIG_TCP_AUTHOPT
+       case TCP_AUTHOPT: {
+               struct tcp_authopt info;
+
+               if (get_user(len, optlen))
+                       return -EFAULT;
+
+               lock_sock(sk);

net/ipv4/tcp.c static int do_tcp_getsockopt(struct sock *sk, int level, · line 4248

Quoted 10 lines
net/ipv4/tcp.c +10 -0
+               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;
+       }

I'm pretty sure it's not a good choice to write partly tcp_authopt. And a user has no way to check what's the correct len on this kernel. Instead of len = min_t(unsigned int, len, sizeof(info)), it should be if (len != sizeof(info))

Quoted 1 line
     return -EINVAL;

Purpose is to allow sockopts to grow as md5 has grown.

Quoted 9 lines

[..]

net/ipv4/tcp_authopt.c line 47

Quoted 6 lines
net/ipv4/tcp_authopt.c new file +6 -0
+int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
+{
+       struct tcp_authopt opt;
+       struct tcp_authopt_info *info;
+
+       WARN_ON(!lockdep_sock_is_held(sk));

sock_owned_by_me(sk)

Yes, this seems better. I think there are several socket locking mistakes in later commits. The patch adding support for detailed keyid control probably needs bh_lock_sock to avoid races between key selection and caching on send and key manipulation by userspace.

Quoted 6 lines

net/ipv4/tcp_authopt.c line 52

Quoted 4 lines
net/ipv4/tcp_authopt.c new file +4 -0
+       /* If userspace optlen is too short fill the rest with zeros */
+       if (optlen > sizeof(opt))
+               return -EINVAL;
+       memset(&opt, 0, sizeof(opt));

it's 4 bytes, why not just (optlen != sizeof(opt))?

It's extended in a later commit to add detailed keyid control.

Quoted 39 lines

[..]

net/ipv4/tcp_authopt.c line 70

Quoted 6 lines
net/ipv4/tcp_authopt.c new file +6 -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;
+
+       WARN_ON(!lockdep_sock_is_held(sk));

sock_owned_by_me(sk)

[..]

net/ipv4/tcp_authopt.c line 123

Quoted 12 lines
net/ipv4/tcp_authopt.c new file +12 -0
+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;
+
+       /* 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;

Again, not a good practice to zero-extend struct. Enforce proper size with -EINVAL.

[..]

net/ipv4/tcp_authopt.c line 152

Quoted 10 lines
net/ipv4/tcp_authopt.c new file +10 -0
+       /* 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 key family */
+       if (opt.flags & TCP_AUTHOPT_KEY_ADDR_BIND) {
+               if (sk->sk_family != opt.addr.ss_family)
+                       return -EINVAL;
+       }
Quoted 3 lines

Probably, can be in the reverse order, so that
__tcp_authopt_info_get_or_create()
won't allocate memory.

OK. It only saves a tiny bit of memory on API misuse.

Quoted 11 lines

net/ipv4/tcp_authopt.c line 160

Quoted 7 lines
net/ipv4/tcp_authopt.c new file +7 -0
+       /* If an old value exists for same local_id it is deleted */
+       key_info = __tcp_authopt_key_info_lookup(sk, info, opt.local_id);
+       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;

1. You don't need sock_kmalloc() together with tcp_authopt_key_del(). It just frees the memory and allocates it back straight away - no
sense in doing that.

The list is scanned in multiple places in later commits using nothing but an rcu_read_lock, this means that keys can't be updated in-place.

Quoted 11 lines

2. I think RFC says you must not allow a user to change an existing key:

Quoted 2 lines

MKT parameters are not changed. Instead, new MKTs can be installed, and a connection can change which MKT it uses.

IIUC, it means that one can't just change an existing MKT, but one can remove and later
add MKT with the same (send_id, recv_id, src_addr/port, dst_addr/port).

So, a reasonable thing to do:
if (key_info)
return -EEXIST.

You're right, making the user delete keys explicitly is better.

--
Regards,
Leonard

#5 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

Quoted 17 lines · Leonard Crestez
Quoted 5 lines · Dmitry Safonov

Hi Leonard,

On Tue, 10 Aug 2021 at 02:50, Leonard Crestez <[email protected]> wrote:
[..]

Quoted 10 lines

include/net/tcp_authopt.h line 11

Quoted 5 lines
include/net/tcp_authopt.h new file +5 -0
+/* Representation of a Master Key Tuple as per RFC5925 */
+struct tcp_authopt_key_info {
+       struct hlist_node node;
+       /* Local identifier */
+       u32 local_id;

There is no local_id in RFC5925, what's that?
An MKT is identified by (send_id, recv_id), together with (src_addr/src_port, dst_addr/dst_port).
Why introducing something new to already complicated RFC?

Quoted 7 lines

It was there to simplify user interface and initial implementation.

But it seems that BGP listeners already needs to support multiple keychains for different peers so identifying the key by (send_id, recv_id, binding) is easier for userspace to work with. Otherwise they need to create their own local_id mapping internally.

any proposed simplification needs to be well explained and how it relates to the RFC spec.

#6 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

Quoted 20 lines · David Ahern
Quoted 19 lines · Leonard Crestez
Quoted 5 lines · Dmitry Safonov

Hi Leonard,

On Tue, 10 Aug 2021 at 02:50, Leonard Crestez <[email protected]> wrote:
[..]

Quoted 10 lines

include/net/tcp_authopt.h line 11

Quoted 5 lines
include/net/tcp_authopt.h new file +5 -0
+/* Representation of a Master Key Tuple as per RFC5925 */
+struct tcp_authopt_key_info {
+       struct hlist_node node;
+       /* Local identifier */
+       u32 local_id;

There is no local_id in RFC5925, what's that?
An MKT is identified by (send_id, recv_id), together with (src_addr/src_port, dst_addr/dst_port).
Why introducing something new to already complicated RFC?

It was there to simplify user interface and initial implementation.

Quoted 8 lines
Quoted 5 lines

But it seems that BGP listeners already needs to support multiple keychains for different peers so identifying the key by (send_id, recv_id, binding) is easier for userspace to work with. Otherwise they need to create their own local_id mapping internally.

any proposed simplification needs to be well explained and how it relates to the RFC spec.

The local_id only exists between userspace and kernel so it's not really covered by the RFC.

There are objections to this and it seems to be unhelpful for userspace zo I will replace it with match by binding.

BTW: another somewhat dubious simplification is that I offloaded the RFC requirement to never add overlapping keys to userspace. So if userspace adds keys with same recvid that match the same TCP 4-tuple then connections will just start failing.

It's arguably fine to allow userspace misconfiguration to cause failures.

--
Regards,
Leonard

#7 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

Quoted 1 line · Leonard Crestez

On 11.08.2021 16:42, David Ahern wrote:

[..]

Quoted 10 lines
Quoted 3 lines

any proposed simplification needs to be well explained and how it relates to the RFC spec.

The local_id only exists between userspace and kernel so it's not really covered by the RFC.

There are objections to this and it seems to be unhelpful for userspace zo I will replace it with match by binding.

Quoted 6 lines

BTW: another somewhat dubious simplification is that I offloaded the RFC requirement to never add overlapping keys to userspace. So if userspace adds keys with same recvid that match the same TCP 4-tuple then connections will just start failing.

It's arguably fine to allow userspace misconfiguration to cause failures.

I think it's fine. But worth documenting. Also, keep in mind that someone in userspace with his funny ideas might start relying on such behavior in future.

Thanks,
Dmitry

#8 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

In reply to Leonard Crestez
Quoted 27 lines · Leonard Crestez
Quoted 26 lines · David Ahern
Quoted 24 lines · Leonard Crestez
Quoted 5 lines · Dmitry Safonov

Hi Leonard,

On Tue, 10 Aug 2021 at 02:50, Leonard Crestez <[email protected]> wrote:
[..]

Quoted 10 lines

include/net/tcp_authopt.h line 11

Quoted 5 lines
include/net/tcp_authopt.h new file +5 -0
+/* Representation of a Master Key Tuple as per RFC5925 */
+struct tcp_authopt_key_info {
+       struct hlist_node node;
+       /* Local identifier */
+       u32 local_id;

There is no local_id in RFC5925, what's that?
An MKT is identified by (send_id, recv_id), together with (src_addr/src_port, dst_addr/dst_port).
Why introducing something new to already complicated RFC?

It was there to simplify user interface and initial implementation.

But it seems that BGP listeners already needs to support multiple keychains for different peers so identifying the key by (send_id, recv_id, binding) is easier for userspace to work with. Otherwise they need to create their own local_id mapping internally.

Quoted 5 lines
Quoted 2 lines

any proposed simplification needs to be well explained and how it relates to the RFC spec.

The local_id only exists between userspace and kernel so it's not really covered by the RFC.

My point is that you need to document the uapi (however it ends up) and how it is used to achieve the intent of the RFC.

Quoted 14 lines

There are objections to this and it seems to be unhelpful for userspace zo I will replace it with match by binding.

BTW: another somewhat dubious simplification is that I offloaded the RFC requirement to never add overlapping keys to userspace. So if userspace adds keys with same recvid that match the same TCP 4-tuple then connections will just start failing.

It's arguably fine to allow userspace misconfiguration to cause failures.

Signature · 2 lines
Regards,
Leonard
#9 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

In reply to Leonard Crestez
Quoted 1 line · Leonard Crestez

On 8/10/21 11:41 PM, Dmitry Safonov wrote:

[..]

Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

+       u32 flags;
+       /* Wire identifiers */
+       u8 send_id, recv_id;
+       u8 alg_id;
+       u8 keylen;
+       u8 key[TCP_AUTHOPT_MAXKEYLEN];
+       struct rcu_head rcu;

Quoted 4 lines
Quoted 1 line

This is unaligned and will add padding.

Not clear padding matters. Moving rcu_head higher would avoid it, is that what you're suggesting.

Yes.

Quoted 9 lines
Quoted 9 lines

I wonder if it's also worth saving some bytes by something like struct tcp_ao_key *key;

With
struct tcp_ao_key {
       u8 keylen;
       u8 key[0];
};

Quoted 4 lines
Quoted 1 line

Hmm?

This increases memory management complexity for very minor gains. Very few tcp_authopt_key will ever be created.

The change doesn't seem to be big, like:

net/ipv4/tcp_authopt.c +0 -0
@@ -422,8 +422,16 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t

optval, unsig>

        key_info = __tcp_authopt_key_info_lookup(sk, info, opt.local_id);
        if (key_info)
                tcp_authopt_key_del(sk, info, key_info);

+
+ key = sock_kmalloc(sk, sizeof(*key) + opt.keylen, GFP_KERNEL |
__GFP_ZERO);
+ if (!key) {
+ tcp_authopt_alg_release(alg);
+ return -ENOMEM;
+ }
+
key_info = sock_kmalloc(sk, sizeof(*key_info), GFP_KERNEL |

__GFP_ZERO);
        if (!key_info) {
+               sock_kfree_s(sk, key, sizeof(*key) + opt.keylen);
                tcp_authopt_alg_release(alg);
                return -ENOMEM;
        }

I don't know, probably it'll be enough for every user to limit their keys by length of 80, but if one day it won't be enough - this ABI will be painful to extend.

[..]

Quoted 14 lines
Quoted 8 lines
Quoted 4 lines

+#define TCP_AUTHOPT                    38      /* TCP Authentication Option (RFC2385) */
+#define TCP_AUTHOPT_KEY                39      /* TCP Authentication Option update key (RFC2385) */

RFC2385 is md5 one.
Also, should there be TCP_AUTHOPT_ADD_KEY, TCP_AUTHOPT_DELTE_KEY instead of using flags inside setsocketopt()? (no hard feelings)

Fixed RFC reference.

TCP_AUTHOPT_DELETE_KEY could be clearer, I just wanted to avoid bloating the sockopt space. But there's not any scarcity.

Quoted 6 lines

For reference tcp_md5 handles key deletion based on keylen == 0. This seems wrong to me, an empty key is in fact valid though not realistic.

If local_id is removed in favor of "full match on id and binding" then the delete sockopt would still need most of a full struct tcp_authopt_key anyway.

Sounds like a plan.

[..]>> I'm not sure what's the use of enum here, probably: > #define

Quoted 4 lines
Quoted 1 line

TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED BIT(2)

This is an enum because it looks nice in kernel-doc. I couldn't find a way to attach docs to a macro and include it somewhere else.

Yeah, ok, seems like good justification.

Quoted 4 lines

BTW, the enum gains more members later.

As for BIT() it doesn't see to be allowed in uapi and there were recent changes removing such usage.

Ok, I just saw it's still used in include/uapi, but not aware of the removal.

Quoted 49 lines
Quoted 28 lines

[..]

Quoted 12 lines
(quoted diff) +12 -0
+struct tcp_authopt_key {
+       /** @flags: Combination of &enum tcp_authopt_key_flag */
+       __u32   flags;
+       /** @local_id: Local identifier */
+       __u32   local_id;
+       /** @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 */

Review

Quoted 10 lines
(quoted diff) +10 -0
+       __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;
+};

It'll be an ABI if this is accepted. As it is - it can't support RFC5925 fully.
Extending syscall ABI is painful. I think, even the initial ABI *must*

Quoted 8 lines

support
all possible features of the RFC.
In other words, there must be src_addr, src_port, dst_addr and dst_port. I can see from docs you've written you don't want to support a mix of different
addr/port MKTs, so you can return -EINVAL or -ENOTSUPP for any value but zero.
This will create an ABI that can be later extended to fully support RFC.

RFC states that MKT connection identifiers can be specified using ranges and wildcards and the details are up to the implementation. Keys are *NOT* just bound to a classical TCP 4-tuple.

* src_addr and src_port is implicit in socket binding. Maybe in theory src_addr they could apply for a server socket bound to 0.0.0.0:PORT but userspace can just open more sockets.
* dst_port is not supported by MD5 and I can't think of any useful usecase. This is either well known (179 for BGP) or auto-allocated.
* tcp_md5 was recently enhanced allow a "prefixlen" for addr and "l3mdev" ifindex binding.

Quoted 3 lines

This last point shows that the binding features people require can't be easily predicted in advance so it's better to allow the rules to be extended.

Yeah, I see both changes you mention went on easy way as they reused existing paddings in the ABI structures.
Ok, if you don't want to reserve src_addr/src_port/dst_addr/dst_port, than how about reserving some space for those instead?

Quoted 12 lines
Quoted 12 lines

The same is about key: I don't think you need to define/use tcp_authopt_alg.
Just use algo name - that way TCP-AO will automatically be able to use any algo supported by crypto engine.
See how xfrm does it, e.g.:
struct xfrm_algo_auth {
     char        alg_name[64];
     unsigned int    alg_key_len;    /* in bits */
     unsigned int    alg_trunc_len;  /* in bits */
     char        alg_key[0];
};

Quoted 6 lines
Quoted 2 lines

So you can let a user chose maclen instead of hard-coding it. Much more extendable than what you propose.

This complicates ABI and implementation with features that are not needed. I'd much rather only expose an enum of real-world tcp-ao algorithms.

I see it exactly the opposite way: a new enum unnecessary complicates ABI, instead of passing alg_name[] to crypto engine. No need to add any support in tcp-ao as the algorithms are already provided by kernel. That is how it transparently works for ipsec, why not for tcp-ao?

Quoted 27 lines
Quoted 18 lines

[..]

Quoted 16 lines
(quoted diff) +16 -0
+#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;
Quoted 8 lines
Quoted 2 lines

+               return 0;
+       }

I'm pretty sure it's not a good choice to write partly tcp_authopt. And a user has no way to check what's the correct len on this kernel. Instead of len = min_t(unsigned int, len, sizeof(info)), it should be if (len != sizeof(info))
     return -EINVAL;

Quoted 1 line

Purpose is to allow sockopts to grow as md5 has grown.

md5 has not grown. See above.

Another issue with your approach

+ /* 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;

is that userspace compiled with updated/grew structure will fail on older kernel. So, no extension without breaking something is possible. Which also reminds me that currently you don't validate (opt.flags) for unknown by kernel flags.

Extending syscalls is impossible without breaking userspace if ABI is not designed with extensibility in mind. That was quite a big problem, and still is. Please, read this carefully:
https://lwn.net/Articles/830666/

That is why I'm suggesting you all those changes that will be harder to fix when/if your patches get accepted.
As an example how it should work see in copy_clone_args_from_user().

[..]

Thanks,
Dmitry

#10 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

Quoted 209 lines · Dmitry Safonov
Quoted 1 line · Leonard Crestez

On 8/10/21 11:41 PM, Dmitry Safonov wrote:

[..]

Quoted 8 lines
Quoted 8 lines
Quoted 7 lines

+       u32 flags;
+       /* Wire identifiers */
+       u8 send_id, recv_id;
+       u8 alg_id;
+       u8 keylen;
+       u8 key[TCP_AUTHOPT_MAXKEYLEN];
+       struct rcu_head rcu;

Quoted 4 lines
Quoted 1 line

This is unaligned and will add padding.

Not clear padding matters. Moving rcu_head higher would avoid it, is that what you're suggesting.

Yes.

Quoted 9 lines
Quoted 9 lines

I wonder if it's also worth saving some bytes by something like struct tcp_ao_key *key;

With
struct tcp_ao_key {
       u8 keylen;
       u8 key[0];
};

Quoted 4 lines
Quoted 1 line

Hmm?

This increases memory management complexity for very minor gains. Very few tcp_authopt_key will ever be created.

The change doesn't seem to be big, like:

net/ipv4/tcp_authopt.c +0 -0
@@ -422,8 +422,16 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t

optval, unsig>

        key_info = __tcp_authopt_key_info_lookup(sk, info, opt.local_id);
        if (key_info)
                tcp_authopt_key_del(sk, info, key_info);
net/ipv4/tcp_authopt.c +2 -0
+
+       key = sock_kmalloc(sk, sizeof(*key) + opt.keylen, GFP_KERNEL |

__GFP_ZERO);

net/ipv4/tcp_authopt.c +5 -0
+       if (!key) {
+               tcp_authopt_alg_release(alg);
+               return -ENOMEM;
+       }
+
        key_info = sock_kmalloc(sk, sizeof(*key_info), GFP_KERNEL |
__GFP_ZERO);
        if (!key_info) {
+               sock_kfree_s(sk, key, sizeof(*key) + opt.keylen);
                tcp_authopt_alg_release(alg);
                return -ENOMEM;
        }

I don't know, probably it'll be enough for every user to limit their keys by length of 80, but if one day it won't be enough - this ABI will be painful to extend.

[..]

Quoted 14 lines
Quoted 8 lines
Quoted 4 lines

+#define TCP_AUTHOPT                    38      /* TCP Authentication Option (RFC2385) */
+#define TCP_AUTHOPT_KEY                39      /* TCP Authentication Option update key (RFC2385) */

RFC2385 is md5 one.
Also, should there be TCP_AUTHOPT_ADD_KEY, TCP_AUTHOPT_DELTE_KEY instead of using flags inside setsocketopt()? (no hard feelings)

Fixed RFC reference.

TCP_AUTHOPT_DELETE_KEY could be clearer, I just wanted to avoid bloating the sockopt space. But there's not any scarcity.

Quoted 6 lines

For reference tcp_md5 handles key deletion based on keylen == 0. This seems wrong to me, an empty key is in fact valid though not realistic.

If local_id is removed in favor of "full match on id and binding" then the delete sockopt would still need most of a full struct tcp_authopt_key anyway.

Sounds like a plan.

[..]>> I'm not sure what's the use of enum here, probably: > #define

Quoted 4 lines
Quoted 1 line

TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED BIT(2)

This is an enum because it looks nice in kernel-doc. I couldn't find a way to attach docs to a macro and include it somewhere else.

Yeah, ok, seems like good justification.

Quoted 4 lines

BTW, the enum gains more members later.

As for BIT() it doesn't see to be allowed in uapi and there were recent changes removing such usage.

Ok, I just saw it's still used in include/uapi, but not aware of the removal.

Quoted 49 lines
Quoted 28 lines

[..]

Quoted 12 lines
(quoted diff) +12 -0
+struct tcp_authopt_key {
+       /** @flags: Combination of &enum tcp_authopt_key_flag */
+       __u32   flags;
+       /** @local_id: Local identifier */
+       __u32   local_id;
+       /** @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 */

Review

Quoted 10 lines
(quoted diff) +10 -0
+       __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;
+};

It'll be an ABI if this is accepted. As it is - it can't support RFC5925 fully.
Extending syscall ABI is painful. I think, even the initial ABI *must*

Quoted 8 lines

support
all possible features of the RFC.
In other words, there must be src_addr, src_port, dst_addr and dst_port. I can see from docs you've written you don't want to support a mix of different
addr/port MKTs, so you can return -EINVAL or -ENOTSUPP for any value but zero.
This will create an ABI that can be later extended to fully support RFC.

RFC states that MKT connection identifiers can be specified using ranges and wildcards and the details are up to the implementation. Keys are *NOT* just bound to a classical TCP 4-tuple.

* src_addr and src_port is implicit in socket binding. Maybe in theory src_addr they could apply for a server socket bound to 0.0.0.0:PORT but userspace can just open more sockets.
* dst_port is not supported by MD5 and I can't think of any useful usecase. This is either well known (179 for BGP) or auto-allocated.
* tcp_md5 was recently enhanced allow a "prefixlen" for addr and "l3mdev" ifindex binding.

Quoted 3 lines

This last point shows that the binding features people require can't be easily predicted in advance so it's better to allow the rules to be extended.

Yeah, I see both changes you mention went on easy way as they reused existing paddings in the ABI structures.
Ok, if you don't want to reserve src_addr/src_port/dst_addr/dst_port, than how about reserving some space for those instead?

Quoted 12 lines
Quoted 12 lines

The same is about key: I don't think you need to define/use tcp_authopt_alg.
Just use algo name - that way TCP-AO will automatically be able to use any algo supported by crypto engine.
See how xfrm does it, e.g.:
struct xfrm_algo_auth {
     char        alg_name[64];
     unsigned int    alg_key_len;    /* in bits */
     unsigned int    alg_trunc_len;  /* in bits */
     char        alg_key[0];
};

Quoted 6 lines
Quoted 2 lines

So you can let a user chose maclen instead of hard-coding it. Much more extendable than what you propose.

This complicates ABI and implementation with features that are not needed. I'd much rather only expose an enum of real-world tcp-ao algorithms.

I see it exactly the opposite way: a new enum unnecessary complicates ABI, instead of passing alg_name[] to crypto engine. No need to add any support in tcp-ao as the algorithms are already provided by kernel. That is how it transparently works for ipsec, why not for tcp-ao?

Quoted 27 lines
Quoted 26 lines

[..]

Quoted 8 lines
(quoted diff) +8 -0
+#ifdef CONFIG_TCP_AUTHOPT
+       case TCP_AUTHOPT: {
+               struct tcp_authopt info;
+
+               if (get_user(len, optlen))
+                       return -EFAULT;
+
+               lock_sock(sk);

Review

Quoted 10 lines
(quoted diff) +10 -0
+               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;
+       }

I'm pretty sure it's not a good choice to write partly tcp_authopt. And a user has no way to check what's the correct len on this kernel. Instead of len = min_t(unsigned int, len, sizeof(info)), it should be if (len != sizeof(info))
     return -EINVAL;

Quoted 3 lines
Quoted 1 line

Purpose is to allow sockopts to grow as md5 has grown.

md5 has not grown. See above.

MD5 uapi has - e.g., 8917a777be3ba and 6b102db50cdde. We want similar capabilities for growth with this API.

Quoted 15 lines

Another issue with your approach

net/ipv4/tcp_authopt.c +6 -0
+       /* 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;

is that userspace compiled with updated/grew structure will fail on older kernel. So, no extension without breaking something is possible. Which also reminds me that currently you don't validate (opt.flags) for unknown by kernel flags.

Quoted 9 lines

Extending syscalls is impossible without breaking userspace if ABI is not designed with extensibility in mind. That was quite a big problem, and still is. Please, read this carefully:
https://lwn.net/Articles/830666/

That is why I'm suggesting you all those changes that will be harder to fix when/if your patches get accepted.
As an example how it should work see in copy_clone_args_from_user().

Look at how TCP_ZEROCOPY_RECEIVE has grown over releases as an example of how to properly handle this.

#11 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

Hi David,

Quoted 3 lines · David Ahern
Quoted 2 lines · Dmitry Safonov
Quoted 1 line · Leonard Crestez

On 8/10/21 11:41 PM, Dmitry Safonov wrote:

[..]

Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

I'm pretty sure it's not a good choice to write partly tcp_authopt. And a user has no way to check what's the correct len on this kernel. Instead of len = min_t(unsigned int, len, sizeof(info)), it should be if (len != sizeof(info))
     return -EINVAL;

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

Purpose is to allow sockopts to grow as md5 has grown.

md5 has not grown. See above.

MD5 uapi has - e.g., 8917a777be3ba and 6b102db50cdde. We want similar capabilities for growth with this API.

So, you mean adding a new setsockopt when the struct has to be extended? Like TCP_AUTHOPT_EXT?

It can work, but sounds like adding a new syscall every time the old one can't be extended. I can see that with current limitations on TCP-AO RFC the ABI in these patches will have to be extended.

The second commit started using new cmd.tcpm_flags, where unknown flags are still at this moment silently ignored by the kernel. So 6b102db50cdd could have introduced a regression in userspace. By luck and by reason that md5 isn't probably frequently used it didn't.
Not nice at all example for newer APIs.

Quoted 19 lines
Quoted 19 lines

Another issue with your approach

(quoted diff) +6 -0
+       /* 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;

is that userspace compiled with updated/grew structure will fail on older kernel. So, no extension without breaking something is possible. Which also reminds me that currently you don't validate (opt.flags) for unknown by kernel flags.

Extending syscalls is impossible without breaking userspace if ABI is not designed with extensibility in mind. That was quite a big problem, and still is. Please, read this carefully:
https://lwn.net/Articles/830666/

Quoted 7 lines
Quoted 4 lines

That is why I'm suggesting you all those changes that will be harder to fix when/if your patches get accepted.
As an example how it should work see in copy_clone_args_from_user().

Look at how TCP_ZEROCOPY_RECEIVE has grown over releases as an example of how to properly handle this.

Exactly.

: switch (len) {
: case offsetofend(...)
: case offsetofend(...)

And than also:
: if (unlikely(len > sizeof(zc))) {
: err = check_zeroed_user(optval + sizeof(zc),
: len - sizeof(zc));

Does it sound similar to what I've written in my ABI review? And what the LWN article has in it.
Please, look again at the patch I replied to.

Thanks,
Dmitry

#12 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

Quoted 15 lines · Dmitry Safonov

Hi David,

Quoted 3 lines · David Ahern
Quoted 2 lines · Dmitry Safonov
Quoted 1 line · Leonard Crestez

On 8/10/21 11:41 PM, Dmitry Safonov wrote:

[..]

Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
Quoted 5 lines

I'm pretty sure it's not a good choice to write partly tcp_authopt. And a user has no way to check what's the correct len on this kernel. Instead of len = min_t(unsigned int, len, sizeof(info)), it should be if (len != sizeof(info))
     return -EINVAL;

Purpose is to allow sockopts to grow as md5 has grown.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

md5 has not grown. See above.

MD5 uapi has - e.g., 8917a777be3ba and 6b102db50cdde. We want similar capabilities for growth with this API.

So, you mean adding a new setsockopt when the struct has to be extended? Like TCP_AUTHOPT_EXT?

uh, no. That was needed because of failures with the original implementation wrt checking that all unused bits are 0. If checking is not done from day 1, that field can never be used in the future.

My point here was only that MD5 uapi was extended.

My second point is more relevant to Leonard as a very recent example of how to build an extendable struct.

Quoted 22 lines
Quoted 3 lines

Look at how TCP_ZEROCOPY_RECEIVE has grown over releases as an example of how to properly handle this.

Exactly.

: switch (len) {
: case offsetofend(...)
: case offsetofend(...)

And than also:
: if (unlikely(len > sizeof(zc))) {
: err = check_zeroed_user(optval + sizeof(zc),
: len - sizeof(zc));

Does it sound similar to what I've written in my ABI review? And what the LWN article has in it.
Please, look again at the patch I replied to.

Thanks,
Dmitry

#13 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

In reply to Dmitry Safonov
Quoted 40 lines · Dmitry Safonov
Quoted 10 lines · Leonard Crestez
Quoted 9 lines · Dmitry Safonov

I wonder if it's also worth saving some bytes by something like struct tcp_ao_key *key;

With
struct tcp_ao_key {
       u8 keylen;
       u8 key[0];
};

Quoted 4 lines
Quoted 1 line

Hmm?

This increases memory management complexity for very minor gains. Very few tcp_authopt_key will ever be created.

The change doesn't seem to be big, like:

net/ipv4/tcp_authopt.c +0 -0
@@ -422,8 +422,16 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t

optval, unsig>

         key_info = __tcp_authopt_key_info_lookup(sk, info, opt.local_id);
         if (key_info)
                 tcp_authopt_key_del(sk, info, key_info);
net/ipv4/tcp_authopt.c +2 -0
+
+       key = sock_kmalloc(sk, sizeof(*key) + opt.keylen, GFP_KERNEL |

__GFP_ZERO);

net/ipv4/tcp_authopt.c +5 -0
+       if (!key) {
+               tcp_authopt_alg_release(alg);
+               return -ENOMEM;
+       }
+
         key_info = sock_kmalloc(sk, sizeof(*key_info), GFP_KERNEL |
__GFP_ZERO);
         if (!key_info) {
+               sock_kfree_s(sk, key, sizeof(*key) + opt.keylen);
                 tcp_authopt_alg_release(alg);
                 return -ENOMEM;
         }
Quoted 3 lines

I don't know, probably it'll be enough for every user to limit their keys by length of 80, but if one day it won't be enough - this ABI will be painful to extend.

struct tcp_authopt_key also needs to be modified and a separate copy_from_user is required. It's not very complicated but not very useful either.

Quoted 47 lines
Quoted 47 lines
Quoted 26 lines
Quoted 12 lines
(quoted diff) +12 -0
+struct tcp_authopt_key {
+       /** @flags: Combination of &enum tcp_authopt_key_flag */
+       __u32   flags;
+       /** @local_id: Local identifier */
+       __u32   local_id;
+       /** @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 */

Review

Quoted 10 lines
(quoted diff) +10 -0
+       __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;
+};

It'll be an ABI if this is accepted. As it is - it can't support RFC5925 fully.
Extending syscall ABI is painful. I think, even the initial ABI *must*

Quoted 8 lines

support
all possible features of the RFC.
In other words, there must be src_addr, src_port, dst_addr and dst_port. I can see from docs you've written you don't want to support a mix of different
addr/port MKTs, so you can return -EINVAL or -ENOTSUPP for any value but zero.
This will create an ABI that can be later extended to fully support RFC.

RFC states that MKT connection identifiers can be specified using ranges and wildcards and the details are up to the implementation. Keys are *NOT* just bound to a classical TCP 4-tuple.

* src_addr and src_port is implicit in socket binding. Maybe in theory src_addr they could apply for a server socket bound to 0.0.0.0:PORT but userspace can just open more sockets.
* dst_port is not supported by MD5 and I can't think of any useful usecase. This is either well known (179 for BGP) or auto-allocated.
* tcp_md5 was recently enhanced allow a "prefixlen" for addr and "l3mdev" ifindex binding.

Quoted 8 lines
Quoted 3 lines

This last point shows that the binding features people require can't be easily predicted in advance so it's better to allow the rules to be extended.

Yeah, I see both changes you mention went on easy way as they reused existing paddings in the ABI structures.
Ok, if you don't want to reserve src_addr/src_port/dst_addr/dst_port, than how about reserving some space for those instead?

My idea was that each additional binding featurs can be added as a new bit flag in tcp_authopt_key_flag and the structure extended. Older applications won't pass the flag and kernel will silently accept the shorter optval and you get compatibility.

As far as I can tell MD5 supports binding in 3 ways:

1) By dst ip address
2) By dst ip address and prefixlen
3) By ifindex for vrfs

Current version of tcp_authopt only supports (1) but I think adding the other methods isn't actually difficult at all.

I'd rather not guess at future features by adding unused fields.

Quoted 15 lines
Quoted 15 lines
Quoted 14 lines

The same is about key: I don't think you need to define/use tcp_authopt_alg.
Just use algo name - that way TCP-AO will automatically be able to use any algo supported by crypto engine.
See how xfrm does it, e.g.:
struct xfrm_algo_auth {
     char        alg_name[64];
     unsigned int    alg_key_len;    /* in bits */ unsigned int    alg_trunc_len;  /* in bits */
     char        alg_key[0];
};

So you can let a user chose maclen instead of hard-coding it. Much more extendable than what you propose.

Quoted 8 lines
Quoted 3 lines

This complicates ABI and implementation with features that are not needed. I'd much rather only expose an enum of real-world tcp-ao algorithms.

I see it exactly the opposite way: a new enum unnecessary complicates ABI, instead of passing alg_name[] to crypto engine. No need to add any support in tcp-ao as the algorithms are already provided by kernel. That is how it transparently works for ipsec, why not for tcp-ao?

The TCP Authentication Option standard has been out there for many many years now and alternative algorithms are not widely used. I think cisco has a third algorithm? What you're asking for is a large extension of the IETF standard.

If you look at the rest of this series I had a lot of trouble with crypto tfm allocation context so I had to create per-cpu pool, similar to tcp-md5. Should I potentially create a pool for each alg known to crypto-api?

Letting use control algorithms and traffickey and mac lengths creates new potential avenues for privilege escalation that need to be checked.

As much as possible I would like to avoid exposing the linux crypto api through TCP sockopts.

I was also thinking of having a non-namespaced sysctl to disable tcp_authopt by default for security reasons. Unless you're running a router you should never let userspace touch these options.

Quoted 39 lines
Quoted 26 lines
Quoted 17 lines

[..]

Quoted 16 lines
(quoted diff) +16 -0
+#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;
Quoted 8 lines
Quoted 2 lines

+               return 0;
+       }

I'm pretty sure it's not a good choice to write partly tcp_authopt. And a user has no way to check what's the correct len on this kernel. Instead of len = min_t(unsigned int, len, sizeof(info)), it should be if (len != sizeof(info))
     return -EINVAL;

Quoted 1 line

Purpose is to allow sockopts to grow as md5 has grown.

md5 has not grown. See above.

Another issue with your approach

net/ipv4/tcp_authopt.c +6 -0
+       /* 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;
Quoted 2 lines

is that userspace compiled with updated/grew structure will fail on older kernel. So, no extension without breaking something is possible.

Userspace that needs new features and also compatibility with older kernels could check something like uname.

I think this is already a problem with md5: passing TCP_MD5SIG_FLAG_PREFIX on an old kernel simply won't take effect and that's fine.

The bigger concern is to ensure that old binaries work without changes.

Quoted 2 lines

Which also reminds me that currently you don't validate (opt.flags) for unknown by kernel flags.

Not sure what you mean, it is explicitly only known flags that are copied from userspace. I can make setsockopt return an error on unknown flags, this will make new apps fail more explicitly on old kernels.

Quoted 7 lines

Extending syscalls is impossible without breaking userspace if ABI is not designed with extensibility in mind. That was quite a big problem, and still is. Please, read this carefully:
https://lwn.net/Articles/830666/

That is why I'm suggesting you all those changes that will be harder to fix when/if your patches get accepted.

Both of the sockopt structs have a "flags" field and structure expansion will be accompanied by new flags. Is this not sufficient for compatibility?

#14 lore raw

Re: [RFCv2 1/9] tcp: authopt: Initial support and key management

In reply to Leonard Crestez
Quoted 26 lines · Leonard Crestez
Quoted 11 lines · Dmitry Safonov

On Tue, 10 Aug 2021 at 02:50, Leonard Crestez <[email protected]>

Quoted 9 lines

+       /* If an old value exists for same local_id it is deleted */ +       key_info = __tcp_authopt_key_info_lookup(sk, info, opt.local_id);
+       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;

Quoted 3 lines

1. You don't need sock_kmalloc() together with tcp_authopt_key_del(). It just frees the memory and allocates it back straight away - no sense in doing that.

The list is scanned in multiple places in later commits using nothing but an rcu_read_lock, this means that keys can't be updated in-place.

Quoted 8 lines

2. I think RFC says you must not allow a user to change an existing key:

Quoted 2 lines

MKT parameters are not changed. Instead, new MKTs can be installed, and a connection can change which MKT it uses.

IIUC, it means that one can't just change an existing MKT, but one can remove and later
add MKT with the same (send_id, recv_id, src_addr/port, dst_addr/port).

Quoted 5 lines
Quoted 3 lines

So, a reasonable thing to do:
if (key_info)
     return -EEXIST.

You're right, making the user delete keys explicitly is better.

On a second thought this might be required to mark keys as "send-only" and "recv-only" atomically.

Separately from RFC5925 some vendors implement a "keychain" model based on RFC8177 where each key has a distinct "accept-lifetime" and a "send-lifetime". This could be implemented by adding flags "expired_for_send" and "expired_for_recv" but requires the ability to set an expiration mark without the key ever being deleted.

--
Regards,
Leonard

#15 lore raw

RFC v2 2/9 docs: Add user documentation for tcp_authopt

In reply to Leonard Crestez

The .rst documentation contains a brief description of the user interface and includes kernel-doc generated from uapi header.

---
 Documentation/networking/index.rst       |  1 +
 Documentation/networking/tcp_authopt.rst | 44 ++++++++++++++++++++++++
 2 files changed, 45 insertions(+)
 create mode 100644 Documentation/networking/tcp_authopt.rst
Documentation/networking/index.rst +1 -0
index 58bc8cd367c6..f5c324a060d8 100644
@@ -100,10 +100,11 @@ Contents:
    strparser
    switchdev
    sysfs-tagging
    tc-actions-env-rules
    tcp-thin
+   tcp_authopt
    team
    timestamping
    tipc
    tproxy
    tuntap
Documentation/networking/tcp_authopt.rst new file +44 -0

44 lines not on this page · raw message

Signature · 1 line
2.25.1
#16 lore raw

RFC v2 3/9 tcp: authopt: Add crypto initialization

In reply to Leonard Crestez

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

---
 include/net/tcp_authopt.h |   3 +
 net/ipv4/tcp_authopt.c    | 177 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 178 insertions(+), 2 deletions(-)
include/net/tcp_authopt.h +3 -0
index 458d108bb7a8..bd5ba95e15de 100644
@@ -2,10 +2,12 @@
 #ifndef _LINUX_TCP_AUTHOPT_H
 #define _LINUX_TCP_AUTHOPT_H
 
 #include <uapi/linux/tcp.h>
 
+struct tcp_authopt_alg_imp;
+
 /* Representation of a Master Key Tuple as per RFC5925 */
 struct tcp_authopt_key_info {
 	struct hlist_node node;
 	/* Local identifier */
 	u32 local_id;
@@ -15,10 +17,11 @@ struct tcp_authopt_key_info {
 	u8 alg_id;
 	u8 keylen;
 	u8 key[TCP_AUTHOPT_MAXKEYLEN];
 	struct rcu_head rcu;
 	struct sockaddr_storage addr;
+	struct tcp_authopt_alg_imp *alg;
 };
 
 /* Per-socket information regarding tcp_authopt */
 struct tcp_authopt_info {
 	/* List of tcp_authopt_key_info */
net/ipv4/tcp_authopt.c +175 -2

234 lines not on this page · raw message

Signature · 1 line
2.25.1
#17 lore raw

RFC v2 4/9 tcp: authopt: Compute packet signatures

In reply to Leonard Crestez

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.

---
 net/ipv4/tcp_authopt.c | 467 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 467 insertions(+)
net/ipv4/tcp_authopt.c +467 -0
index 799607fc076f..a42daadf6b7d 100644
@@ -341,5 +341,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;
+}
Signature · 1 line
2.25.1
#18 lore raw

Re: [RFCv2 4/9] tcp: authopt: Compute packet signatures

reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross #…

Hi Leonard,

[FYI, it's a private test report for your RFC patch.] [auto build test WARNING on 2a2b6e3640c43a808dcb5226963e2cc0669294b1]

url:    https://github.com/0day-ci/linux/commits/Leonard-Crestez/tcp-Initial-support-for-RFC5925-auth-option/20210810-053824
base:   2a2b6e3640c43a808dcb5226963e2cc0669294b1
config: riscv-allyesconfig (attached as .config)
compiler: riscv64-linux-gcc (GCC) 10.3.0

reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# https://github.com/0day-ci/linux/commit/1a34e246690a909d4f7e783ae2aa8db24a22ca72
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Leonard-Crestez/tcp-Initial-support-for-RFC5925-auth-option/20210810-053824 git checkout 1a34e246690a909d4f7e783ae2aa8db24a22ca72
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-10.3.0 make.cross ARCH=riscv

If you fix the issue, kindly add following tag as appropriate

All warnings (new ones prefixed by >>):

net/ipv4/tcp_authopt.c:160:30: warning: no previous prototype for '__tcp_authopt_key_info_lookup' [-Wmissing-prototypes]
160 | struct tcp_authopt_key_info *__tcp_authopt_key_info_lookup(const struct sock *sk,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
net/ipv4/tcp_authopt.c: In function 'tcp_set_authopt':
net/ipv4/tcp_authopt.c:211:62: error: expected ';' before ')' token

     211 |  info->flags = opt.flags & TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED);
         |                                                              ^
         |                                                              ;
   net/ipv4/tcp_authopt.c:211:62: error: expected statement before ')' token
   net/ipv4/tcp_authopt.c: At top level:
   net/ipv4/tcp_authopt.c:249:6: warning: no previous prototype for '__tcp_authopt_info_free' [-Wmissing-prototypes]
     249 | void __tcp_authopt_info_free(struct sock *sk, struct tcp_authopt_info *info)
         |      ^~~~~~~~~~~~~~~~~~~~~~~
   net/ipv4/tcp_authopt.c: In function 'tcp_authopt_shash_traffic_key':
>> net/ipv4/tcp_authopt.c:377:21: warning: variable 'daddr' set but not used [-Wunused-but-set-variable]
     377 |    struct in6_addr *daddr;
         |                     ^~~~~
>> net/ipv4/tcp_authopt.c:376:21: warning: variable 'saddr' set but not used [-Wunused-but-set-variable]
     376 |    struct in6_addr *saddr;
         |                     ^~~~~
   net/ipv4/tcp_authopt.c: At top level:
>> net/ipv4/tcp_authopt.c:769:5: warning: no previous prototype for '__tcp_authopt_calc_mac' [-Wmissing-prototypes]

769 | int __tcp_authopt_calc_mac(struct sock *sk,
| ^~~~~~~~~~~~~~~~~~~~~~
net/ipv4/tcp_authopt.c:117:13: warning: 'tcp_authopt_alg_incref' defined but not used [-Wunused-function]
117 | static void tcp_authopt_alg_incref(struct tcp_authopt_alg_imp *alg)
| ^~~~~~~~~~~~~~~~~~~~~~

vim +/daddr +377 net/ipv4/tcp_authopt.c

346
347 /* feed traffic key into shash */
348 static int tcp_authopt_shash_traffic_key(struct shash_desc *desc,
349 struct sock *sk,
350 struct sk_buff *skb,
351 bool input,
352 bool ipv6)
353 {
354 struct tcphdr *th = tcp_hdr(skb);
355 int err;
356 __be32 sisn, disn;
357 __be16 digestbits = htons(crypto_shash_digestsize(desc->tfm) * 8);
358
359 // RFC5926 section 3.1.1.1
360 err = crypto_shash_update(desc, "\x01TCP-AO", 7);
361 if (err)
362 return err;
363
364 /* Addresses from packet on input and from socket on output
365 * This is because on output MAC is computed before prepending IP header
366 */
367 if (input) {
368 if (ipv6)
369 err = crypto_shash_update(desc, (u8 *)&ipv6_hdr(skb)->saddr, 32);
370 else
371 err = crypto_shash_update(desc, (u8 *)&ip_hdr(skb)->saddr, 8);
372 if (err)
373 return err;
374 } else {
375 if (ipv6) {

 > 376				struct in6_addr *saddr;
 > 377				struct in6_addr *daddr;

378
379 saddr = &sk->sk_v6_rcv_saddr;
380 daddr = &sk->sk_v6_daddr;
381 err = crypto_shash_update(desc, (u8 *)&sk->sk_v6_rcv_saddr, 16);
382 if (err)
383 return err;
384 err = crypto_shash_update(desc, (u8 *)&sk->sk_v6_daddr, 16);
385 if (err)
386 return err;
387 } else {
388 err = crypto_shash_update(desc, (u8 *)&sk->sk_rcv_saddr, 4);
389 if (err)
390 return err;
391 err = crypto_shash_update(desc, (u8 *)&sk->sk_daddr, 4);
392 if (err)
393 return err;
394 }
395 }
396
397 /* TCP ports from header */
398 err = crypto_shash_update(desc, (u8 *)&th->source, 4);
399 if (err)
400 return err;
401
402 /* special cases for SYN and SYN/ACK */
403 if (th->syn && !th->ack) {
404 sisn = th->seq;
405 disn = 0;
406 } else if (th->syn && th->ack) {
407 sisn = th->seq;
408 disn = htonl(ntohl(th->ack_seq) - 1);
409 } else {
410 struct tcp_authopt_info *authopt_info;
411
412 /* Fetching authopt_info like this means it's possible that authopt_info
413 * was deleted while we were hashing. If that happens we drop the packet
414 * which should be fine.
415 *
416 * A better solution might be to always pass info as a parameter, or
417 * compute traffic_key for established sockets separately.
418 */
419 rcu_read_lock();
420 authopt_info = rcu_dereference(tcp_sk(sk)->authopt_info);
421 if (!authopt_info) {
422 rcu_read_unlock();
423 return -EINVAL;
424 }
425 /* Initial sequence numbers for ESTABLISHED connections from info */
426 if (input) {
427 sisn = htonl(authopt_info->dst_isn);
428 disn = htonl(authopt_info->src_isn);
429 } else {
430 sisn = htonl(authopt_info->src_isn);
431 disn = htonl(authopt_info->dst_isn);
432 }
433 rcu_read_unlock();
434 }
435
436 err = crypto_shash_update(desc, (u8 *)&sisn, 4);
437 if (err)
438 return err;
439 err = crypto_shash_update(desc, (u8 *)&disn, 4);
440 if (err)
441 return err;
442
443 err = crypto_shash_update(desc, (u8 *)&digestbits, 2);
444 if (err)
445 return err;
446
447 return 0;
448 }
449

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

#19 lore raw

RFC v2 5/9 tcp: authopt: Hook into tcp core

In reply to Leonard Crestez

The tcp_authopt features exposes a minimal interface to the rest of the TCP stack. Only a few functions are exposed and if the feature is disabled they return neutral values, avoiding ifdefs in the rest of the code.

Add calls into tcp authopt from send, receive and accept code.

---
 include/net/tcp_authopt.h |  55 +++++++++
 net/ipv4/tcp_authopt.c    | 227 ++++++++++++++++++++++++++++++++++++++
 net/ipv4/tcp_input.c      |  17 +++
 net/ipv4/tcp_ipv4.c       |   3 +
 net/ipv4/tcp_minisocks.c  |   2 +
 net/ipv4/tcp_output.c     |  56 +++++++++-
 net/ipv6/tcp_ipv6.c       |   4 +
 7 files changed, 363 insertions(+), 1 deletion(-)
include/net/tcp_authopt.h +55 -0
index bd5ba95e15de..28ebc77473a4 100644
@@ -15,10 +15,11 @@ struct tcp_authopt_key_info {
 	/* Wire identifiers */
 	u8 send_id, recv_id;
 	u8 alg_id;
 	u8 keylen;
 	u8 key[TCP_AUTHOPT_MAXKEYLEN];
+	u8 maclen;
 	struct rcu_head rcu;
 	struct sockaddr_storage addr;
 	struct tcp_authopt_alg_imp *alg;
 };
 
@@ -31,15 +32,52 @@ struct tcp_authopt_info {
 	u32 dst_isn;
 	struct rcu_head rcu;
 };
 
 #ifdef CONFIG_TCP_AUTHOPT
+struct tcp_authopt_key_info *tcp_authopt_select_key(const struct sock *sk,
+						    const struct sock *addr_sk,
+						    u8 *rnextkeyid);
 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);
+int tcp_authopt_hash(
+		char *hash_location,
+		struct tcp_authopt_key_info *key,
+		struct sock *sk, struct sk_buff *skb);
+int __tcp_authopt_openreq(struct sock *newsk, const struct sock *oldsk, struct request_sock *req);
+static inline int tcp_authopt_openreq(
+		struct sock *newsk,
+		const struct sock *oldsk,
+		struct request_sock *req)
+{
+	if (!rcu_dereference(tcp_sk(oldsk)->authopt_info))
+		return 0;
+	else
+		return __tcp_authopt_openreq(newsk, oldsk, req);
+}
+int __tcp_authopt_inbound_check(
+		struct sock *sk,
+		struct sk_buff *skb,
+		struct tcp_authopt_info *info);
+static inline int tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb)
+{
+	struct tcp_authopt_info *info = rcu_dereference(tcp_sk(sk)->authopt_info);
+
+	if (info)
+		return __tcp_authopt_inbound_check(sk, skb, info);
+	else
+		return 0;
+}
 #else
+static struct tcp_authopt_key_info *tcp_authopt_select_key(const struct sock *sk,
+							   const struct sock *addr_sk,
+							   u8 *rnextkeyid);
+{
+	return NULL;
+}
 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)
@@ -51,8 +89,25 @@ 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;
 }
+static inline int tcp_authopt_hash(
+		char *hash_location,
+		struct tcp_authopt_key_info *key,
+		struct sock *sk, struct sk_buff *skb)
+{
+	return -EINVAL;
+}
+static inline int tcp_authopt_openreq(struct sock *newsk,
+				      const struct sock *oldsk,
+				      struct request_sock *req)
+{
+	return 0;
+}
+static inline int tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb)
+{
+	return 0;
+}
 #endif
 
 #endif /* _LINUX_TCP_AUTHOPT_H */
net/ipv4/tcp_authopt.c +227 -0

258 lines not on this page · raw message

net/ipv4/tcp_input.c +17 -0
index 3f7bd7ae7d7a..e0b51b2f747f 100644
@@ -70,10 +70,11 @@
 #include <linux/sysctl.h>
 #include <linux/kernel.h>
 #include <linux/prefetch.h>
 #include <net/dst.h>
 #include <net/tcp.h>
+#include <net/tcp_authopt.h>
 #include <net/inet_common.h>
 #include <linux/ipsec.h>
 #include <asm/unaligned.h>
 #include <linux/errqueue.h>
 #include <trace/events/tcp.h>
@@ -5967,18 +5968,34 @@ void tcp_init_transfer(struct sock *sk, int bpf_op, struct sk_buff *skb)
 	if (!icsk->icsk_ca_initialized)
 		tcp_init_congestion_control(sk);
 	tcp_init_buffer_space(sk);
 }
 
+static void tcp_authopt_finish_connect(struct sock *sk, struct sk_buff *skb)
+{
+#ifdef CONFIG_TCP_AUTHOPT
+	struct tcp_authopt_info *info;
+
+	info = rcu_dereference_protected(tcp_sk(sk)->authopt_info, lockdep_sock_is_held(sk));
+	if (!info)
+		return;
+
+	info->src_isn = ntohl(tcp_hdr(skb)->ack_seq) - 1;
+	info->dst_isn = ntohl(tcp_hdr(skb)->seq);
+#endif
+}
+
 void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
 {
 	struct tcp_sock *tp = tcp_sk(sk);
 	struct inet_connection_sock *icsk = inet_csk(sk);
 
 	tcp_set_state(sk, TCP_ESTABLISHED);
 	icsk->icsk_ack.lrcvtime = tcp_jiffies32;
 
+	tcp_authopt_finish_connect(sk, skb);
+
 	if (skb) {
 		icsk->icsk_af_ops->sk_rx_dst_set(sk, skb);
 		security_inet_conn_established(sk, skb);
 		sk_mark_napi_id(sk, skb);
 	}
net/ipv4/tcp_ipv4.c +3 -0
index 1348615c7576..a1d39183908c 100644
@@ -2060,10 +2060,13 @@ int tcp_v4_rcv(struct sk_buff *skb)
 		goto discard_and_relse;
 
 	if (tcp_v4_inbound_md5_hash(sk, skb, dif, sdif))
 		goto discard_and_relse;
 
+	if (tcp_authopt_inbound_check(sk, skb))
+		goto discard_and_relse;
+
 	nf_reset_ct(skb);
 
 	if (tcp_filter(sk, skb))
 		goto discard_and_relse;
 	th = (const struct tcphdr *)skb->data;
net/ipv4/tcp_minisocks.c +2 -0
index 0a4f3f16140a..4d7d86547b0e 100644
@@ -24,10 +24,11 @@
 #include <linux/slab.h>
 #include <linux/sysctl.h>
 #include <linux/workqueue.h>
 #include <linux/static_key.h>
 #include <net/tcp.h>
+#include <net/tcp_authopt.h>
 #include <net/inet_common.h>
 #include <net/xfrm.h>
 #include <net/busy_poll.h>
 
 static bool tcp_in_window(u32 seq, u32 end_seq, u32 s_win, u32 e_win)
@@ -539,10 +540,11 @@ struct sock *tcp_create_openreq_child(const struct sock *sk,
 #ifdef CONFIG_TCP_MD5SIG
 	newtp->md5sig_info = NULL;	/*XXX*/
 	if (newtp->af_specific->md5_lookup(sk, newsk))
 		newtp->tcp_header_len += TCPOLEN_MD5SIG_ALIGNED;
 #endif
+	tcp_authopt_openreq(newsk, sk, req);
 	if (skb->len >= TCP_MSS_DEFAULT + newtp->tcp_header_len)
 		newicsk->icsk_ack.last_seg_size = skb->len - newtp->tcp_header_len;
 	newtp->rx_opt.mss_clamp = req->mss;
 	tcp_ecn_openreq_child(newtp, req);
 	newtp->fastopen_req = NULL;
net/ipv4/tcp_output.c +55 -1

161 lines not on this page · raw message

net/ipv6/tcp_ipv6.c +4 -0
index 0ce52d46e4f8..51381a9c2bd5 100644
@@ -40,10 +40,11 @@
 #include <linux/icmpv6.h>
 #include <linux/random.h>
 #include <linux/indirect_call_wrapper.h>
 
 #include <net/tcp.h>
+#include <net/tcp_authopt.h>
 #include <net/ndisc.h>
 #include <net/inet6_hashtables.h>
 #include <net/inet6_connection_sock.h>
 #include <net/ipv6.h>
 #include <net/transp_v6.h>
@@ -1733,10 +1734,13 @@ INDIRECT_CALLABLE_SCOPE int tcp_v6_rcv(struct sk_buff *skb)
 		goto discard_and_relse;
 
 	if (tcp_v6_inbound_md5_hash(sk, skb, dif, sdif))
 		goto discard_and_relse;
 
+	if (tcp_authopt_inbound_check(sk, skb))
+		goto discard_and_relse;
+
 	if (tcp_filter(sk, skb))
 		goto discard_and_relse;
 	th = (const struct tcphdr *)skb->data;
 	hdr = ipv6_hdr(skb);
 	tcp_v6_fill_cb(skb, hdr, th);
Signature · 1 line
2.25.1
#20 lore raw

Re: [RFCv2 5/9] tcp: authopt: Hook into tcp core

reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross #…

Hi Leonard,

[FYI, it's a private test report for your RFC patch.] [auto build test WARNING on 2a2b6e3640c43a808dcb5226963e2cc0669294b1]

url:    https://github.com/0day-ci/linux/commits/Leonard-Crestez/tcp-Initial-support-for-RFC5925-auth-option/20210810-053824
base:   2a2b6e3640c43a808dcb5226963e2cc0669294b1
config: riscv-allyesconfig (attached as .config)
compiler: riscv64-linux-gcc (GCC) 10.3.0

reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# https://github.com/0day-ci/linux/commit/262249e896df425f9daf2437bdd09b46a30516b2
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Leonard-Crestez/tcp-Initial-support-for-RFC5925-auth-option/20210810-053824 git checkout 262249e896df425f9daf2437bdd09b46a30516b2
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-10.3.0 make.cross ARCH=riscv

If you fix the issue, kindly add following tag as appropriate

All warnings (new ones prefixed by >>):

net/ipv4/tcp_authopt.c:160:30: warning: no previous prototype for '__tcp_authopt_key_info_lookup' [-Wmissing-prototypes]
160 | struct tcp_authopt_key_info *__tcp_authopt_key_info_lookup(const struct sock *sk,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

>> net/ipv4/tcp_authopt.c:173:30: warning: no previous prototype for 'tcp_authopt_lookup_send' [-Wmissing-prototypes]
     173 | struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct tcp_authopt_info *info,
         |                              ^~~~~~~~~~~~~~~~~~~~~~~
   net/ipv4/tcp_authopt.c: In function 'tcp_set_authopt':
   net/ipv4/tcp_authopt.c:267:62: error: expected ';' before ')' token
     267 |  info->flags = opt.flags & TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED);
         |                                                              ^
         |                                                              ;
   net/ipv4/tcp_authopt.c:267:62: error: expected statement before ')' token
   net/ipv4/tcp_authopt.c: At top level:
   net/ipv4/tcp_authopt.c:305:6: warning: no previous prototype for '__tcp_authopt_info_free' [-Wmissing-prototypes]
     305 | void __tcp_authopt_info_free(struct sock *sk, struct tcp_authopt_info *info)
         |      ^~~~~~~~~~~~~~~~~~~~~~~
   In file included from include/linux/rbtree.h:22,
                    from include/linux/mm_types.h:10,
                    from include/linux/mmzone.h:21,
                    from include/linux/gfp.h:6,
                    from include/linux/mm.h:10,
                    from include/linux/bvec.h:14,
                    from include/linux/skbuff.h:17,
                    from include/linux/tcp.h:17,
                    from include/net/tcp.h:20,
                    from net/ipv4/tcp_authopt.c:4:
   net/ipv4/tcp_authopt.c: In function 'tcp_authopt_clone_keys':
   net/ipv4/tcp_authopt.c:412:80: error: 'sk' undeclared (first use in this function); did you mean 's8'?
     412 |  hlist_for_each_entry_rcu(old_key, &old_info->head, node, lockdep_sock_is_held(sk)) {
         |                                                                                ^~
   include/linux/rcupdate.h:318:8: note: in definition of macro 'RCU_LOCKDEP_WARN'
     318 |   if ((c) && debug_lockdep_rcu_enabled() && !__warned) { \
         |        ^
   include/linux/rculist.h:706:7: note: in expansion of macro '__list_check_rcu'
     706 |  for (__list_check_rcu(dummy, ## cond, 0),   \
         |       ^~~~~~~~~~~~~~~~
   net/ipv4/tcp_authopt.c:412:2: note: in expansion of macro 'hlist_for_each_entry_rcu'
     412 |  hlist_for_each_entry_rcu(old_key, &old_info->head, node, lockdep_sock_is_held(sk)) {
         |  ^~~~~~~~~~~~~~~~~~~~~~~~
   net/ipv4/tcp_authopt.c:412:80: note: each undeclared identifier is reported only once for each function it appears in
     412 |  hlist_for_each_entry_rcu(old_key, &old_info->head, node, lockdep_sock_is_held(sk)) {
         |                                                                                ^~
   include/linux/rcupdate.h:318:8: note: in definition of macro 'RCU_LOCKDEP_WARN'
     318 |   if ((c) && debug_lockdep_rcu_enabled() && !__warned) { \
         |        ^
   include/linux/rculist.h:706:7: note: in expansion of macro '__list_check_rcu'
     706 |  for (__list_check_rcu(dummy, ## cond, 0),   \
         |       ^~~~~~~~~~~~~~~~
   net/ipv4/tcp_authopt.c:412:2: note: in expansion of macro 'hlist_for_each_entry_rcu'
     412 |  hlist_for_each_entry_rcu(old_key, &old_info->head, node, lockdep_sock_is_held(sk)) {
         |  ^~~~~~~~~~~~~~~~~~~~~~~~
   net/ipv4/tcp_authopt.c: In function 'tcp_authopt_shash_traffic_key':
   net/ipv4/tcp_authopt.c:486:21: warning: variable 'daddr' set but not used [-Wunused-but-set-variable]
     486 |    struct in6_addr *daddr;
         |                     ^~~~~
   net/ipv4/tcp_authopt.c:485:21: warning: variable 'saddr' set but not used [-Wunused-but-set-variable]
     485 |    struct in6_addr *saddr;
         |                     ^~~~~

net/ipv4/tcp_authopt.c: At top level:
net/ipv4/tcp_authopt.c:878:5: warning: no previous prototype for '__tcp_authopt_calc_mac' [-Wmissing-prototypes]
878 | int __tcp_authopt_calc_mac(struct sock *sk,
| ^~~~~~~~~~~~~~~~~~~~~~

vim +/tcp_authopt_lookup_send +173 net/ipv4/tcp_authopt.c

172

 > 173	struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct tcp_authopt_info *info,

174 const struct sock *addr_sk,
175 int send_id)
176 {
177 struct tcp_authopt_key_info *result = NULL;
178 struct tcp_authopt_key_info *key;
179
180 hlist_for_each_entry_rcu(key, &info->head, node, 0) {
181 if (send_id >= 0 && key->send_id != send_id)
182 continue;
183 if (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND) {
184 if (addr_sk->sk_family == AF_INET) {
185 struct sockaddr_in *key_addr = (struct sockaddr_in *)&key->addr;
186 const struct in_addr *daddr =
187 (const struct in_addr *)&addr_sk->sk_daddr;
188
189 if (WARN_ON(key_addr->sin_family != AF_INET))
190 continue;
191 if (memcmp(daddr, &key_addr->sin_addr, sizeof(*daddr)))
192 continue;
193 }
194 if (addr_sk->sk_family == AF_INET6) {
195 struct sockaddr_in6 *key_addr = (struct sockaddr_in6 *)&key->addr;
196 const struct in6_addr *daddr = &addr_sk->sk_v6_daddr;
197
198 if (WARN_ON(key_addr->sin6_family != AF_INET6))
199 continue;
200 if (memcmp(daddr, &key_addr->sin6_addr, sizeof(*daddr)))
201 continue;
202 }
203 }
204 if (result && net_ratelimit())
205 pr_warn("ambiguous tcp authentication keys configured for send\n");
206 result = key;
207 }
208
209 return result;
210 }
211

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

#21 lore raw

RFC v2 6/9 tcp: authopt: Add key selection controls

In reply to Leonard Crestez

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.

---
 Documentation/networking/tcp_authopt.rst | 25 +++++++++++
 include/net/tcp_authopt.h                |  8 ++++
 include/uapi/linux/tcp.h                 | 31 +++++++++++++
 net/ipv4/tcp_authopt.c                   | 57 ++++++++++++++++++++++--
 4 files changed, 117 insertions(+), 4 deletions(-)
Documentation/networking/tcp_authopt.rst +25 -0
index 484f66f41ad5..cded87a70d05 100644
@@ -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
include/net/tcp_authopt.h +8 -0
index 28ebc77473a4..759635346874 100644
@@ -25,11 +25,19 @@ struct tcp_authopt_key_info {
 
 /* Per-socket information regarding tcp_authopt */
 struct tcp_authopt_info {
 	/* List of tcp_authopt_key_info */
 	struct hlist_head head;
+	/* Current send_key, cached.
+	 * Once a key is found it only changes by user or remote request.
+	 */
+	struct tcp_authopt_key_info *send_key;
 	u32 flags;
+	u32 local_send_id;
+	u8 send_rnextkeyid;
+	u8 recv_keyid;
+	u8 recv_rnextkeyid;
 	u32 src_isn;
 	u32 dst_isn;
 	struct rcu_head rcu;
 };
 
include/uapi/linux/tcp.h +31 -0
index bc47664156eb..c04f5166ab33 100644
@@ -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.local_send_id` 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;
+	/**
+	 * @local_send_id: `tcp_authopt_key.local_id` of preferred send key
+	 *
+	 * This is only used if `TCP_AUTHOPT_FLAG_LOCK_KEYID` is set
+	 */
+	__u32	local_send_id;
+	/**
+	 * @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`
  *
net/ipv4/tcp_authopt.c +53 -4

130 lines not on this page · raw message

Signature · 1 line
2.25.1
#22 lore raw

RFC v2 7/9 tcp: authopt: Add snmp counters

In reply to Leonard Crestez

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.

---
 include/uapi/linux/snmp.h | 1 +
 net/ipv4/proc.c           | 1 +
 net/ipv4/tcp_authopt.c    | 3 +++
 3 files changed, 5 insertions(+)
include/uapi/linux/snmp.h +1 -0
index 904909d020e2..1d96030889a1 100644
@@ -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
net/ipv4/proc.c +1 -0
index b0d3a09dc84e..61dd06f8389c 100644
@@ -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)
net/ipv4/tcp_authopt.c +3 -0
index 40412d9ea04e..bee8873423e4 100644
@@ -1043,10 +1043,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:
@@ -1054,10 +1055,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");
 			goto accept;
@@ -1071,10 +1073,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;
 	}
 
 accept:
Signature · 1 line
2.25.1
#23 lore raw

RFC v2 8/9 selftests: Initial TCP-AO support for nettest

In reply to Leonard Crestez
---
 tools/testing/selftests/net/nettest.c | 43 ++++++++++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)
tools/testing/selftests/net/nettest.c +42 -1
index bd6288302094..48d8f3a6eb27 100644
@@ -100,10 +100,12 @@ struct sock_args {
 		struct sockaddr_in v4;
 		struct sockaddr_in6 v6;
 	} md5_prefix;
 	unsigned int prefix_len;
 
+	const char *authopt_password;
+
 	/* expected addresses and device index for connection */
 	const char *expected_dev;
 	const char *expected_server_dev;
 	int expected_ifindex;
 
@@ -250,10 +252,36 @@ static int switch_ns(const char *ns)
 	close(fd);
 
 	return ret;
 }
 
+static int tcp_set_authopt(int sd, struct sock_args *args)
+{
+	struct tcp_authopt info;
+	struct tcp_authopt_key key;
+	int rc;
+
+	memset(&info, 0, sizeof(info));
+	info.local_send_id = 1;
+
+	rc = setsockopt(sd, IPPROTO_TCP, TCP_AUTHOPT, &info, sizeof(info));
+	if (rc < 0)
+		log_err_errno("setsockopt(TCP_AUHTOPT_KEY)");
+
+	memset(&key, 0, sizeof(key));
+	strcpy((char *)key.key, args->authopt_password);
+	key.keylen = strlen(args->authopt_password);
+	key.alg = TCP_AUTHOPT_ALG_HMAC_SHA_1_96;
+	key.local_id = 1;
+
+	rc = setsockopt(sd, IPPROTO_TCP, TCP_AUTHOPT_KEY, &key, sizeof(key));
+	if (rc < 0)
+		log_err_errno("setsockopt(TCP_AUHTOPT_KEY)");
+
+	return rc;
+}
+
 static int tcp_md5sig(int sd, void *addr, socklen_t alen, struct sock_args *args)
 {
 	int keylen = strlen(args->password);
 	struct tcp_md5sig md5sig = {};
 	int opt = TCP_MD5SIG;
@@ -1508,10 +1536,15 @@ static int do_server(struct sock_args *args, int ipc_fd)
 	if (args->password && tcp_md5_remote(lsd, args)) {
 		close(lsd);
 		goto err_exit;
 	}
 
+	if (args->authopt_password && tcp_set_authopt(lsd, args)) {
+		close(lsd);
+		goto err_exit;
+	}
+
 	ipc_write(ipc_fd, 1);
 	while (1) {
 		log_msg("waiting for client connection.\n");
 		FD_ZERO(&rfds);
 		FD_SET(lsd, &rfds);
@@ -1630,10 +1663,13 @@ static int connectsock(void *addr, socklen_t alen, struct sock_args *args)
 		goto out;
 
 	if (args->password && tcp_md5sig(sd, addr, alen, args))
 		goto err;
 
+	if (args->authopt_password && tcp_set_authopt(sd, args))
+		goto err;
+
 	if (args->bind_test_only)
 		goto out;
 
 	if (connect(sd, addr, alen) < 0) {
 		if (errno != EINPROGRESS) {
@@ -1819,11 +1855,11 @@ static int ipc_parent(int cpid, int fd, struct sock_args *args)
 
 	wait(&status);
 	return client_status;
 }
 
-#define GETOPT_STR  "sr:l:c:p:t:g:P:DRn:M:X:m:d:I:BN:O:SCi6xL:0:1:2:3:Fbq"
+#define GETOPT_STR  "sr:l:c:p:t:g:P:DRn:M:X:m:A:d:I:BN:O:SCi6xL:0:1:2:3:Fbq"
 
 static void print_usage(char *prog)
 {
 	printf(
 	"usage: %s OPTS\n"
@@ -1856,10 +1892,12 @@ static void print_usage(char *prog)
 	"    -n num        number of times to send message\n"
 	"\n"
 	"    -M password   use MD5 sum protection\n"
 	"    -X password   MD5 password for client mode\n"
 	"    -m prefix/len prefix and length to use for MD5 key\n"
+	"    -A password   use RFC5925 TCP Authentication option\n"
+	"\n"
 	"    -g grp        multicast group (e.g., 239.1.1.1)\n"
 	"    -i            interactive mode (default is echo and terminate)\n"
 	"\n"
 	"    -0 addr       Expected local address\n"
 	"    -1 addr       Expected remote address\n"
@@ -1970,10 +2008,13 @@ int main(int argc, char *argv[])
 			args.client_pw = optarg;
 			break;
 		case 'm':
 			args.md5_prefix_str = optarg;
 			break;
+		case 'A':
+			args.authopt_password = optarg;
+			break;
 		case 'S':
 			args.use_setsockopt = 1;
 			break;
 		case 'C':
 			args.use_cmsg = 1;
Signature · 1 line
2.25.1
#24 lore raw

RFC v2 9/9 selftests: Initial TCP-AO support for fcnal-test

In reply to Leonard Crestez

Just test that a correct password is required.

---
 tools/testing/selftests/net/fcnal-test.sh | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
tools/testing/selftests/net/fcnal-test.sh +22 -0
index a8ad92850e63..569c340040f4 100755
@@ -788,10 +788,31 @@ ipv4_ping()
 }
 
 ################################################################################
 # IPv4 TCP
 
+#
+# TCP Authentication Option Tests
+#
+ipv4_tcp_authopt()
+{
+	# basic use case
+	log_start
+	run_cmd nettest -s -A ${MD5_PW} &
+	sleep 1
+	run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_PW}
+	log_test $? 0 "AO: Simple password"
+
+	# wrong password
+	log_start
+	show_hint "Should timeout since client uses wrong password"
+	run_cmd nettest -s -A ${MD5_PW} &
+	sleep 1
+	run_cmd_nsb nettest -r ${NSA_IP} -A ${MD5_WRONG_PW}
+	log_test $? 2 "AO: Client uses wrong password"
+}
+
 #
 # MD5 tests without VRF
 #
 ipv4_tcp_md5_novrf()
 {
@@ -1119,10 +1140,11 @@ ipv4_tcp_novrf()
 	show_hint "Should fail 'Connection refused'"
 	run_cmd nettest -d ${NSA_DEV} -r ${a}
 	log_test_addr ${a} $? 1 "No server, device client, local conn"
 
 	ipv4_tcp_md5_novrf
+	ipv4_tcp_authopt
 }
 
 ipv4_tcp_vrf()
 {
 	local a
Signature · 1 line
2.25.1
#25 lore raw

Re: [RFCv2 9/9] selftests: Initial TCP-AO support for fcnal-test

Quoted 2 lines · Leonard Crestez

Just test that a correct password is required.

This test suite needs to be comprehensive that the UAPI works as designed and fails when it should - cleanly and with an extack message as to why some config option fails. Tests should cover the datapath - that it works properly when it should and fails cleanly when it should not. If addresses are involved in the configuration, then the tests need to be written for non VRFs, with VRFs and default VRF since addresses are relative.

Also, in tree test suites are best for the maintenance of this code going forward.

#26 lore raw

Re: [RFCv2 9/9] selftests: Initial TCP-AO support for fcnal-test

Quoted 12 lines · David Ahern
Quoted 2 lines · Leonard Crestez

Just test that a correct password is required.

This test suite needs to be comprehensive that the UAPI works as designed and fails when it should - cleanly and with an extack message as to why some config option fails. Tests should cover the datapath - that it works properly when it should and fails cleanly when it should not. If addresses are involved in the configuration, then the tests need to be written for non VRFs, with VRFs and default VRF since addresses are relative.

Quoted 2 lines

Also, in tree test suites are best for the maintenance of this code going forward.

I can try to integrate my python test suite into kselftest. It's not a very orthodox choice but a rewrite in C would be much larger.

--
Regards,
Leonard