Skip to content

Commit 638384a

Browse files
committed
WIP: TLS sockets
This commit also changes std.net.socket.SocketAddress such that it no longer stores IP address as a String, instead storing them as a proper IpAddress instance. In addition, SocketAddress.new is removed in favour of creating an instance of the class directly, as all fields are (and will remain) public such that a dedicated "new" method is redundant. This fixes #329. Changelog: added
1 parent 131c0e1 commit 638384a

23 files changed

+887
-283
lines changed

Cargo.lock

+328-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

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ 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-platform-verifier = "^0.3"
37+
rustls-pemfile = "^2.1"
38+
2639
[dependencies.socket2]
2740
version = "^0.5"
2841
features = ["all"]

rt/src/network_poller.rs

+11
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ pub(crate) type NetworkPoller = sys::Poller;
2828
pub(crate) enum Interest {
2929
Read,
3030
Write,
31+
ReadWrite,
32+
}
33+
34+
impl Interest {
35+
pub(crate) fn new(read: bool, write: bool) -> Interest {
36+
match (read, write) {
37+
(true, true) => Interest::ReadWrite,
38+
(false, true) => Interest::Write,
39+
_ => Interest::Read,
40+
}
41+
}
3142
}
3243

3344
/// A thread that polls a poller and reschedules processes.

rt/src/network_poller/epoll.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ fn flags_for(interest: Interest) -> EventFlags {
1111
let flags = match interest {
1212
Interest::Read => EventFlags::IN,
1313
Interest::Write => EventFlags::OUT,
14+
Interest::ReadWrite => EventFlags::IN | EventFlags::OUT,
1415
};
1516

1617
flags | EventFlags::ET | EventFlags::ONESHOT

rt/src/network_poller/kqueue.rs

+15-9
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,24 @@ 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+
Interest::ReadWrite => [
57+
Event::new(EventFilter::Write(fd), EventFlags::ADD | flags, id),
58+
Event::new(EventFilter::Read(fd), EventFlags::ADD | flags, id),
59+
],
60+
};
5561

5662
self.apply(&events);
5763
}

rt/src/result.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ 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+
_ => -1,
19+
}
1720
};
1821

1922
code as i64
@@ -60,7 +63,7 @@ impl Result {
6063
}
6164

6265
pub(crate) fn io_error(error: io::Error) -> Result {
63-
Self::error({ error_to_int(error) } as _)
66+
Self::error(error_to_int(error) as _)
6467
}
6568
}
6669

rt/src/runtime.rs

