Skip to content

Commit 1217d06

Browse files
committed
Add support for TLS sockets
This adds the module std.net.tls and refactors std.net.socket in various places, such that we can provide support for TLS 1.2 and TLS 1.3. The TLS stack is backed by Rustls (https://github.com/rustls/rustls). My original plan was to write the stack in Inko, but I deemed this far too time consuming and not beneficial for users (compared to using an existing mature stack). I also experimented with OpenSSL, but using OpenSSL is like walking through a minefield, and its API is a pain to use (in part due to its use of global and thread-local state). Rustls is compiled such that it uses the "ring" backend instead of aws-lc. This is done because aws-lc requires additional dependencies on FreeBSD, and increases compile times significantly (about 30 seconds or so). While performance of TLS 1.3 is less ideal when using ring compared to using aws-lc (rustls/rustls#1751), it should still be good enough (and still be much faster compared to using OpenSSL). A downside of using Rustls is that the executable sizes increase by about 6 MiB (or 2 MiB when stripping them), due to the extra code introduced by Rustls and its dependencies. Sadly we can't avoid this unless we use OpenSSL, which introduces far more pressing issues. For certificate validation we use a patched version of the rustls-platform-verifier crate. The patched version strips the code we don't need (mostly so we don't get tons of "this code is unused" warnings and what not), and patches the macOS code to account for the system verification process being (potentially) slow by using the `Process::blocking` method. This fixes #329. Changelog: added
1 parent 42db383 commit 1217d06

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3569
-795
lines changed

Cargo.lock

+215-94
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/src/linker.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,13 @@ pub(crate) fn link(
256256
cmd.arg("-lm");
257257
cmd.arg("-lpthread");
258258
}
259-
_ => {}
259+
OperatingSystem::Mac => {
260+
// This is needed for TLS support.
261+
for name in ["Security", "CoreFoundation"] {
262+
cmd.arg("-framework");
263+
cmd.arg(name);
264+
}
265+
}
260266
}
261267

262268
let mut static_linking = state.config.static_linking;

rt/Cargo.toml

+29
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,35 @@ unicode-segmentation = "^1.10"
2323
backtrace = "^0.3"
2424
rustix = { version = "^0.38", features = ["fs", "mm", "param", "process", "net", "std", "time", "event"], default-features = false }
2525

26+
# The dependencies needed for TLS support.
27+
#
28+
# We use ring instead of the default aws-lc-sys because:
29+
#
30+
# 1. aws-lc-sys requires cmake to be installed when building on FreeBSD (and
31+
# potentially other platforms), as aws-lc-sys only provides generated
32+
# bindings for a limited set of platforms
33+
# 2. aws-lc-sys increases compile times quite a bit
34+
# 3. We don't care about FIPS compliance at the time of writing
35+
rustls = { version = "^0.23", features = ["ring", "tls12", "std"], default-features = false }
36+
rustls-pemfile = "^2.1"
37+
38+
# These dependencies are used by the customized version of
39+
# rustls-platform-modifier. We include a custom version so we can deal with the
40+
# platform verification process being potentially slow. See
41+
# https://github.com/rustls/rustls/issues/850 and
42+
# https://github.com/inko-lang/inko/issues/329 for more details.
43+
once_cell = "1.9"
44+
45+
[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "tvos")))'.dependencies]
46+
rustls-native-certs = "0.7"
47+
webpki = { package = "rustls-webpki", version = "0.102", default-features = false }
48+
49+
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))'.dependencies]
50+
core-foundation = "0.9"
51+
core-foundation-sys = "0.8"
52+
security-framework = { version = "2.10", features = ["OSX_10_14"] }
53+
security-framework-sys = { version = "2.10", features = ["OSX_10_14"] }
54+
2655
[dependencies.socket2]
2756
version = "^0.5"
2857
features = ["all"]

rt/src/lib.rs

