Page MenuHomeFreeBSD

Add kernel-side support for in-kernel TLS.
ClosedPublic

Authored by jhb on Aug 15 2019, 6:00 PM.

Details

Summary

KTLS adds support for in-kernel framing and encryption of Transport
Layer Security (1.0-1.2) data on TCP sockets. KTLS only supports
offload of TLS for transmitted data. Key negotation must still be
performed in userland. Once completed, transmit session keys for a
connection are provided to the kernel via a new TCP_TXTLS_ENABLE
socket option. All subsequent data transmitted on the socket is
placed into TLS frames and encrypted using the supplied keys.

Any data written to a KTLS-enabled socket via write(2), aio_write(2),
or sendfile(2) is assumed to be application data and is encoded in TLS
frames with an application data type. Individual records can be sent
with a custom type (e.g. handshake messages) via sendmsg(2) with a new
control message (TLS_SET_RECORD_TYPE) specifying the record type.

At present, rekeying is not supported though the in-kernel framework
should support rekeying.

KTLS makes use of the recently added unmapped mbufs to store TLS
frames in the socket buffer. Each TLS frame is described by a single
ext_pgs mbuf. The ext_pgs structure contains the header of the TLS
record (and trailer for encrypted records) as well as references to
the associated TLS session.

KTLS supports two primary methods of encrypting TLS frames: software
TLS and ifnet TLS.

Software TLS marks mbufs holding socket data as not ready via
M_NOTREADY similar to sendfile(2) when TLS framing information is
added to an unmapped mbuf in ktls_frame(). ktls_enqueue() is then
called to schedule TLS frames for encryption. In the case of
sendfile_iodone() calls ktls_enqueue() instead of pru_ready() leaving
the mbufs marked M_NOTREADY until encryption is completed. For other
writes (vn_sendfile when pages are available, write(2), etc.), the
PRUS_NOTREADY is set when invoking pru_send() along with invoking
ktls_enqueue().

A pool of worker threads (the "KTLS" kernel process) encrypts TLS
frames queued via ktls_enqueue(). Each TLS frame is temporarily
mapped using the direct map and passed to a software encryption
backend to perform the actual encryption.

(Note: The use of PHYS_TO_DMAP could be replaced with sf_bufs if
someone wished to make this work on architectures without a direct
map.)

KTLS supports pluggable software encryption backends. Internally,
Netflix uses proprietary pure-software backends. This commit includes
a simple backend in a new ktls_ocf.ko module that uses the kernel's
OpenCrypto framework to provide AES-GCM encryption of TLS frames. As
a result, software TLS is now a bit of a misnomer as it can make use
of hardware crypto accelerators.

Once software encryption has finished, the TLS frame mbufs are marked
ready via pru_ready(). At this point, the encrypted data appears as
regular payload to the TCP stack stored in unmapped mbufs.

ifnet TLS permits a NIC to offload the TLS encryption and TCP
segmentation. In this mode, a new send tag type (IF_SND_TAG_TYPE_TLS)
is allocated on the interface a socket is routed over and associated
with a TLS session. TLS records for a TLS session using ifnet TLS are
not marked M_NOTREADY but are passed down the stack unencrypted. The
ip_output_send() and ip6_output_send() helper functions that apply
send tags to outbound IP packets verify that the send tag of the TLS
record matches the outbound interface. If so, the packet is tagged
with the TLS send tag and sent to the interface. The NIC device
driver must recognize packets with the TLS send tag and schedule them
for TLS encryption and TCP segmentation. If the the outbound
interface does not match the interface in the TLS send tag, the packet
is dropped. In addition, a task is scheduled to refresh the TLS send
tag for the TLS session. If a new TLS send tag cannot be allocated,
the connection is dropped. If a new TLS send tag is allocated,
however, subsequent packets will be tagged with the correct TLS send
tag. (This latter case has been tested by configuring both ports of a
Chelsio T6 in a lagg and failing over from one port to another. As
the connections migrated to the new port, new TLS send tags were
allocated for the new port and connections resumed without being
dropped.)

ifnet TLS can be enabled and disabled on supported network interfaces
via new '[-]txtls[46]' options to ifconfig(8). ifnet TLS is supported
across both vlan devices and lagg interfaces using failover, lacp with
flowid enabled, or lacp with flowid enabled.

Applications may request the current KTLS mode of a connection via a
new TCP_TXTLS_MODE socket option. They can also use this socket
option to toggle between software and ifnet TLS modes.

In addition, a testing tool is available in tools/tools/switch_tls.
This is modeled on tcpdrop and uses similar syntax. However, instead
of dropping connections, -s is used to force KTLS connections to
switch to software TLS and -i is used to switch to ifnet TLS.

Various sysctls and counters are available under the kern.ipc.tls
sysctl node. The kern.ipc.tls.enable node must be set to true to
enable KTLS (it is off by default). The use of unmapped mbufs must
also be enabled via kern.ipc.mb_use_ext_pgs to enable KTLS.

KTLS is enabled via the KERN_TLS kernel option.

This patch is the culmination of years of work by several folks
including Scott Long and Randall Stewart for the original design and
implementation; Drew Gallatin for several optimizations including the
use of ext_pgs mbufs, the M_NOTREADY mechanism for TLS records
awaiting software encryption, and pluggable software crypto backends;
and John Baldwin for modifications to support hardware TLS offload.

Obtained from: Netflix
Sponsored by: Netflix

Test Plan
  • Netflix has run something pretty close to this in production for many years. For my part, I have tested this using additional ifnet TLS patches for Chelsio T6 as well as using cryptosoft, aesni(4) and ccr(4) with ktls_ocf. For the server process I used nginx patched to use sendfile with TLS along with a patched openssl. Work on getting those patches available is still ongoing but they will land eventually.