+6
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ pub unsafe extern "system" fn inko_runtime_new(
6767
// does for us when compiling an executable.
6868
signal_sched::block_all();
6969

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

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/socket.rs

+187-12
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ use crate::network_poller::Interest;
44
use crate::process::ProcessPointer;
55
use crate::result::{error_to_int, Result};
66
use crate::scheduler::timeouts::Timeout;
7-
use crate::socket::Socket;
7+
use crate::socket::{read_from, Socket};
88
use crate::state::State;
9-
use std::io::{self, Write};
9+
use rustls::pki_types::ServerName;
10+
use rustls::{ClientConfig, ClientConnection, RootCertStore, Stream};
11+
use rustls_pemfile::certs;
12+
use rustls_platform_verifier::tls_config;
13+
use std::fs::File;
14+
use std::io::{self, BufReader, Write};
1015
use std::ptr::{drop_in_place, write};
16+
use std::sync::Arc;
1117

1218
#[repr(C)]
1319
pub struct RawAddress {
@@ -24,19 +30,13 @@ impl RawAddress {
2430
}
2531
}
2632

27-
fn blocking<T>(
33+
fn poll(
2834
state: &State,
2935
mut process: ProcessPointer,
3036
socket: &mut Socket,
3137
interest: Interest,
3238
deadline: i64,
33-
mut func: impl FnMut(&mut Socket) -> io::Result<T>,
34-
) -> io::Result<T> {
35-
match func(socket) {
36-
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
37-
val => return val,
38-
}
39-
39+
) -> io::Result<()> {
4040
let poll_id = unsafe { process.thread() }.network_poller;
4141

4242
// We must keep the process' state lock open until everything is registered,
@@ -72,7 +72,24 @@ fn blocking<T>(
7272
return Err(io::Error::from(io::ErrorKind::TimedOut));
7373
}
7474

75-
func(socket)
75+
Ok(())
76+
}
77+
78+
fn blocking<T>(
79+
state: &State,
80+
process: ProcessPointer,
81+
socket: &mut Socket,
82+
interest: Interest,
83+
deadline: i64,
84+
mut func: impl FnMut(&mut Socket) -> io::Result<T>,
85+
) -> io::Result<T> {
86+
match func(socket) {
87+
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
88+
poll(state, process, socket, interest, deadline)
89+
.and_then(|_| func(socket))
90+
}
91+
val => val,
92+
}
7693
}
7794

7895
#[no_mangle]
@@ -127,7 +144,7 @@ pub unsafe extern "system" fn inko_socket_read(
127144
let state = &*state;
128145

129146
blocking(state, process, &mut *socket, Interest::Read, deadline, |sock| {
130-
sock.read(&mut (*buffer).value, amount as usize)
147+
read_from(sock, &mut (*buffer).value, amount as usize)
131148
})
132149
.map(|size| Result::ok(size as _))
133150
.unwrap_or_else(Result::io_error)
@@ -349,3 +366,161 @@ pub unsafe extern "system" fn inko_socket_try_clone(
349366
pub unsafe extern "system" fn inko_socket_drop(socket: *mut Socket) {
350367
drop_in_place(socket);
351368
}
369+
370+
#[no_mangle]
371+
pub unsafe extern "system" fn inko_tls_client_config_new() -> Result {
372+
Result::ok(Arc::into_raw(Arc::new(tls_config())) as *mut _)
373+
}
374+
375+
#[no_mangle]
376+
pub unsafe extern "system" fn inko_tls_client_config_with_certificate(
377+
path: *const InkoString,
378+
) -> Result {
379+
let mut store = RootCertStore::empty();
380+
let mut reader = match File::open(InkoString::read(path)) {
381+
Ok(f) => BufReader::new(f),
382+
Err(e) => return Result::io_error(e),
383+
};
384+
385+
for res in certs(&mut reader) {
386+
match res {
387+
// We don't want to expose a bunch of error messages/cases for the
388+
// different reasons for a certificate being invalid, as it's not
389+
// clear users actually care about that. As such, at least for the
390+
// time being we just use a single opaque error for invalid
391+
// certificates.
392+
Ok(cert) => {
393+
if store.add(cert).is_err() {
394+
return Result::none();
395+
}
396+
}
397+
Err(e) => return Result::io_error(e),
398+
}
399+
}
400+
401+
let conf = Arc::new(
402+
ClientConfig::builder()
403+
.with_root_certificates(store)
404+
.with_no_client_auth(),
405+
);
406+
407+
Result::ok(Arc::into_raw(conf) as *mut _)
408+
}
409+
410+
#[no_mangle]
411+
pub unsafe extern "system" fn inko_tls_client_config_clone(
412+
config: *const ClientConfig,
413+
) -> *const ClientConfig {
414+
Arc::increment_strong_count(config);
415+
config
416+
}
417+
418+
#[no_mangle]
419+
pub unsafe extern "system" fn inko_tls_client_config_drop(
420+
config: *const ClientConfig,
421+
) {
422+
drop(Arc::from_raw(config));
423+
}
424+
425+
#[no_mangle]
426+
pub unsafe extern "system" fn inko_tls_client_connection_new(
427+
config: *const ClientConfig,
428+
server: *const InkoString,
429+
) -> Result {
430+
let name = match ServerName::try_from(InkoString::read(server)) {
431+
Ok(v) => v,
432+
Err(_) => return Result::error(0 as _),
433+
};
434+
435+
Arc::increment_strong_count(config);
436+
437+
// ClientConnection::new() is fallible, but from the documentation and
438+
// source code it's not at all clear under what circumstances it produces an
439+
// error, and if such an error can be handled in a meaningful way. As such,
440+
// we panic in the event of an error.
441+
let con = ClientConnection::new(Arc::from_raw(config), name)
442+
.expect("failed to set up the client connection");
443+
444+
Result::ok_boxed(con)
445+
}
446+
447+
#[no_mangle]
448+
pub unsafe extern "system" fn inko_tls_client_connection_drop(
449+
connection: *mut ClientConnection,
450+
) {
451+
drop(Box::from_raw(connection));
452+
}
453+
454+
#[no_mangle]
455+
pub unsafe extern "system" fn inko_tls_socket_write(
456+
state: *const State,
457+
process: ProcessPointer,
458+
socket: *mut Socket,
459+
connection: *mut ClientConnection,
460+
data: *mut u8,
461+
size: i64,
462+
deadline: i64,
463+
) -> Result {
464+
let state = &*state;
465+
let slice = std::slice::from_raw_parts(data, size as _);
466+
let mut stream = Stream::new(&mut *connection, &mut *socket);
467+
468+
loop {
469+
match stream.write(slice) {
470+
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
471+
let interest = Interest::new(
472+
stream.conn.wants_read(),
473+
stream.conn.wants_write(),
474+
);
475+
476+
if let Err(e) =
477+
poll(state, process, stream.sock, interest, deadline)
478+
{
479+
return Result::io_error(e);
480+
}
481+
}
482+
val => {
483+
return val
484+
.map(|v| Result::ok(v as _))
485+
.unwrap_or_else(Result::io_error);
486+
}
487+
}
488+
}
489+
}
490+
491+
#[no_mangle]
492+
pub unsafe extern "system" fn inko_tls_socket_read(
493+
state: *const State,
494+
process: ProcessPointer,
495+
socket: *mut Socket,
496+
connection: *mut ClientConnection,
497+
buffer: *mut ByteArray,
498+
amount: i64,
499+
deadline: i64,
500+
) -> Result {
501+
let state = &*state;
502+
let buf = &mut (*buffer).value;
503+
let mut stream = Stream::new(&mut *connection, &mut *socket);
504+
505+
loop {
506+
match read_from(&mut stream, buf, amount as usize) {
507+
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
508+
let interest = Interest::new(
509+
stream.conn.wants_read(),
510+
stream.conn.wants_write(),
511+
);
512+
513+
if let Err(e) =
514+
poll(state, process, stream.sock, interest, deadline)
515+
{
516+
return Result::io_error(e);
517+
}
518+
}
519+
val => {
520+
return val
521+
.map(|v| Result::ok(v as _))
522+
.unwrap_or_else(Result::io_error);
523+
}
524+
};
525+
}
526+
}

0 commit comments

Comments
 (0)