Skip to content

Commit 57c611c

Browse files
authored
Add SetSockOpt definitions related to Linux Kernel TLS. (#2175)
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.
1 parent a1aa343 commit 57c611c

File tree

4 files changed

+256
-8
lines changed

4 files changed

+256
-8
lines changed

changelog/2175.added.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added `SetSockOpt` impls to enable Linux Kernel TLS on a TCP socket and to import TLS parameters.

src/sys/socket/sockopt.rs

+155-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ macro_rules! getsockopt_impl {
127127
/// both of them.
128128
/// * `$name:ident`: name of type `GetSockOpt`/`SetSockOpt` will be implemented for.
129129
/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets*
130-
/// (`lic::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),
130+
/// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),
131131
/// and more. Please refer to your system manual for more options. Will be passed as the second
132132
/// argument (`level`) to the `getsockopt`/`setsockopt` call.
133133
/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`,
@@ -1126,6 +1126,160 @@ where
11261126
}
11271127
}
11281128

1129+
/// Set the Upper Layer Protocol (ULP) on the TCP socket.
1130+
///
1131+
/// For example, to enable the TLS ULP on a socket, the C function call would be:
1132+
///
1133+
/// ```c
1134+
/// setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));
1135+
/// ```
1136+
///
1137+
/// ... and the `nix` equivalent is:
1138+
///
1139+
/// ```ignore,rust
1140+
/// setsockopt(sock, TcpUlp::default(), b"tls");
1141+
/// ```
1142+
///
1143+
/// Note that the ULP name does not need a trailing NUL terminator (`\0`).
1144+
#[cfg(linux_android)]
1145+
#[derive(Clone, Debug)]
1146+
pub struct TcpUlp<T>(::std::marker::PhantomData<T>);
1147+
1148+
#[cfg(linux_android)]
1149+
impl<T> Default for TcpUlp<T> {
1150+
fn default() -> Self {
1151+
TcpUlp(Default::default())
1152+
}
1153+
}
1154+
1155+
#[cfg(linux_android)]
1156+
impl<T> SetSockOpt for TcpUlp<T>
1157+
where
1158+
T: AsRef<[u8]> + Clone,
1159+
{
1160+
type Val = T;
1161+
1162+
fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
1163+
unsafe {
1164+
let res = libc::setsockopt(
1165+
fd.as_fd().as_raw_fd(),
1166+
libc::SOL_TCP,
1167+
libc::TCP_ULP,
1168+
val.as_ref().as_ptr().cast(),
1169+
val.as_ref().len() as libc::socklen_t,
1170+
);
1171+
Errno::result(res).map(drop)
1172+
}
1173+
}
1174+
}
1175+
1176+
/// Value used with the [`TcpTlsTx`] and [`TcpTlsRx`] socket options.
1177+
#[cfg(target_os = "linux")]
1178+
#[derive(Copy, Clone, Debug)]
1179+
pub enum TlsCryptoInfo {
1180+
/// AES-128-GCM
1181+
Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128),
1182+
1183+
/// AES-256-GCM
1184+
Aes256Gcm(libc::tls12_crypto_info_aes_gcm_256),
1185+
1186+
/// CHACHA20-POLY1305
1187+
Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305),
1188+
}
1189+
1190+
/// Set the Kernel TLS write parameters on the TCP socket.
1191+
///
1192+
/// For example, the C function call would be:
1193+
///
1194+
/// ```c
1195+
/// setsockopt(sock, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info));
1196+
/// ```
1197+
///
1198+
/// ... and the `nix` equivalent is:
1199+
///
1200+
/// ```ignore,rust
1201+
/// setsockopt(sock, TcpTlsTx, &crypto_info);
1202+
/// ```
1203+
#[cfg(target_os = "linux")]
1204+
#[derive(Copy, Clone, Debug)]
1205+
pub struct TcpTlsTx;
1206+
1207+
#[cfg(target_os = "linux")]
1208+
impl SetSockOpt for TcpTlsTx {
1209+
type Val = TlsCryptoInfo;
1210+
1211+
fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
1212+
let (ffi_ptr, ffi_len) = match val {
1213+
TlsCryptoInfo::Aes128Gcm(crypto_info) => {
1214+
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
1215+
}
1216+
TlsCryptoInfo::Aes256Gcm(crypto_info) => {
1217+
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
1218+
}
1219+
TlsCryptoInfo::Chacha20Poly1305(crypto_info) => {
1220+
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
1221+
}
1222+
};
1223+
unsafe {
1224+
let res = libc::setsockopt(
1225+
fd.as_fd().as_raw_fd(),
1226+
libc::SOL_TLS,
1227+
libc::TLS_TX,
1228+
ffi_ptr,
1229+
ffi_len as libc::socklen_t,
1230+
);
1231+
Errno::result(res).map(drop)
1232+
}
1233+
}
1234+
}
1235+
1236+
/// Set the Kernel TLS read parameters on the TCP socket.
1237+
///
1238+
/// For example, the C function call would be:
1239+
///
1240+
/// ```c
1241+
/// setsockopt(sock, SOL_TLS, TLS_RX, &crypto_info, sizeof(crypto_info));
1242+
/// ```
1243+
///
1244+
/// ... and the `nix` equivalent is:
1245+
///
1246+
/// ```ignore,rust
1247+
/// setsockopt(sock, TcpTlsRx, &crypto_info);
1248+
/// ```
1249+
#[cfg(target_os = "linux")]
1250+
#[derive(Copy, Clone, Debug)]
1251+
pub struct TcpTlsRx;
1252+
1253+
#[cfg(target_os = "linux")]
1254+
impl SetSockOpt for TcpTlsRx {
1255+
type Val = TlsCryptoInfo;
1256+
1257+
fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
1258+
let (ffi_ptr, ffi_len) = match val {
1259+
TlsCryptoInfo::Aes128Gcm(crypto_info) => {
1260+
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
1261+
}
1262+
TlsCryptoInfo::Aes256Gcm(crypto_info) => {
1263+
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
1264+
}
1265+
TlsCryptoInfo::Chacha20Poly1305(crypto_info) => {
1266+
(<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
1267+
}
1268+
};
1269+
unsafe {
1270+
let res = libc::setsockopt(
1271+
fd.as_fd().as_raw_fd(),
1272+
libc::SOL_TLS,
1273+
libc::TLS_RX,
1274+
ffi_ptr,
1275+
ffi_len as libc::socklen_t,
1276+
);
1277+
Errno::result(res).map(drop)
1278+
}
1279+
}
1280+
}
1281+
1282+
11291283
/*
11301284
*
11311285
* ===== Accessor helpers =====

test/common/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ use cfg_if::cfg_if;
22

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

88
let stderr = io::stderr();
99
let mut handle = stderr.lock();
1010
writeln!(handle, $($reason),+).unwrap();
1111
return;
12-
}
12+
}}
1313
}
1414

1515
cfg_if! {

test/sys/test_sockopt.rs

+98-5
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,14 @@ fn test_so_listen_q_limit() {
131131
#[test]
132132
fn test_so_tcp_maxseg() {
133133
use nix::sys::socket::{
134-
accept, bind, connect, listen, Backlog, SockaddrIn,
134+
accept, bind, connect, getsockname, listen, Backlog, SockaddrIn,
135135
};
136136
use nix::unistd::write;
137137
use std::net::SocketAddrV4;
138138
use std::str::FromStr;
139139

140-
let std_sa = SocketAddrV4::from_str("127.0.0.1:4003").unwrap();
141-
let sock_addr = SockaddrIn::from(std_sa);
140+
let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
141+
let mut sock_addr = SockaddrIn::from(std_sa);
142142

143143
let rsock = socket(
144144
AddressFamily::Inet,
@@ -148,6 +148,7 @@ fn test_so_tcp_maxseg() {
148148
)
149149
.unwrap();
150150
bind(rsock.as_raw_fd(), &sock_addr).unwrap();
151+
sock_addr = getsockname(rsock.as_raw_fd()).unwrap();
151152
listen(&rsock, Backlog::new(10).unwrap()).unwrap();
152153
let initial = getsockopt(&rsock, sockopt::TcpMaxSeg).unwrap();
153154
// Initial MSS is expected to be 536 (https://tools.ietf.org/html/rfc879#section-1) but some
@@ -305,7 +306,7 @@ fn test_get_mtu() {
305306
use std::net::SocketAddrV4;
306307
use std::str::FromStr;
307308

308-
let std_sa = SocketAddrV4::from_str("127.0.0.1:4001").unwrap();
309+
let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
309310
let std_sb = SocketAddrV4::from_str("127.0.0.1:4002").unwrap();
310311

311312
let usock = socket(
@@ -615,7 +616,7 @@ fn test_ts_clock_monotonic() {
615616

616617
#[test]
617618
#[cfg(linux_android)]
618-
// Disable the test under emulation because it failsi with ENOPROTOOPT in CI
619+
// Disable the test under emulation because it fails with ENOPROTOOPT in CI
619620
// on cross target. Lack of QEMU support is suspected.
620621
#[cfg_attr(qemu, ignore)]
621622
fn test_ip_bind_address_no_port() {
@@ -735,3 +736,95 @@ fn can_get_listen_on_tcp_socket() {
735736
let s_listening2 = getsockopt(&s, sockopt::AcceptConn).unwrap();
736737
assert!(s_listening2);
737738
}
739+
740+
#[cfg(target_os = "linux")]
741+
// Some architectures running under cross don't support `setsockopt(SOL_TCP, TCP_ULP)`
742+
// because the cross image is based on Ubuntu 16.04 which predates TCP ULP support
743+
// (it was added in kernel v4.13 released in 2017). For these architectures,
744+
// the `setsockopt(SOL_TCP, TCP_ULP, "tls", sizeof("tls"))` call succeeds
745+
// but the subsequent `setsockopt(SOL_TLS, TLS_TX, ...)` call fails with `ENOPROTOOPT`.
746+
// It's as if the first `setsockopt` call enabled some other option, not `TCP_ULP`.
747+
// For example, `strace` says:
748+
//
749+
// [pid 813] setsockopt(4, SOL_TCP, 0x1f /* TCP_??? */, [7564404], 4) = 0
750+
//
751+
// It's not clear why `setsockopt(SOL_TCP, TCP_ULP)` succeeds if the container image libc doesn't support it,
752+
// but in any case we can't run the test on such an architecture, so skip it.
753+
#[cfg_attr(qemu, ignore)]
754+
#[test]
755+
fn test_ktls() {
756+
use nix::sys::socket::{
757+
accept, bind, connect, getsockname, listen, Backlog, SockaddrIn,
758+
};
759+
use std::net::SocketAddrV4;
760+
use std::str::FromStr;
761+
762+
let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap();
763+
let mut sock_addr = SockaddrIn::from(std_sa);
764+
765+
let rsock = socket(
766+
AddressFamily::Inet,
767+
SockType::Stream,
768+
SockFlag::empty(),
769+
SockProtocol::Tcp,
770+
)
771+
.unwrap();
772+
bind(rsock.as_raw_fd(), &sock_addr).unwrap();
773+
sock_addr = getsockname(rsock.as_raw_fd()).unwrap();
774+
listen(&rsock, Backlog::new(10).unwrap()).unwrap();
775+
776+
let ssock = socket(
777+
AddressFamily::Inet,
778+
SockType::Stream,
779+
SockFlag::empty(),
780+
SockProtocol::Tcp,
781+
)
782+
.unwrap();
783+
connect(ssock.as_raw_fd(), &sock_addr).unwrap();
784+
785+
let _rsess = accept(rsock.as_raw_fd()).unwrap();
786+
787+
match setsockopt(&ssock, sockopt::TcpUlp::default(), b"tls") {
788+
Ok(()) => (),
789+
790+
// TLS ULP is not enabled, so we can't test kTLS.
791+
Err(nix::Error::ENOENT) => skip!("TLS ULP is not enabled"),
792+
793+
Err(err) => panic!("{err:?}"),
794+
}
795+
796+
// In real life we would do a TLS handshake and extract the protocol version and secrets.
797+
// For this test we just make some up.
798+
799+
let tx = sockopt::TlsCryptoInfo::Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128 {
800+
info: libc::tls_crypto_info {
801+
version: libc::TLS_1_2_VERSION,
802+
cipher_type: libc::TLS_CIPHER_AES_GCM_128,
803+
},
804+
iv: *b"\x04\x05\x06\x07\x08\x09\x0a\x0b",
805+
key: *b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
806+
salt: *b"\x00\x01\x02\x03",
807+
rec_seq: *b"\x00\x00\x00\x00\x00\x00\x00\x00",
808+
});
809+
setsockopt(&ssock, sockopt::TcpTlsTx, &tx)
810+
.expect("setting TLS_TX after enabling TLS ULP should succeed");
811+
812+
let rx = sockopt::TlsCryptoInfo::Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128 {
813+
info: libc::tls_crypto_info {
814+
version: libc::TLS_1_2_VERSION,
815+
cipher_type: libc::TLS_CIPHER_AES_GCM_128,
816+
},
817+
iv: *b"\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb",
818+
key: *b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
819+
salt: *b"\xf0\xf1\xf2\xf3",
820+
rec_seq: *b"\x00\x00\x00\x00\x00\x00\x00\x00",
821+
});
822+
match setsockopt(&ssock, sockopt::TcpTlsRx, &rx) {
823+
Ok(()) => (),
824+
Err(nix::Error::ENOPROTOOPT) => {
825+
// TLS_TX was added in v4.13 and TLS_RX in v4.17, so we appear to be between that range.
826+
// It's good enough that TLS_TX worked, so let the test succeed.
827+
}
828+
Err(err) => panic!("{err:?}"),
829+
}
830+
}

0 commit comments

Comments
 (0)