Diff Detail

Lint
Lint OK
Unit
No Unit Test Coverage
Build Status
Buildable 26120
Build 24639: arc lint + arc unit

Event Timeline

jhb created this revision.Aug 15 2019, 6:00 PM
jhb updated this revision to Diff 60847.Aug 15 2019, 6:02 PM
  • Spelling nit noticed by arc.
jhb updated this revision to Diff 60848.Aug 15 2019, 6:17 PM
  • Bump __FreeBSD_version.
emaste added a subscriber: emaste.Aug 15 2019, 7:04 PM
hselasky added inline comments.Aug 16 2019, 11:07 AM
sys/sys/ktls.h
88

Could you also define TLS_MINOR_VER_THREE ?

hselasky added inline comments.Aug 16 2019, 11:15 AM
sys/netinet/tcp_output.c
226
#ifdef KERN_TLS
const bool hw_tls = (so->so_snd.sb_flags & SB_TLS_IFNET) != 0;
#else
const bool hw_tls = false;
#endif
1012

Should you at ()'s here?

1012

s/at/add

sys/netinet/tcp_stacks/rack.c
6974

ditto const bool hw_tls = ...

??

markj added a subscriber: markj.Aug 16 2019, 2:05 PM
markj added inline comments.
sys/kern/uipc_ktls.c
1355

Why is this NODUMP? Are you trying to avoid dumping sensitive data, or is it just to avoid bloating minidumps with a large number of probably unusable pages?

Why do we allocate with VM_ALLOC_SYSTEM instead of VM_ALLOC_NORMAL?

1358

It looks like this careful handling of wire_count is meant to avoid updating wire_count once per page allocation. That might be a holdover from when wire_count was updated with atomics, so batching was important, but now it's a per-CPU counter, so I wonder if this is really needed.

glebius added inline comments.
sys/kern/uipc_ktls.c
1358

Indeed, that is leftover from when wire count was an atomic.

markj added inline comments.Aug 16 2019, 6:24 PM
sys/kern/uipc_ktls.c
1358

In that case I think you can just use VM_ALLOC_WIRED and not bother with any of the wire_adj dance.

gallatin added inline comments.Aug 16 2019, 6:59 PM
sys/kern/uipc_ktls.c
1355

Mostly to avoid bloating dumps. These are the destination buffers for crypto. Unless you're debugging the crypto itself, seeing encrypted data is not going to be helpful. In our case, we have enough encrypted data sitting on socket buffers that it would severely bloat the minidumps, to the point where we would likely be unable to take dumps.

VM_ALLOC_SYSTEM is an accident, I agree that it should be VM_ALLOC_NORMAL

1358

Yes, this is exactly it. When I first wrote this, I wanted to avoid repeatedly touching an atomic.

jhb added inline comments.Aug 16 2019, 6:59 PM
sys/netinet/tcp_output.c
1012

We don't typically add excessive ()'s. style(9) discourages it and neither clang nor gcc warn in this case. It is pretty intuitive for comparisons to have higher precedence than logical and/or.

sys/sys/ktls.h
88

That I think can wait until we support 1.3. Kernel modules will then be able to use that #ifdef to determine if the kernel itself supports 1.3. We will likely need to make some changes to accommodate 1.3 since the "real" record type isn't in the on-the-wire TLS header for example. As long as existing drivers and modules whitelist what versions they support this should be fine as-is.

jhb marked 6 inline comments as done.Aug 16 2019, 7:23 PM
jhb added inline comments.
sys/kern/uipc_ktls.c
1355

I'll change it to VM_ALLOC_NORMAL.

sys/netinet/tcp_output.c
226

I could do that. In theory style(9) doesn't like assignments in declarations though we do violate that rule a bit. FWIW, the compiler generates identical output either way.

jhb updated this revision to Diff 60904.Aug 16 2019, 7:25 PM
jhb marked an inline comment as done.
  • Use VM_ALLOC_NORMAL and VM_ALLOC_WIRED.
  • Use const bool for hw_tls.
jhb marked 3 inline comments as done.Aug 19 2019, 5:17 PM
hselasky accepted this revision.Aug 21 2019, 9:45 AM
rrs accepted this revision.Aug 21 2019, 10:56 AM
This revision is now accepted and ready to land.Aug 21 2019, 10:56 AM
jhb added a comment.Aug 23 2019, 12:46 AM

https://github.com/openssl/openssl/compare/OpenSSL_1_1_1-stable...bsdjhb:kern_tls_1_1_1 contains patches close to what has been used previously in production but updated against changes made while upstreaming. It is not against a FreeBSD source tree, but against openssl directly.

https://github.com/nginx/nginx/compare/branches/stable-1.14...bsdjhb:ktls-1.14 contains a set of patches against nginx-1.14 that is close to what has been used previously in production.

https://github.com/openssl/openssl/compare/master...bsdjhb:ktls_master is a set of not-yet-tested (but compiles!) patches against OpenSSL master.

I have tested with the first two patchsets at least, so barring objections I'll merge this tomorrow.

gallatin accepted this revision.Fri, Aug 23, 11:39 PM
jhb updated this revision to Diff 61326.Mon, Aug 26, 11:52 PM
  • Rebasing on today's head.
This revision now requires review to proceed.Mon, Aug 26, 11:52 PM
This revision is now accepted and ready to land.Tue, Aug 27, 12:39 AM
jhb closed this revision.Tue, Aug 27, 12:39 AM