+15-14
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@
33
#![allow(clippy::missing_safety_doc)]
44
#![allow(clippy::too_many_arguments)]
55

6-
pub mod macros;
6+
mod macros;
77

8-
pub mod arc_without_weak;
9-
pub mod config;
10-
pub mod context;
11-
pub mod mem;
12-
pub mod memory_map;
13-
pub mod network_poller;
14-
pub mod process;
15-
pub mod result;
16-
pub mod runtime;
17-
pub mod scheduler;
18-
pub mod socket;
19-
pub mod stack;
20-
pub mod state;
8+
mod arc_without_weak;
9+
mod config;
10+
mod context;
11+
mod mem;
12+
mod memory_map;
13+
mod network_poller;
14+
mod process;
15+
mod result;
16+
mod runtime;
17+
mod rustls_platform_verifier;
18+
mod scheduler;
19+
mod socket;
20+
mod stack;
21+
mod state;
2122

2223
#[cfg(test)]
2324
pub mod test;

rt/src/network_poller.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const CAPACITY: usize = 1024;
2525
pub(crate) type NetworkPoller = sys::Poller;
2626

2727
/// The type of event a poller should wait for.
28+
#[derive(Debug)]
2829
pub(crate) enum Interest {
2930
Read,
3031
Write,

rt/src/network_poller/kqueue.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,20 @@ impl Poller {
4040
source: impl AsFd,
4141
interest: Interest,
4242
) {
43-
let fd = source.as_fd().as_raw_fd();
44-
let (add, del) = match interest {
45-
Interest::Read => (EventFilter::Read(fd), EventFilter::Write(fd)),
46-
Interest::Write => (EventFilter::Write(fd), EventFilter::Read(fd)),
47-
};
4843
let id = process.identifier() as isize;
44+
let fd = source.as_fd().as_raw_fd();
4945
let flags =
5046
EventFlags::CLEAR | EventFlags::ONESHOT | EventFlags::RECEIPT;
51-
let events = [
52-
Event::new(add, EventFlags::ADD | flags, id),
53-
Event::new(del, EventFlags::DELETE, 0),
54-
];
47+
let events = match interest {
48+
Interest::Read => [
49+
Event::new(EventFilter::Read(fd), EventFlags::ADD | flags, id),
50+
Event::new(EventFilter::Write(fd), EventFlags::DELETE, 0),
51+
],
52+
Interest::Write => [
53+
Event::new(EventFilter::Write(fd), EventFlags::ADD | flags, id),
54+
Event::new(EventFilter::Read(fd), EventFlags::DELETE, 0),
55+
],
56+
};
5557

5658
self.apply(&events);
5759
}

rt/src/process.rs

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::scheduler::process::Thread;
44
use crate::scheduler::timeouts::Timeout;
55
use crate::stack::Stack;
66
use crate::state::State;
7-
use backtrace;
87
use std::alloc::{alloc, dealloc, handle_alloc_error, Layout};
98
use std::cell::UnsafeCell;
109
use std::collections::VecDeque;

rt/src/result.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ pub(crate) fn error_to_int(error: io::Error) -> i64 {
1313
// raw_os_error() above returns a None.
1414
Errno::TIMEDOUT.raw_os_error()
1515
} else {
16-
-1
16+
match error.kind() {
17+
io::ErrorKind::InvalidData => -2,
18+
io::ErrorKind::UnexpectedEof => -3,
19+
_ => -1,
20+
}
1721
};
1822

1923
code as i64
@@ -60,7 +64,7 @@ impl Result {
6064
}
6165

6266
pub(crate) fn io_error(error: io::Error) -> Result {
63-
Self::error({ error_to_int(error) } as _)
67+
Self::error(error_to_int(error) as _)
6468
}
6569
}
6670

rt/src/runtime.rs

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod stdio;
1414
mod string;
1515
mod sys;
1616
mod time;
17+
mod tls;
1718

