Skip to content

Commit

Permalink
Add SetSockOpt definitions related to Linux Kernel TLS. (#2175)
Browse files Browse the repository at this point in the history
1. kTLS is implemented as a TCP ULP, so add a `SetSockOpt` impl for it.

2. kTLS parameters are pushed to the kernel using `setsockopt(SOL_TLS)`,
   so add `SetSockOpt` impls for them.

Other test fixes:

1. Change `setsockopt` tests to bind to port 0 so that
   they bind to an unbound port and don't affect each other.

2. Fix `skip!` macro used to skip tests to generate its own block so that
   it can be used as an expr.
  • Loading branch information
Arnavion authored Jan 3, 2024
1 parent a1aa343 commit 57c611c
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 8 deletions.
1 change: 1 addition & 0 deletions changelog/2175.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `SetSockOpt` impls to enable Linux Kernel TLS on a TCP socket and to import TLS parameters.
156 changes: 155 additions & 1 deletion src/sys/socket/sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ macro_rules! getsockopt_impl {
/// both of them.
/// * `$name:ident`: name of type `GetSockOpt`/`SetSockOpt` will be implemented for.
/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets*
/// (`lic::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),
/// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),
/// and more. Please refer to your system manual for more options. Will be passed as the second
/// argument (`level`) to the `getsockopt`/`setsockopt` call.
/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`,
Expand Down Expand Up @@ -1126,6 +1126,160 @@ where
}
}

/// Set the Upper Layer Protocol (ULP) on the TCP socket.
///
/// For example, to enable the TLS ULP on a socket, the C function call would be:
///
/// ```c
/// setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));
/// ```
///
/// ... and the `nix` equivalent is:
///
/// ```ignore,rust
/// setsockopt(sock, TcpUlp::default(), b"tls");
/// ```
///
/// Note that the ULP name does not need a trailing NUL terminator (`\0`).
#[cfg(linux_android)]
#[derive(Clone, Debug)]
pub struct TcpUlp<T>(::std::marker::PhantomData<T>);

#[cfg(linux_android)]
impl<T> Default for TcpUlp<T> {
fn default() -> Self {
TcpUlp(Default::default())
}
}

#[cfg(linux_android)]
impl<T> SetSockOpt for TcpUlp<T>
where
T: AsRef<[u8]> + Clone,
{
type Val = T;

fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
unsafe {
let res = libc::setsockopt(
fd.as_fd().as_raw_fd(),
libc::SOL_TCP,
libc::TCP_ULP,
val.as_ref().as_ptr().cast(),
val.as_ref().len() as libc::socklen_t,
);
Errno::result(res).map(drop)
}
}
}

/// Value used with the [`TcpTlsTx`] and [`TcpTlsRx`] socket options.
#[cfg(target_os = "linux")]
#[derive(Copy, Clone, Debug)]
pub enum TlsCryptoInfo {
/// AES-128-GCM
Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128),

/// AES-256-GCM
Aes256Gcm(libc::tls12_crypto_info_aes_gcm_256),

/// CHACHA20-POLY1305
Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305),
}

/// Set the Kernel TLS write parameters on the TCP socket.
///
/// For example, the C function call would be:
///
/// ```c
/// setsockopt(sock, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info));
/// ```
///
/// ... and the `nix` equivalent is:
///
/// ```ignore,rust
/// setsockopt(sock, TcpTlsTx, &crypto_info);
/// ```
#[cfg(target_os = "linux")]
#[derive(Copy, Clone, Debug)]
pub struct TcpTlsTx;

#[cfg(target_os = "linux")]
impl SetSockOpt for TcpTlsTx {
type Val = TlsCryptoInfo;

fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
let (ffi_ptr, ffi_len) = match val {
TlsCryptoInfo::Aes128Gcm(crypto_info) => {
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
}
TlsCryptoInfo::Aes256Gcm(crypto_info) => {
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
}
TlsCryptoInfo::Chacha20Poly1305(crypto_info) => {
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
}
};
unsafe {
let res = libc::setsockopt(
fd.as_fd().as_raw_fd(),
libc::SOL_TLS,
libc::TLS_TX,
ffi_ptr,
ffi_len as libc::socklen_t,
);
Errno::result(res).map(drop)
}
}
}

