diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Documentation/netlink/specs/handshake.yaml | 124 | ||||
-rw-r--r-- | Documentation/networking/index.rst | 1 | ||||
-rw-r--r-- | Documentation/networking/tls-handshake.rst | 217 | ||||
-rw-r--r-- | MAINTAINERS | 11 | ||||
-rw-r--r-- | include/net/handshake.h | 43 | ||||
-rw-r--r-- | include/trace/events/handshake.h | 159 | ||||
-rw-r--r-- | include/uapi/linux/handshake.h | 73 | ||||
-rw-r--r-- | net/Kconfig | 20 | ||||
-rw-r--r-- | net/Makefile | 1 | ||||
-rw-r--r-- | net/handshake/.kunitconfig | 11 | ||||
-rw-r--r-- | net/handshake/Makefile | 13 | ||||
-rw-r--r-- | net/handshake/genl.c | 58 | ||||
-rw-r--r-- | net/handshake/genl.h | 24 | ||||
-rw-r--r-- | net/handshake/handshake-test.c | 523 | ||||
-rw-r--r-- | net/handshake/handshake.h | 87 | ||||
-rw-r--r-- | net/handshake/netlink.c | 319 | ||||
-rw-r--r-- | net/handshake/request.c | 344 | ||||
-rw-r--r-- | net/handshake/tlshd.c | 418 | ||||
-rw-r--r-- | net/handshake/trace.c | 20 |
20 files changed, 2467 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index 70ec6037fa7a..51117ba29c88 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ modules.order !.gitignore !.mailmap !.rustfmt.toml +!.kunitconfig # # Generated include files diff --git a/Documentation/netlink/specs/handshake.yaml b/Documentation/netlink/specs/handshake.yaml new file mode 100644 index 000000000000..614f1a585511 --- /dev/null +++ b/Documentation/netlink/specs/handshake.yaml @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +# +# Author: Chuck Lever <chuck.lever@oracle.com> +# +# Copyright (c) 2023, Oracle and/or its affiliates. +# + +name: handshake + +protocol: genetlink + +doc: Netlink protocol to request a transport layer security handshake. + +definitions: + - + type: enum + name: handler-class + value-start: 0 + entries: [ none, tlshd, max ] + - + type: enum + name: msg-type + value-start: 0 + entries: [ unspec, clienthello, serverhello ] + - + type: enum + name: auth + value-start: 0 + entries: [ unspec, unauth, psk, x509 ] + +attribute-sets: + - + name: x509 + attributes: + - + name: cert + type: u32 + - + name: privkey + type: u32 + - + name: accept + attributes: + - + name: sockfd + type: u32 + - + name: handler-class + type: u32 + enum: handler-class + - + name: message-type + type: u32 + enum: msg-type + - + name: timeout + type: u32 + - + name: auth-mode + type: u32 + enum: auth + - + name: peer-identity + type: u32 + multi-attr: true + - + name: certificate + type: nest + nested-attributes: x509 + multi-attr: true + - + name: done + attributes: + - + name: status + type: u32 + - + name: sockfd + type: u32 + - + name: remote-auth + type: u32 + multi-attr: true + +operations: + list: + - + name: ready + doc: Notify handlers that a new handshake request is waiting + notify: accept + - + name: accept + doc: Handler retrieves next queued handshake request + attribute-set: accept + flags: [ admin-perm ] + do: + request: + attributes: + - handler-class + reply: + attributes: + - sockfd + - message-type + - timeout + - auth-mode + - peer-identity + - certificate + - + name: done + doc: Handler reports handshake completion + attribute-set: done + do: + request: + attributes: + - status + - sockfd + - remote-auth + +mcast-groups: + list: + - + name: none + - + name: tlshd diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst index 24bb256d6d53..a164ff074356 100644 --- a/Documentation/networking/index.rst +++ b/Documentation/networking/index.rst @@ -36,6 +36,7 @@ Contents: scaling tls tls-offload + tls-handshake nfc 6lowpan 6pack diff --git a/Documentation/networking/tls-handshake.rst b/Documentation/networking/tls-handshake.rst new file mode 100644 index 000000000000..a2817a88e905 --- /dev/null +++ b/Documentation/networking/tls-handshake.rst @@ -0,0 +1,217 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================= +In-Kernel TLS Handshake +======================= + +Overview +======== + +Transport Layer Security (TLS) is a Upper Layer Protocol (ULP) that runs +over TCP. TLS provides end-to-end data integrity and confidentiality in +addition to peer authentication. + +The kernel's kTLS implementation handles the TLS record subprotocol, but +does not handle the TLS handshake subprotocol which is used to establish +a TLS session. Kernel consumers can use the API described here to +request TLS session establishment. + +There are several possible ways to provide a handshake service in the +kernel. The API described here is designed to hide the details of those +implementations so that in-kernel TLS consumers do not need to be +aware of how the handshake gets done. + + +User handshake agent +==================== + +As of this writing, there is no TLS handshake implementation in the +Linux kernel. To provide a handshake service, a handshake agent +(typically in user space) is started in each network namespace where a +kernel consumer might require a TLS handshake. Handshake agents listen +for events sent from the kernel that indicate a handshake request is +waiting. + +An open socket is passed to a handshake agent via a netlink operation, +which creates a socket descriptor in the agent's file descriptor table. +If the handshake completes successfully, the handshake agent promotes +the socket to use the TLS ULP and sets the session information using the +SOL_TLS socket options. The handshake agent returns the socket to the +kernel via a second netlink operation. + + +Kernel Handshake API +==================== + +A kernel TLS consumer initiates a client-side TLS handshake on an open +socket by invoking one of the tls_client_hello() functions. First, it +fills in a structure that contains the parameters of the request: + +.. code-block:: c + + struct tls_handshake_args { + struct socket *ta_sock; + tls_done_func_t ta_done; + void *ta_data; + unsigned int ta_timeout_ms; + key_serial_t ta_keyring; + key_serial_t ta_my_cert; + key_serial_t ta_my_privkey; + unsigned int ta_num_peerids; + key_serial_t ta_my_peerids[5]; + }; + +The @ta_sock field references an open and connected socket. The consumer +must hold a reference on the socket to prevent it from being destroyed +while the handshake is in progress. The consumer must also have +instantiated a struct file in sock->file. + + +@ta_done contains a callback function that is invoked when the handshake +has completed. Further explanation of this function is in the "Handshake +Completion" sesction below. + +The consumer can fill in the @ta_timeout_ms field to force the servicing +handshake agent to exit after a number of milliseconds. This enables the +socket to be fully closed once both the kernel and the handshake agent +have closed their endpoints. + +Authentication material such as x.509 certificates, private certificate +keys, and pre-shared keys are provided to the handshake agent in keys +that are instantiated by the consumer before making the handshake +request. The consumer can provide a private keyring that is linked into +the handshake agent's process keyring in the @ta_keyring field to prevent +access of those keys by other subsystems. + +To request an x.509-authenticated TLS session, the consumer fills in +the @ta_my_cert and @ta_my_privkey fields with the serial numbers of +keys containing an x.509 certificate and the private key for that +certificate. Then, it invokes this function: + +.. code-block:: c + + ret = tls_client_hello_x509(args, gfp_flags); + +The function returns zero when the handshake request is under way. A +zero return guarantees the callback function @ta_done will be invoked +for this socket. The function returns a negative errno if the handshake +could not be started. A negative errno guarantees the callback function +@ta_done will not be invoked on this socket. + + +To initiate a client-side TLS handshake with a pre-shared key, use: + +.. code-block:: c + + ret = tls_client_hello_psk(args, gfp_flags); + +However, in this case, the consumer fills in the @ta_my_peerids array +with serial numbers of keys containing the peer identities it wishes +to offer, and the @ta_num_peerids field with the number of array +entries it has filled in. The other fields are filled in as above. + + +To initiate an anonymous client-side TLS handshake use: + +.. code-block:: c + + ret = tls_client_hello_anon(args, gfp_flags); + +The handshake agent presents no peer identity information to the remote +during this type of handshake. Only server authentication (ie the client +verifies the server's identity) is performed during the handshake. Thus +the established session uses encryption only. + + +Consumers that are in-kernel servers use: + +.. code-block:: c + + ret = tls_server_hello_x509(args, gfp_flags); + +or + +.. code-block:: c + + ret = tls_server_hello_psk(args, gfp_flags); + +The argument structure is filled in as above. + + +If the consumer needs to cancel the handshake request, say, due to a ^C +or other exigent event, the consumer can invoke: + +.. code-block:: c + + bool tls_handshake_cancel(sock); + +This function returns true if the handshake request associated with +@sock has been canceled. The consumer's handshake completion callback +will not be invoked. If this function returns false, then the consumer's +completion callback has already been invoked. + + +Handshake Completion +==================== + +When the handshake agent has completed processing, it notifies the +kernel that the socket may be used by the consumer again. At this point, +the consumer's handshake completion callback, provided in the @ta_done +field in the tls_handshake_args structure, is invoked. + +The synopsis of this function is: + +.. code-block:: c + + typedef void (*tls_done_func_t)(void *data, int status, + key_serial_t peerid); + +The consumer provides a cookie in the @ta_data field of the +tls_handshake_args structure that is returned in the @data parameter of +this callback. The consumer uses the cookie to match the callback to the +thread waiting for the handshake to complete. + +The success status of the handshake is returned via the @status +parameter: + ++------------+----------------------------------------------+ +| status | meaning | ++============+==============================================+ +| 0 | TLS session established successfully | ++------------+----------------------------------------------+ +| -EACCESS | Remote peer rejected the handshake or | +| | authentication failed | ++------------+----------------------------------------------+ +| -ENOMEM | Temporary resource allocation failure | ++------------+----------------------------------------------+ +| -EINVAL | Consumer provided an invalid argument | ++------------+----------------------------------------------+ +| -ENOKEY | Missing authentication material | ++------------+----------------------------------------------+ +| -EIO | An unexpected fault occurred | ++------------+----------------------------------------------+ + +The @peerid parameter contains the serial number of a key containing the +remote peer's identity or the value TLS_NO_PEERID if the session is not +authenticated. + +A best practice is to close and destroy the socket immediately if the +handshake failed. + + +Other considerations +-------------------- + +While a handshake is under way, the kernel consumer must alter the +socket's sk_data_ready callback function to ignore all incoming data. +Once the handshake completion callback function has been invoked, normal +receive operation can be resumed. + +Once a TLS session is established, the consumer must provide a buffer +for and then examine the control message (CMSG) that is part of every +subsequent sock_recvmsg(). Each control message indicates whether the +received message data is TLS record data or session metadata. + +See tls.rst for details on how a kTLS consumer recognizes incoming +(decrypted) application data, alerts, and handshake packets once the +socket has been promoted to use the TLS ULP. diff --git a/MAINTAINERS b/MAINTAINERS index 4fc57dfd5fd0..04ebde8ccb75 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8947,6 +8947,17 @@ Q: http://patchwork.linuxtv.org/project/linux-media/list/ T: git git://linuxtv.org/anttip/media_tree.git F: drivers/media/usb/hackrf/ +HANDSHAKE UPCALL FOR TRANSPORT LAYER SECURITY +M: Chuck Lever <chuck.lever@oracle.com> +L: kernel-tls-handshake@lists.linux.dev +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/netlink/specs/handshake.yaml +F: Documentation/networking/tls-handshake.rst +F: include/net/handshake.h +F: include/trace/events/handshake.h +F: net/handshake/ + HANTRO VPU CODEC DRIVER M: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar> M: Philipp Zabel <p.zabel@pengutronix.de> diff --git a/include/net/handshake.h b/include/net/handshake.h new file mode 100644 index 000000000000..3352b1ab43b3 --- /dev/null +++ b/include/net/handshake.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Generic netlink HANDSHAKE service. + * + * Author: Chuck Lever <chuck.lever@oracle.com> + * + * Copyright (c) 2023, Oracle and/or its affiliates. + */ + +#ifndef _NET_HANDSHAKE_H +#define _NET_HANDSHAKE_H + +enum { + TLS_NO_KEYRING = 0, + TLS_NO_PEERID = 0, + TLS_NO_CERT = 0, + TLS_NO_PRIVKEY = 0, +}; + +typedef void (*tls_done_func_t)(void *data, int status, + key_serial_t peerid); + +struct tls_handshake_args { + struct socket *ta_sock; + tls_done_func_t ta_done; + void *ta_data; + unsigned int ta_timeout_ms; + key_serial_t ta_keyring; + key_serial_t ta_my_cert; + key_serial_t ta_my_privkey; + unsigned int ta_num_peerids; + key_serial_t ta_my_peerids[5]; +}; + +int tls_client_hello_anon(const struct tls_handshake_args *args, gfp_t flags); +int tls_client_hello_x509(const struct tls_handshake_args *args, gfp_t flags); +int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags); +int tls_server_hello_x509(const struct tls_handshake_args *args, gfp_t flags); +int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags); + +bool tls_handshake_cancel(struct sock *sk); + +#endif /* _NET_HANDSHAKE_H */ diff --git a/include/trace/events/handshake.h b/include/trace/events/handshake.h new file mode 100644 index 000000000000..8dadcab5f12a --- /dev/null +++ b/include/trace/events/handshake.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM handshake + +#if !defined(_TRACE_HANDSHAKE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_HANDSHAKE_H + +#include <linux/net.h> +#include <linux/tracepoint.h> + +DECLARE_EVENT_CLASS(handshake_event_class, + TP_PROTO( + const struct net *net, + const struct handshake_req *req, + const struct sock *sk + ), + TP_ARGS(net, req, sk), + TP_STRUCT__entry( + __field(const void *, req) + __field(const void *, sk) + __field(unsigned int, netns_ino) + ), + TP_fast_assign( + __entry->req = req; + __entry->sk = sk; + __entry->netns_ino = net->ns.inum; + ), + TP_printk("req=%p sk=%p", + __entry->req, __entry->sk + ) +); +#define DEFINE_HANDSHAKE_EVENT(name) \ + DEFINE_EVENT(handshake_event_class, name, \ + TP_PROTO( \ + const struct net *net, \ + const struct handshake_req *req, \ + const struct sock *sk \ + ), \ + TP_ARGS(net, req, sk)) + +DECLARE_EVENT_CLASS(handshake_fd_class, + TP_PROTO( + const struct net *net, + const struct handshake_req *req, + const struct sock *sk, + int fd + ), + TP_ARGS(net, req, sk, fd), + TP_STRUCT__entry( + __field(const void *, req) + __field(const void *, sk) + __field(int, fd) + __field(unsigned int, netns_ino) + ), + TP_fast_assign( + __entry->req = req; + __entry->sk = req->hr_sk; + __entry->fd = fd; + __entry->netns_ino = net->ns.inum; + ), + TP_printk("req=%p sk=%p fd=%d", + __entry->req, __entry->sk, __entry->fd + ) +); +#define DEFINE_HANDSHAKE_FD_EVENT(name) \ + DEFINE_EVENT(handshake_fd_class, name, \ + TP_PROTO( \ + const struct net *net, \ + const struct handshake_req *req, \ + const struct sock *sk, \ + int fd \ + ), \ + TP_ARGS(net, req, sk, fd)) + +DECLARE_EVENT_CLASS(handshake_error_class, + TP_PROTO( + const struct net *net, + const struct handshake_req *req, + const struct sock *sk, + int err + ), + TP_ARGS(net, req, sk, err), + TP_STRUCT__entry( + __field(const void *, req) + __field(const void *, sk) + __field(int, err) + __field(unsigned int, netns_ino) + ), + TP_fast_assign( + __entry->req = req; + __entry->sk = sk; + __entry->err = err; + __entry->netns_ino = net->ns.inum; + ), + TP_printk("req=%p sk=%p err=%d", + __entry->req, __entry->sk, __entry->err + ) +); +#define DEFINE_HANDSHAKE_ERROR(name) \ + DEFINE_EVENT(handshake_error_class, name, \ + TP_PROTO( \ + const struct net *net, \ + const struct handshake_req *req, \ + const struct sock *sk, \ + int err \ + ), \ + TP_ARGS(net, req, sk, err)) + + +/* + * Request lifetime events + */ + +DEFINE_HANDSHAKE_EVENT(handshake_submit); +DEFINE_HANDSHAKE_ERROR(handshake_submit_err); +DEFINE_HANDSHAKE_EVENT(handshake_cancel); +DEFINE_HANDSHAKE_EVENT(handshake_cancel_none); +DEFINE_HANDSHAKE_EVENT(handshake_cancel_busy); +DEFINE_HANDSHAKE_EVENT(handshake_destruct); + + +TRACE_EVENT(handshake_complete, + TP_PROTO( + const struct net *net, + const struct handshake_req *req, + const struct sock *sk, + int status + ), + TP_ARGS(net, req, sk, status), + TP_STRUCT__entry( + __field(const void *, req) + __field(const void *, sk) + __field(int, status) + __field(unsigned int, netns_ino) + ), + TP_fast_assign( + __entry->req = req; + __entry->sk = sk; + __entry->status = status; + __entry->netns_ino = net->ns.inum; + ), + TP_printk("req=%p sk=%p status=%d", + __entry->req, __entry->sk, __entry->status + ) +); + +/* + * Netlink events + */ + +DEFINE_HANDSHAKE_ERROR(handshake_notify_err); +DEFINE_HANDSHAKE_FD_EVENT(handshake_cmd_accept); +DEFINE_HANDSHAKE_ERROR(handshake_cmd_accept_err); +DEFINE_HANDSHAKE_FD_EVENT(handshake_cmd_done); +DEFINE_HANDSHAKE_ERROR(handshake_cmd_done_err); + +#endif /* _TRACE_HANDSHAKE_H */ + +#include <trace/define_trace.h> diff --git a/include/uapi/linux/handshake.h b/include/uapi/linux/handshake.h new file mode 100644 index 000000000000..1de4d0b95325 --- /dev/null +++ b/include/uapi/linux/handshake.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/handshake.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_HANDSHAKE_H +#define _UAPI_LINUX_HANDSHAKE_H + +#define HANDSHAKE_FAMILY_NAME "handshake" +#define HANDSHAKE_FAMILY_VERSION 1 + +enum handshake_handler_class { + HANDSHAKE_HANDLER_CLASS_NONE, + HANDSHAKE_HANDLER_CLASS_TLSHD, + HANDSHAKE_HANDLER_CLASS_MAX, +}; + +enum handshake_msg_type { + HANDSHAKE_MSG_TYPE_UNSPEC, + HANDSHAKE_MSG_TYPE_CLIENTHELLO, + HANDSHAKE_MSG_TYPE_SERVERHELLO, +}; + +enum handshake_auth { + HANDSHAKE_AUTH_UNSPEC, + HANDSHAKE_AUTH_UNAUTH, + HANDSHAKE_AUTH_PSK, + HANDSHAKE_AUTH_X509, +}; + +enum { + HANDSHAKE_A_X509_CERT = 1, + HANDSHAKE_A_X509_PRIVKEY, + + __HANDSHAKE_A_X509_MAX, + HANDSHAKE_A_X509_MAX = (__HANDSHAKE_A_X509_MAX - 1) +}; + +enum { + HANDSHAKE_A_ACCEPT_SOCKFD = 1, + HANDSHAKE_A_ACCEPT_HANDLER_CLASS, + HANDSHAKE_A_ACCEPT_MESSAGE_TYPE, + HANDSHAKE_A_ACCEPT_TIMEOUT, + HANDSHAKE_A_ACCEPT_AUTH_MODE, + HANDSHAKE_A_ACCEPT_PEER_IDENTITY, + HANDSHAKE_A_ACCEPT_CERTIFICATE, + + __HANDSHAKE_A_ACCEPT_MAX, + HANDSHAKE_A_ACCEPT_MAX = (__HANDSHAKE_A_ACCEPT_MAX - 1) +}; + +enum { + HANDSHAKE_A_DONE_STATUS = 1, + HANDSHAKE_A_DONE_SOCKFD, + HANDSHAKE_A_DONE_REMOTE_AUTH, + + __HANDSHAKE_A_DONE_MAX, + HANDSHAKE_A_DONE_MAX = (__HANDSHAKE_A_DONE_MAX - 1) +}; + +enum { + HANDSHAKE_CMD_READY = 1, + HANDSHAKE_CMD_ACCEPT, + HANDSHAKE_CMD_DONE, + + __HANDSHAKE_CMD_MAX, + HANDSHAKE_CMD_MAX = (__HANDSHAKE_CMD_MAX - 1) +}; + +#define HANDSHAKE_MCGRP_NONE "none" +#define HANDSHAKE_MCGRP_TLSHD "tlshd" + +#endif /* _UAPI_LINUX_HANDSHAKE_H */ diff --git a/net/Kconfig b/net/Kconfig index f806722bccf4..7d39c1773eb4 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -68,6 +68,26 @@ source "net/iucv/Kconfig" source "net/smc/Kconfig" source "net/xdp/Kconfig" +config NET_HANDSHAKE + bool + depends on SUNRPC || NVME_TARGET_TCP || NVME_TCP + default y + +config NET_HANDSHAKE_KUNIT_TEST + tristate "KUnit tests for the handshake upcall mechanism" if !KUNIT_ALL_TESTS + default KUNIT_ALL_TESTS + depends on KUNIT + help + This builds the KUnit tests for the handshake upcall mechanism. + + KUnit tests run during boot and output the results to the debug + log in TAP format (https://testanything.org/). Only useful for + kernel devs running KUnit test harness and are not for inclusion + into a production build. + + For more information on KUnit and unit tests in general, refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + config INET bool "TCP/IP networking" help diff --git a/net/Makefile b/net/Makefile index 87592009366f..4c4dc535453d 100644 --- a/net/Makefile +++ b/net/Makefile @@ -79,3 +79,4 @@ obj-$(CONFIG_NET_NCSI) += ncsi/ obj-$(CONFIG_XDP_SOCKETS) += xdp/ obj-$(CONFIG_MPTCP) += mptcp/ obj-$(CONFIG_MCTP) += mctp/ +obj-$(CONFIG_NET_HANDSHAKE) += handshake/ diff --git a/net/handshake/.kunitconfig b/net/handshake/.kunitconfig new file mode 100644 index 000000000000..5c48cf4abca2 --- /dev/null +++ b/net/handshake/.kunitconfig @@ -0,0 +1,11 @@ +CONFIG_KUNIT=y +CONFIG_UBSAN=y +CONFIG_STACKTRACE=y +CONFIG_NET=y +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_INET=y +CONFIG_MULTIUSER=y +CONFIG_NFS_FS=y +CONFIG_SUNRPC=y +CONFIG_NET_HANDSHAKE=y +CONFIG_NET_HANDSHAKE_KUNIT_TEST=y diff --git a/net/handshake/Makefile b/net/handshake/Makefile new file mode 100644 index 000000000000..247d73c6ff6e --- /dev/null +++ b/net/handshake/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Generic HANDSHAKE service +# +# Author: Chuck Lever <chuck.lever@oracle.com> +# +# Copyright (c) 2023, Oracle and/or its affiliates. +# + +obj-y += handshake.o +handshake-y := genl.o netlink.o request.o tlshd.o trace.o + +obj-$(CONFIG_NET_HANDSHAKE_KUNIT_TEST) += handshake-test.o diff --git a/net/handshake/genl.c b/net/handshake/genl.c new file mode 100644 index 000000000000..9f29efb1493e --- /dev/null +++ b/net/handshake/genl.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/handshake.yaml */ +/* YNL-GEN kernel source */ + +#include <net/netlink.h> +#include <net/genetlink.h> + +#include "genl.h" + +#include <linux/handshake.h> + +/* HANDSHAKE_CMD_ACCEPT - do */ +static const struct nla_policy handshake_accept_nl_policy[HANDSHAKE_A_ACCEPT_HANDLER_CLASS + 1] = { + [HANDSHAKE_A_ACCEPT_HANDLER_CLASS] = NLA_POLICY_MAX(NLA_U32, 2), +}; + +/* HANDSHAKE_CMD_DONE - do */ +static const struct nla_policy handshake_done_nl_policy[HANDSHAKE_A_DONE_REMOTE_AUTH + 1] = { + [HANDSHAKE_A_DONE_STATUS] = { .type = NLA_U32, }, + [HANDSHAKE_A_DONE_SOCKFD] = { .type = NLA_U32, }, + [HANDSHAKE_A_DONE_REMOTE_AUTH] = { .type = NLA_U32, }, +}; + +/* Ops table for handshake */ +static const struct genl_split_ops handshake_nl_ops[] = { + { + .cmd = HANDSHAKE_CMD_ACCEPT, + .doit = handshake_nl_accept_doit, + .policy = handshake_accept_nl_policy, + .maxattr = HANDSHAKE_A_ACCEPT_HANDLER_CLASS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = HANDSHAKE_CMD_DONE, + .doit = handshake_nl_done_doit, + .policy = handshake_done_nl_policy, + .maxattr = HANDSHAKE_A_DONE_REMOTE_AUTH, + .flags = GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group handshake_nl_mcgrps[] = { + [HANDSHAKE_NLGRP_NONE] = { "none", }, + [HANDSHAKE_NLGRP_TLSHD] = { "tlshd", }, +}; + +struct genl_family handshake_nl_family __ro_after_init = { + .name = HANDSHAKE_FAMILY_NAME, + .version = HANDSHAKE_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = handshake_nl_ops, + .n_split_ops = ARRAY_SIZE(handshake_nl_ops), + .mcgrps = handshake_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(handshake_nl_mcgrps), +}; diff --git a/net/handshake/genl.h b/net/handshake/genl.h new file mode 100644 index 000000000000..2c1f1aa6a02a --- /dev/null +++ b/net/handshake/genl.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/handshake.yaml */ +/* YNL-GEN kernel header */ + +#ifndef _LINUX_HANDSHAKE_GEN_H +#define _LINUX_HANDSHAKE_GEN_H + +#include <net/netlink.h> +#include <net/genetlink.h> + +#include <linux/handshake.h> + +int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info); +int handshake_nl_done_doit(struct sk_buff *skb, struct genl_info *info); + +enum { + HANDSHAKE_NLGRP_NONE, + HANDSHAKE_NLGRP_TLSHD, +}; + +extern struct genl_family handshake_nl_family; + +#endif /* _LINUX_HANDSHAKE_GEN_H */ diff --git a/net/handshake/handshake-test.c b/net/handshake/handshake-test.c new file mode 100644 index 000000000000..e6adc5dec11a --- /dev/null +++ b/net/handshake/handshake-test.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * KUnit test of the handshake upcall mechanism. + */ + +#include <kunit/test.h> +#include <kunit/visibility.h> + +#include <linux/kernel.h> + +#include <net/sock.h> +#include <net/genetlink.h> +#include <net/netns/generic.h> + +#include <uapi/linux/handshake.h> +#include "handshake.h" + +MODULE_IMPORT_NS(EXPORTED_FOR_KUNIT_TESTING); + +static int test_accept_func(struct handshake_req *req, struct genl_info *info, + int fd) +{ + return 0; +} + +static void test_done_func(struct handshake_req *req, unsigned int status, + struct genl_info *info) +{ +} + +struct handshake_req_alloc_test_param { + const char *desc; + struct handshake_proto *proto; + gfp_t gfp; + bool expect_success; +}; + +static struct handshake_proto handshake_req_alloc_proto_2 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_NONE, +}; + +static struct handshake_proto handshake_req_alloc_proto_3 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_MAX, +}; + +static struct handshake_proto handshake_req_alloc_proto_4 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, +}; + +static struct handshake_proto handshake_req_alloc_proto_5 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_accept = test_accept_func, +}; + +static struct handshake_proto handshake_req_alloc_proto_6 = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_privsize = UINT_MAX, + .hp_accept = test_accept_func, + .hp_done = test_done_func, +}; + +static struct handshake_proto handshake_req_alloc_proto_good = { + .hp_handler_class = HANDSHAKE_HANDLER_CLASS_TLSHD, + .hp_accept = test_accept_func, + .hp_done = test_done_func, +}; + +static const +struct handshake_req_alloc_test_param handshake_req_alloc_params[] = { + { + .desc = "handshake_req_alloc NULL proto", + .proto = NULL, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc CLASS_NONE", + .proto = &handshake_req_alloc_proto_2, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc CLASS_MAX", + .proto = &handshake_req_alloc_proto_3, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc no callbacks", + .proto = &handshake_req_alloc_proto_4, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc no done callback", + .proto = &handshake_req_alloc_proto_5, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc excessive privsize", + .proto = &handshake_req_alloc_proto_6, + .gfp = GFP_KERNEL, + .expect_success = false, + }, + { + .desc = "handshake_req_alloc all good", + .proto = &handshake_req_alloc_proto_good, + .gfp = GFP_KERNEL, + .expect_success = true, + }, +}; + +static void +handshake_req_alloc_get_desc(const struct handshake_req_alloc_test_param *param, + char *desc) +{ + strscpy(desc, param->desc, KUNIT_PARAM_DESC_SIZE); +} + +/* Creates the function handshake_req_alloc_gen_params */ +KUNIT_ARRAY_PARAM(handshake_req_alloc, handshake_req_alloc_params, + handshake_req_alloc_get_desc); + +static void handshake_req_alloc_case(struct kunit *test) +{ + const struct handshake_req_alloc_test_param *param = test->param_value; + struct handshake_req *result; + + /* Arrange */ + + /* Act */ + result = handshake_req_alloc(param->proto, param->gfp); + + /* Assert */ + if (param->expect_success) + KUNIT_EXPECT_NOT_NULL(test, result); + else + KUNIT_EXPECT_NULL(test, result); + |