1819
use crate::config::Config;
1920
use crate::mem::ClassPointer;
@@ -67,6 +68,12 @@ pub unsafe extern "system" fn inko_runtime_new(
6768
// does for us when compiling an executable.
6869
signal_sched::block_all();
6970

71+
// Configure the TLS provider. This must be done once before we start the
72+
// program.
73+
rustls::crypto::ring::default_provider()
74+
.install_default()
75+
.expect("failed to set up the default TLS cryptography provider");
76+
7077
Box::into_raw(Box::new(Runtime::new(&*counts, args)))
7178
}
7279

rt/src/runtime/env.rs

-20
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,6 @@ pub unsafe extern "system" fn inko_env_size(state: *const State) -> i64 {
4040
(*state).environment.len() as _
4141
}
4242

43-
#[no_mangle]
44-
pub unsafe extern "system" fn inko_env_home_directory(
45-
state: *const State,
46-
) -> InkoResult {
47-
let state = &*state;
48-
49-
// Rather than performing all sorts of magical incantations to get the home
50-
// directory, we're just going to require that HOME is set.
51-
//
52-
// If the home is explicitly set to an empty string we still ignore it,
53-
// because there's no scenario in which Some("") is useful.
54-
state
55-
.environment
56-
.get("HOME")
57-
.filter(|&path| !path.is_empty())
58-
.cloned()
59-
.map(|v| InkoResult::ok(InkoString::alloc(state.string_class, v) as _))
60-
.unwrap_or_else(InkoResult::none)
61-
}
62-
6343
#[no_mangle]
6444
pub unsafe extern "system" fn inko_env_temp_directory(
6545
state: *const State,

rt/src/runtime/helpers.rs

+51
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
use crate::context;
2+
use crate::network_poller::Interest;
3+
use crate::process::ProcessPointer;
4+
use crate::scheduler::timeouts::Timeout;
5+
use crate::socket::Socket;
6+
use crate::state::State;
17
use std::io::{self, Read};
28

39
/// Reads a number of bytes from a buffer into a Vec.
@@ -14,3 +20,48 @@ pub(crate) fn read_into<T: Read>(
1420

1521
Ok(read as i64)
1622
}
23+
24+
pub(crate) fn poll(
25+
state: &State,
26+
mut process: ProcessPointer,
27+
socket: &mut Socket,
28+
interest: Interest,
29+
deadline: i64,
30+
) -> io::Result<()> {
31+
let poll_id = unsafe { process.thread() }.network_poller;
32+
33+
// We must keep the process' state lock open until everything is registered,
34+
// otherwise a timeout thread may reschedule the process (i.e. the timeout
35+
// is very short) before we finish registering the socket with a poller.
36+
{
37+
let mut proc_state = process.state();
38+
39+
// A deadline of -1 signals that we should wait indefinitely.
40+
if deadline >= 0 {
41+
let time = Timeout::until(deadline as u64);
42+
43+
proc_state.waiting_for_io(Some(time.clone()));
44+
state.timeout_worker.suspend(process, time);
45+
} else {
46+
proc_state.waiting_for_io(None);
47+
}
48+
49+
socket.register(state, process, poll_id, interest);
50+
}
51+
52+
// Safety: the current thread is holding on to the process' run lock, so if
53+
// the process gets rescheduled onto a different thread, said thread won't
54+
// be able to use it until we finish this context switch.
55+
unsafe { context::switch(process) };
56+
57+
if process.timeout_expired() {
58+
// The socket is still registered at this point, so we have to
59+
// deregister first. If we don't and suspend for another IO operation,
60+
// the poller could end up rescheduling the process multiple times (as
61+
// there are multiple events still in flight for the process).
62+
socket.deregister(state);
63+
return Err(io::Error::from(io::ErrorKind::TimedOut));
64+
}
65+
66+
Ok(())
67+
}

0 commit comments

Comments
 (0)