/// Set the Kernel TLS read parameters on the TCP socket.
///
/// For example, the C function call would be:
///
/// ```c
/// setsockopt(sock, SOL_TLS, TLS_RX, &crypto_info, sizeof(crypto_info));
/// ```
///
/// ... and the `nix` equivalent is:
///
/// ```ignore,rust
/// setsockopt(sock, TcpTlsRx, &crypto_info);
/// ```
#[cfg(target_os = "linux")]
#[derive(Copy, Clone, Debug)]
pub struct TcpTlsRx;

#[cfg(target_os = "linux")]
impl SetSockOpt for TcpTlsRx {
type Val = TlsCryptoInfo;

fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
let (ffi_ptr, ffi_len) = match val {
TlsCryptoInfo::Aes128Gcm(crypto_info) => {
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
}
TlsCryptoInfo::Aes256Gcm(crypto_info) => {
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
}
TlsCryptoInfo::Chacha20Poly1305(crypto_info) => {
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
}
};
unsafe {
let res = libc::setsockopt(
fd.as_fd().as_raw_fd(),
libc::SOL_TLS,
libc::TLS_RX,
ffi_ptr,
ffi_len as libc::socklen_t,
);
Errno::result(res).map(drop)
}
}
}


/*
*
* ===== Accessor helpers =====
Expand Down
4 changes: 2 additions & 2 deletions test/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ use cfg_if::cfg_if;

#[macro_export]
macro_rules! skip {
($($reason: expr),+) => {
($($reason: expr),+) => {{
use ::std::io::{self, Write};

let stderr = io::stderr();
let mut handle = stderr.lock();
writeln!(handle, $($reason),+).unwrap();
return;
}
}}
}

cfg_if! {
Expand Down
103 changes: 98 additions & 5 deletions test/sys/test_sockopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ fn test_so_listen_q_limit() {
#[test]
fn test_so_tcp_maxseg() {
use nix::sys::socket::{
accept, bind, connect, listen, Backlog, SockaddrIn,
accept, bind, connect, getsockname, listen, Backlog, SockaddrIn,
};
use nix::unistd::write;
use std::net::SocketAddrV4;
use std::str::FromStr;

let std_sa = SocketAddrV4::from_str("127.0.0.1:4003").unwrap();
let sock_addr = SockaddrIn::from(std_sa);
let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
let mut sock_addr = SockaddrIn::from(std_sa);

let rsock = socket(
AddressFamily::Inet,
Expand All @@ -148,6 +148,7 @@ fn test_so_tcp_maxseg() {
)
.unwrap();
bind(rsock.as_raw_fd(), &sock_addr).unwrap();
sock_addr = getsockname(rsock.as_raw_fd()).unwrap();
listen(&rsock, Backlog::new(10).unwrap()).unwrap();
let initial = getsockopt(&rsock, sockopt::TcpMaxSeg).unwrap();
// Initial MSS is expected to be 536 (https://tools.ietf.org/html/rfc879#section-1) but some
Expand Down Expand Up @@ -305,7 +306,7 @@ fn test_get_mtu() {
use std::net::SocketAddrV4;
use std::str::FromStr;

let std_sa = SocketAddrV4::from_str("127.0.0.1:4001").unwrap();
let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
let std_sb = SocketAddrV4::from_str("127.0.0.1:4002").unwrap();

let usock = socket(
Expand Down Expand Up @@ -615,7 +616,7 @@ fn test_ts_clock_monotonic() {

#[test]
#[cfg(linux_android)]
// Disable the test under emulation because it failsi with ENOPROTOOPT in CI
// Disable the test under emulation because it fails with ENOPROTOOPT in CI
// on cross target. Lack of QEMU support is suspected.
#[cfg_attr(qemu, ignore)]
fn test_ip_bind_address_no_port() {
Expand Down Expand Up @@ -735,3 +736,95 @@ fn can_get_listen_on_tcp_socket() {
let s_listening2 = getsockopt(&s, sockopt::AcceptConn).unwrap();
assert!(s_listening2);
}

#[cfg(target_os = "linux")]
// Some architectures running under cross don't support `setsockopt(SOL_TCP, TCP_ULP)`
// because the cross image is based on Ubuntu 16.04 which predates TCP ULP support
// (it was added in kernel v4.13 released in 2017). For these architectures,
// the `setsockopt(SOL_TCP, TCP_ULP, "tls", sizeof("tls"))` call succeeds
// but the subsequent `setsockopt(SOL_TLS, TLS_TX, ...)` call fails with `ENOPROTOOPT`.
// It's as if the first `setsockopt` call enabled some other option, not `TCP_ULP`.
// For example, `strace` says:
//
// [pid 813] setsockopt(4, SOL_TCP, 0x1f /* TCP_??? */, [7564404], 4) = 0
//
// It's not clear why `setsockopt(SOL_TCP, TCP_ULP)` succeeds if the container image libc doesn't support it,
// but in any case we can't run the test on such an architecture, so skip it.
#[cfg_attr(qemu, ignore)]
#[test]
fn test_ktls() {
use nix::sys::socket::{
accept, bind, connect, getsockname, listen, Backlog, SockaddrIn,
};
use std::net::SocketAddrV4;
use std::str::FromStr;

let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
let mut sock_addr = SockaddrIn::from(std_sa);

let rsock = socket(
AddressFamily::Inet,
SockType::Stream,
SockFlag::empty(),
SockProtocol::Tcp,
)
.unwrap();
bind(rsock.as_raw_fd(), &sock_addr).unwrap();
sock_addr = getsockname(rsock.as_raw_fd()).unwrap();
listen(&rsock, Backlog::new(10).unwrap()).unwrap();

let ssock = socket(
AddressFamily::Inet,
SockType::Stream,
SockFlag::empty(),
SockProtocol::Tcp,
)
.unwrap();
connect(ssock.as_raw_fd(), &sock_addr).unwrap();

let _rsess = accept(rsock.as_raw_fd()).unwrap();

match setsockopt(&ssock, sockopt::TcpUlp::default(), b"tls") {
Ok(()) => (),

// TLS ULP is not enabled, so we can't test kTLS.
Err(nix::Error::ENOENT) => skip!("TLS ULP is not enabled"),

Err(err) => panic!("{err:?}"),
}

// In real life we would do a TLS handshake and extract the protocol version and secrets.
// For this test we just make some up.

let tx = sockopt::TlsCryptoInfo::Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128 {
info: libc::tls_crypto_info {
version: libc::TLS_1_2_VERSION,
cipher_type: libc::TLS_CIPHER_AES_GCM_128,
},
iv: *b"\x04\x05\x06\x07\x08\x09\x0a\x0b",
key: *b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
salt: *b"\x00\x01\x02\x03",
rec_seq: *b"\x00\x00\x00\x00\x00\x00\x00\x00",
});
setsockopt(&ssock, sockopt::TcpTlsTx, &tx)
.expect("setting TLS_TX after enabling TLS ULP should succeed");

let rx = sockopt::TlsCryptoInfo::Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128 {
info: libc::tls_crypto_info {
version: libc::TLS_1_2_VERSION,
cipher_type: libc::TLS_CIPHER_AES_GCM_128,
},
iv: *b"\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb",
key: *b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
salt: *b"\xf0\xf1\xf2\xf3",
rec_seq: *b"\x00\x00\x00\x00\x00\x00\x00\x00",
});
match setsockopt(&ssock, sockopt::TcpTlsRx, &rx) {
Ok(()) => (),
Err(nix::Error::ENOPROTOOPT) => {
// TLS_TX was added in v4.13 and TLS_RX in v4.17, so we appear to be between that range.
// It's good enough that TLS_TX worked, so let the test succeed.
}
Err(err) => panic!("{err:?}"),
}
}

0 comments on commit 57c611c

Please sign in to comment.