itsi-scheduler 0.2.26-aarch64-linux → 0.2.27-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +3 -3
- data/Rakefile +33 -9
- data/ext/itsi_acme/Cargo.toml +2 -1
- data/ext/itsi_acme/src/acceptor.rs +1 -1
- data/ext/itsi_acme/src/acme.rs +31 -3
- data/ext/itsi_acme/src/http_challenge.rs +81 -0
- data/ext/itsi_acme/src/https_helper.rs +3 -1
- data/ext/itsi_acme/src/jose.rs +6 -2
- data/ext/itsi_acme/src/lib.rs +2 -0
- data/ext/itsi_acme/src/resolver.rs +27 -4
- data/ext/itsi_acme/src/state.rs +183 -22
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +115 -64
- data/ext/itsi_scheduler/src/lib.rs +2 -1
- data/ext/itsi_server/Cargo.lock +2 -2
- data/ext/itsi_server/Cargo.toml +2 -1
- data/ext/itsi_server/src/lib.rs +15 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +0 -1
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +9 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +114 -4
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +22 -1
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +100 -0
- data/ext/itsi_server/src/server/binds/listener.rs +9 -24
- data/ext/itsi_server/src/server/binds/tls.rs +372 -67
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +3 -9
- data/ext/itsi_server/src/server/signal.rs +14 -9
- data/ext/itsi_server/src/services/itsi_http_service.rs +51 -10
- data/lib/itsi/scheduler/3.1/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.2/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.3/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.4/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/4.0/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/version.rb +1 -1
- data/lib/itsi/scheduler.rb +121 -6
- metadata +3 -2
|
@@ -4,22 +4,19 @@ mod timer;
|
|
|
4
4
|
use io_helpers::{build_interest, poll_readiness, set_nonblocking};
|
|
5
5
|
use io_waiter::IoWaiter;
|
|
6
6
|
use itsi_error::ItsiError;
|
|
7
|
-
use itsi_rb_helpers::
|
|
8
|
-
use magnus::{
|
|
9
|
-
error::Result as MagnusResult,
|
|
10
|
-
value::{InnerValue, Lazy, LazyId, Opaque, ReprValue},
|
|
11
|
-
Module, RClass, Ruby, Value,
|
|
12
|
-
};
|
|
7
|
+
use itsi_rb_helpers::call_without_gvl;
|
|
8
|
+
use magnus::{error::Result as MagnusResult, Ruby};
|
|
13
9
|
use mio::{Events, Poll, Token, Waker};
|
|
14
|
-
use parking_lot::
|
|
10
|
+
use parking_lot::Mutex;
|
|
15
11
|
use std::{
|
|
16
12
|
collections::{BinaryHeap, HashMap, VecDeque},
|
|
13
|
+
ffi::CString,
|
|
17
14
|
os::fd::RawFd,
|
|
18
|
-
|
|
15
|
+
ptr,
|
|
19
16
|
time::Duration,
|
|
20
17
|
};
|
|
21
18
|
use timer::Timer;
|
|
22
|
-
use tracing::{debug,
|
|
19
|
+
use tracing::{debug, info, warn};
|
|
23
20
|
|
|
24
21
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
25
22
|
pub(crate) struct Readiness(i16);
|
|
@@ -31,9 +28,6 @@ impl std::fmt::Debug for ItsiScheduler {
|
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
const WAKE_TOKEN: Token = Token(0);
|
|
34
|
-
static ID_CURRENT: LazyId = LazyId::new("current");
|
|
35
|
-
static CLASS_FIBER: Lazy<RClass> =
|
|
36
|
-
Lazy::new(|ruby| ruby.module_kernel().const_get("Fiber").unwrap());
|
|
37
31
|
|
|
38
32
|
#[magnus::wrap(class = "Itsi::Scheduler", free_immediately, size)]
|
|
39
33
|
pub(crate) struct ItsiScheduler {
|
|
@@ -123,6 +117,57 @@ impl ItsiScheduler {
|
|
|
123
117
|
self.timers.lock().retain(|timer| timer.token.0 != token);
|
|
124
118
|
}
|
|
125
119
|
|
|
120
|
+
pub fn cancel_wait(&self, token: usize) -> MagnusResult<()> {
|
|
121
|
+
let token = Token(token);
|
|
122
|
+
|
|
123
|
+
self.timers.lock().retain(|timer| timer.token != token);
|
|
124
|
+
|
|
125
|
+
let mut io_waiters = self.io_waiters.lock();
|
|
126
|
+
let Some(mut waiter) = io_waiters.remove(&token) else {
|
|
127
|
+
return Ok(());
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
let mut registry = self.registry.lock();
|
|
131
|
+
let Some(queue) = registry.get_mut(&waiter.fd) else {
|
|
132
|
+
return Ok(());
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
let Some(position) = queue.iter().position(|entry| entry.token == token) else {
|
|
136
|
+
return Ok(());
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
if position == 0 {
|
|
140
|
+
self.poll
|
|
141
|
+
.lock()
|
|
142
|
+
.registry()
|
|
143
|
+
.deregister(&mut waiter)
|
|
144
|
+
.map_err(|_| {
|
|
145
|
+
ItsiError::ArgumentError("Failed to deregister".to_string())
|
|
146
|
+
})?;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
queue.remove(position);
|
|
150
|
+
|
|
151
|
+
if position == 0 {
|
|
152
|
+
if let Some(head) = queue.get_mut(0) {
|
|
153
|
+
let interest = build_interest(head.readiness)?;
|
|
154
|
+
self.poll
|
|
155
|
+
.lock()
|
|
156
|
+
.registry()
|
|
157
|
+
.register(head, head.token, interest)
|
|
158
|
+
.map_err(|_| {
|
|
159
|
+
ItsiError::ArgumentError("Failed to register".to_string())
|
|
160
|
+
})?;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if queue.is_empty() {
|
|
165
|
+
registry.remove(&waiter.fd);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Ok(())
|
|
169
|
+
}
|
|
170
|
+
|
|
126
171
|
pub fn has_pending_io(&self) -> bool {
|
|
127
172
|
!self.timers.lock().is_empty() || !self.io_waiters.lock().is_empty()
|
|
128
173
|
}
|
|
@@ -220,63 +265,69 @@ impl ItsiScheduler {
|
|
|
220
265
|
})
|
|
221
266
|
}
|
|
222
267
|
|
|
223
|
-
pub fn run_blocking_in_thread<T, F>(&self, ruby: &Ruby, work: F) -> MagnusResult<Option<T>>
|
|
224
|
-
where
|
|
225
|
-
T: Send + Sync + std::fmt::Debug + 'static,
|
|
226
|
-
F: FnOnce() -> Option<T> + Send + 'static,
|
|
227
|
-
{
|
|
228
|
-
let result: Arc<RwLock<Option<T>>> = Arc::new(RwLock::new(None));
|
|
229
|
-
let result_clone = Arc::clone(&result);
|
|
230
|
-
|
|
231
|
-
let class_fiber = ruby.get_inner(&CLASS_FIBER);
|
|
232
|
-
let current_fiber = ruby
|
|
233
|
-
.get_inner(&CLASS_FIBER)
|
|
234
|
-
.funcall::<_, _, Value>(*ID_CURRENT, ());
|
|
235
|
-
|
|
236
|
-
if current_fiber.is_err() {
|
|
237
|
-
error!("Failed to get current fiber");
|
|
238
|
-
return Err(ItsiError::ArgumentError("Failed to get current fiber".to_string()).into());
|
|
239
|
-
}
|
|
240
|
-
let current_fiber = Opaque::from(current_fiber.unwrap());
|
|
241
|
-
let scheduler = Opaque::from(class_fiber.funcall::<_, _, Value>("scheduler", ()).unwrap());
|
|
242
|
-
|
|
243
|
-
create_ruby_thread(move || {
|
|
244
|
-
call_without_gvl(|| {
|
|
245
|
-
let outcome = work();
|
|
246
|
-
*result_clone.write() = outcome;
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
let ruby = Ruby::get().unwrap();
|
|
250
|
-
scheduler
|
|
251
|
-
.get_inner_with(&ruby)
|
|
252
|
-
.funcall::<_, _, Value>("unblock", (None::<String>, current_fiber))
|
|
253
|
-
.unwrap();
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
scheduler
|
|
257
|
-
.get_inner_with(ruby)
|
|
258
|
-
.funcall::<_, _, Value>("block", (None::<Value>, None::<u64>))?;
|
|
259
|
-
|
|
260
|
-
let result_opt = Arc::try_unwrap(result).unwrap().write().take();
|
|
261
|
-
Ok(result_opt)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
268
|
pub fn address_resolve(
|
|
265
|
-
|
|
266
|
-
|
|
269
|
+
_ruby: &Ruby,
|
|
270
|
+
_rself: &Self,
|
|
267
271
|
hostname: String,
|
|
268
272
|
) -> MagnusResult<Option<Vec<String>>> {
|
|
269
|
-
let result: Option<Vec<String>> =
|
|
270
|
-
|
|
271
|
-
let
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
let result: Option<Vec<String>> = call_without_gvl(move || {
|
|
274
|
+
let hostname = CString::new(hostname).ok()?;
|
|
275
|
+
let hints = nix::libc::addrinfo {
|
|
276
|
+
ai_flags: 0,
|
|
277
|
+
ai_family: nix::libc::AF_UNSPEC,
|
|
278
|
+
ai_socktype: nix::libc::SOCK_STREAM,
|
|
279
|
+
ai_protocol: 0,
|
|
280
|
+
ai_addrlen: 0,
|
|
281
|
+
ai_addr: ptr::null_mut(),
|
|
282
|
+
ai_canonname: ptr::null_mut(),
|
|
283
|
+
ai_next: ptr::null_mut(),
|
|
284
|
+
};
|
|
285
|
+
let mut res: *mut nix::libc::addrinfo = ptr::null_mut();
|
|
286
|
+
let rc = unsafe {
|
|
287
|
+
nix::libc::getaddrinfo(hostname.as_ptr(), ptr::null(), &hints, &mut res)
|
|
288
|
+
};
|
|
289
|
+
if rc != 0 || res.is_null() {
|
|
290
|
+
return None;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let mut ips = Vec::new();
|
|
294
|
+
let mut current = res;
|
|
295
|
+
while !current.is_null() {
|
|
296
|
+
let ai = unsafe { &*current };
|
|
297
|
+
if !ai.ai_addr.is_null() {
|
|
298
|
+
match ai.ai_family {
|
|
299
|
+
nix::libc::AF_INET => {
|
|
300
|
+
let addr = unsafe {
|
|
301
|
+
&*(ai.ai_addr as *const nix::libc::sockaddr_in)
|
|
302
|
+
};
|
|
303
|
+
let ip = std::net::Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr));
|
|
304
|
+
ips.push(ip.to_string());
|
|
305
|
+
}
|
|
306
|
+
nix::libc::AF_INET6 => {
|
|
307
|
+
let addr = unsafe {
|
|
308
|
+
&*(ai.ai_addr as *const nix::libc::sockaddr_in6)
|
|
309
|
+
};
|
|
310
|
+
let ip = std::net::Ipv6Addr::from(addr.sin6_addr.s6_addr);
|
|
311
|
+
ips.push(ip.to_string());
|
|
312
|
+
}
|
|
313
|
+
_ => {}
|
|
314
|
+
}
|
|
276
315
|
}
|
|
277
|
-
|
|
316
|
+
current = ai.ai_next;
|
|
278
317
|
}
|
|
279
|
-
|
|
318
|
+
|
|
319
|
+
unsafe {
|
|
320
|
+
nix::libc::freeaddrinfo(res);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if ips.is_empty() {
|
|
324
|
+
None
|
|
325
|
+
} else {
|
|
326
|
+
ips.sort();
|
|
327
|
+
ips.dedup();
|
|
328
|
+
Some(ips)
|
|
329
|
+
}
|
|
330
|
+
});
|
|
280
331
|
Ok(result)
|
|
281
332
|
}
|
|
282
333
|
|
|
@@ -20,8 +20,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
20
20
|
scheduler.define_method("warn", method!(ItsiScheduler::warn, 1))?;
|
|
21
21
|
scheduler.define_method("start_timer", method!(ItsiScheduler::start_timer, 2))?;
|
|
22
22
|
scheduler.define_method("clear_timer", method!(ItsiScheduler::clear_timer, 1))?;
|
|
23
|
+
scheduler.define_method("cancel_wait", method!(ItsiScheduler::cancel_wait, 1))?;
|
|
23
24
|
scheduler.define_method(
|
|
24
|
-
"
|
|
25
|
+
"native_address_resolve",
|
|
25
26
|
method!(ItsiScheduler::address_resolve, 1),
|
|
26
27
|
)?;
|
|
27
28
|
scheduler.define_method("has_pending_io?", method!(ItsiScheduler::has_pending_io, 0))?;
|
data/ext/itsi_server/Cargo.lock
CHANGED
|
@@ -984,7 +984,7 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
|
|
984
984
|
|
|
985
985
|
[[package]]
|
|
986
986
|
name = "itsi-scheduler"
|
|
987
|
-
version = "0.
|
|
987
|
+
version = "0.2.23"
|
|
988
988
|
dependencies = [
|
|
989
989
|
"bytes",
|
|
990
990
|
"derive_more",
|
|
@@ -1002,7 +1002,7 @@ dependencies = [
|
|
|
1002
1002
|
|
|
1003
1003
|
[[package]]
|
|
1004
1004
|
name = "itsi-server"
|
|
1005
|
-
version = "0.
|
|
1005
|
+
version = "0.2.23"
|
|
1006
1006
|
dependencies = [
|
|
1007
1007
|
"async-channel",
|
|
1008
1008
|
"async-trait",
|
data/ext/itsi_server/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "itsi-server"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.27"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Wouter Coppieters <wc@pico.net.nz>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -70,6 +70,7 @@ redis = { version = "0.29.2", features = [
|
|
|
70
70
|
] }
|
|
71
71
|
rustls = "0.23.23"
|
|
72
72
|
rustls-pemfile = "2.2.0"
|
|
73
|
+
webpki-roots = "0.26"
|
|
73
74
|
serde = "1.0.219"
|
|
74
75
|
serde_json = "1.0.140"
|
|
75
76
|
serde_magnus = "0.11.0"
|
data/ext/itsi_server/src/lib.rs
CHANGED
|
@@ -41,6 +41,20 @@ fn init(ruby: &Ruby) -> Result<()> {
|
|
|
41
41
|
server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
|
|
42
42
|
server.define_method("start", method!(ItsiServer::start, 0))?;
|
|
43
43
|
server.define_method("stop", method!(ItsiServer::stop, 0))?;
|
|
44
|
+
server.define_method("tls_bindings", method!(ItsiServer::tls_bindings, 0))?;
|
|
45
|
+
server.define_method("tls_domains", method!(ItsiServer::tls_domains, 1))?;
|
|
46
|
+
server.define_method(
|
|
47
|
+
"tls_domain_statuses",
|
|
48
|
+
method!(ItsiServer::tls_domain_statuses, 1),
|
|
49
|
+
)?;
|
|
50
|
+
server.define_method(
|
|
51
|
+
"register_tls_domain",
|
|
52
|
+
method!(ItsiServer::register_tls_domain, 2),
|
|
53
|
+
)?;
|
|
54
|
+
server.define_method(
|
|
55
|
+
"unregister_tls_domain",
|
|
56
|
+
method!(ItsiServer::unregister_tls_domain, 2),
|
|
57
|
+
)?;
|
|
44
58
|
|
|
45
59
|
let request = ruby.get_inner(&ITSI_REQUEST);
|
|
46
60
|
request.define_method("path", method!(ItsiHttpRequest::path, 0))?;
|
|
@@ -94,6 +108,7 @@ fn init(ruby: &Ruby) -> Result<()> {
|
|
|
94
108
|
response.define_method("<<", method!(ItsiHttpResponse::send_frame, 1))?;
|
|
95
109
|
response.define_method("write", method!(ItsiHttpResponse::send_frame, 1))?;
|
|
96
110
|
response.define_method("read", method!(ItsiHttpResponse::recv_frame, 0))?;
|
|
111
|
+
response.define_method("partial_hijack", method!(ItsiHttpResponse::partial_hijack, 1))?;
|
|
97
112
|
response.define_method("closed?", method!(ItsiHttpResponse::is_closed, 0))?;
|
|
98
113
|
response.define_method(
|
|
99
114
|
"send_and_close",
|
|
@@ -11,7 +11,6 @@ use http::{request::Parts, Response, StatusCode};
|
|
|
11
11
|
use http_body_util::BodyExt;
|
|
12
12
|
use itsi_error::CLIENT_CONNECTION_CLOSED;
|
|
13
13
|
use itsi_rb_helpers::{print_rb_backtrace, HeapValue};
|
|
14
|
-
use itsi_tracing::debug;
|
|
15
14
|
use magnus::{
|
|
16
15
|
block::Proc,
|
|
17
16
|
error::{ErrorType, Result as MagnusResult},
|
|
@@ -211,6 +211,15 @@ impl ItsiHttpRequest {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
|
+
Ok(ResponseFrame::PartialHijackedResponse(response)) => {
|
|
215
|
+
match response.process_partial_hijacked_response().await {
|
|
216
|
+
Ok(result) => Ok(result),
|
|
217
|
+
Err(e) => {
|
|
218
|
+
error!("Error processing partial hijacked response: {}", e);
|
|
219
|
+
Ok(Response::new(HttpBody::empty()))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
214
223
|
Err(_) => {
|
|
215
224
|
error!("Failed to receive response from receiver");
|
|
216
225
|
Ok(INTERNAL_SERVER_ERROR_RESPONSE
|
|
@@ -23,10 +23,12 @@ use parking_lot::RwLock;
|
|
|
23
23
|
use std::{
|
|
24
24
|
collections::HashMap,
|
|
25
25
|
io,
|
|
26
|
+
io::Read,
|
|
26
27
|
ops::Deref,
|
|
27
28
|
os::{fd::FromRawFd, unix::net::UnixStream},
|
|
28
29
|
str::FromStr,
|
|
29
30
|
sync::Arc,
|
|
31
|
+
thread,
|
|
30
32
|
time::Duration,
|
|
31
33
|
};
|
|
32
34
|
use tokio::{
|
|
@@ -56,6 +58,7 @@ pub struct ResponseInner {
|
|
|
56
58
|
pub frame_writer: RwLock<Option<Sender<Bytes>>>,
|
|
57
59
|
pub response: RwLock<Option<HttpResponse>>,
|
|
58
60
|
pub hijacked_socket: RwLock<Option<UnixStream>>,
|
|
61
|
+
pub partial_hijacked_socket: RwLock<Option<UnixStream>>,
|
|
59
62
|
pub response_sender: RwLock<Option<OneshotSender<ResponseFrame>>>,
|
|
60
63
|
pub shutdown_rx: watch::Receiver<RunningPhase>,
|
|
61
64
|
pub parts: Arc<Parts>,
|
|
@@ -65,9 +68,21 @@ pub struct ResponseInner {
|
|
|
65
68
|
pub enum ResponseFrame {
|
|
66
69
|
HttpResponse(Box<HttpResponse>),
|
|
67
70
|
HijackedResponse(ItsiHttpResponse),
|
|
71
|
+
PartialHijackedResponse(ItsiHttpResponse),
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
impl ItsiHttpResponse {
|
|
75
|
+
fn is_expected_bridge_shutdown(err: &io::Error) -> bool {
|
|
76
|
+
matches!(
|
|
77
|
+
err.kind(),
|
|
78
|
+
io::ErrorKind::BrokenPipe
|
|
79
|
+
| io::ErrorKind::ConnectionAborted
|
|
80
|
+
| io::ErrorKind::ConnectionReset
|
|
81
|
+
| io::ErrorKind::NotConnected
|
|
82
|
+
| io::ErrorKind::UnexpectedEof
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
pub fn new(
|
|
72
87
|
parts: Arc<Parts>,
|
|
73
88
|
response_sender: OneshotSender<ResponseFrame>,
|
|
@@ -81,6 +96,7 @@ impl ItsiHttpResponse {
|
|
|
81
96
|
frame_writer: RwLock::new(None),
|
|
82
97
|
response: RwLock::new(Some(Response::new(HttpBody::empty()))),
|
|
83
98
|
hijacked_socket: RwLock::new(None),
|
|
99
|
+
partial_hijacked_socket: RwLock::new(None),
|
|
84
100
|
}),
|
|
85
101
|
}
|
|
86
102
|
}
|
|
@@ -93,13 +109,17 @@ impl ItsiHttpResponse {
|
|
|
93
109
|
let (mut cr, mut cw) = tokio::io::split(client_io);
|
|
94
110
|
|
|
95
111
|
let to_ruby = tokio::spawn(async move {
|
|
96
|
-
if let Err(
|
|
97
|
-
|
|
112
|
+
if let Err(err) = tokio::io::copy(&mut cr, &mut lw).await {
|
|
113
|
+
if !Self::is_expected_bridge_shutdown(&err) {
|
|
114
|
+
eprintln!("Error copying upgraded->local: {:?}", err);
|
|
115
|
+
}
|
|
98
116
|
}
|
|
99
117
|
});
|
|
100
118
|
let from_ruby = tokio::spawn(async move {
|
|
101
|
-
if let Err(
|
|
102
|
-
|
|
119
|
+
if let Err(err) = tokio::io::copy(&mut lr, &mut cw).await {
|
|
120
|
+
if !Self::is_expected_bridge_shutdown(&err) {
|
|
121
|
+
eprintln!("Error copying local->upgraded: {:?}", err);
|
|
122
|
+
}
|
|
103
123
|
}
|
|
104
124
|
});
|
|
105
125
|
|
|
@@ -219,6 +239,42 @@ impl ItsiHttpResponse {
|
|
|
219
239
|
Ok(response)
|
|
220
240
|
}
|
|
221
241
|
|
|
242
|
+
pub async fn process_partial_hijacked_response(&self) -> Result<HttpResponse> {
|
|
243
|
+
let stream =
|
|
244
|
+
self.partial_hijacked_socket
|
|
245
|
+
.write()
|
|
246
|
+
.take()
|
|
247
|
+
.ok_or(itsi_error::ItsiError::InvalidInput(
|
|
248
|
+
"Couldn't partial hijack stream".to_owned(),
|
|
249
|
+
))?;
|
|
250
|
+
let stream = TokioUnixStream::from_std(stream).map_err(|err| {
|
|
251
|
+
itsi_error::ItsiError::InvalidInput(format!(
|
|
252
|
+
"Couldn't prepare partial hijack stream: {:?}",
|
|
253
|
+
err
|
|
254
|
+
))
|
|
255
|
+
})?;
|
|
256
|
+
|
|
257
|
+
let mut response = self.response.write().take().ok_or(
|
|
258
|
+
itsi_error::ItsiError::InvalidInput("Missing partial hijack response".to_owned()),
|
|
259
|
+
)?;
|
|
260
|
+
let parts = self.parts.clone();
|
|
261
|
+
|
|
262
|
+
tokio::spawn(async move {
|
|
263
|
+
let mut req = Request::from_parts((*parts).clone(), Empty::<Bytes>::new());
|
|
264
|
+
match hyper::upgrade::on(&mut req).await {
|
|
265
|
+
Ok(upgraded) => {
|
|
266
|
+
if let Err(err) = Self::two_way_bridge(upgraded, stream).await {
|
|
267
|
+
warn!("partial hijack bridge error: {:?}", err);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
Err(err) => warn!("partial hijack upgrade error: {:?}", err),
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
*response.body_mut() = HttpBody::empty();
|
|
275
|
+
Ok(response)
|
|
276
|
+
}
|
|
277
|
+
|
|
222
278
|
pub fn internal_server_error(&self, message: String) {
|
|
223
279
|
error!(message);
|
|
224
280
|
self.close_write().ok();
|
|
@@ -304,6 +360,60 @@ impl ItsiHttpResponse {
|
|
|
304
360
|
// not implemented
|
|
305
361
|
}
|
|
306
362
|
|
|
363
|
+
pub fn partial_hijack(&self, fd: i32) -> MagnusResult<()> {
|
|
364
|
+
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
|
|
365
|
+
let status = self
|
|
366
|
+
.response
|
|
367
|
+
.read()
|
|
368
|
+
.as_ref()
|
|
369
|
+
.map(|response| response.status())
|
|
370
|
+
.unwrap_or(StatusCode::OK);
|
|
371
|
+
|
|
372
|
+
if status == StatusCode::SWITCHING_PROTOCOLS {
|
|
373
|
+
*self.partial_hijacked_socket.write() = Some(stream);
|
|
374
|
+
if let Some(sender) = self.response_sender.write().take() {
|
|
375
|
+
sender
|
|
376
|
+
.send(ResponseFrame::PartialHijackedResponse(self.clone()))
|
|
377
|
+
.ok();
|
|
378
|
+
}
|
|
379
|
+
} else if let Some(mut response) = self.response.write().take() {
|
|
380
|
+
let (writer, reader) = tokio::sync::mpsc::channel::<Bytes>(256);
|
|
381
|
+
let shutdown_rx = self.shutdown_rx.clone();
|
|
382
|
+
let frame_stream = FrameStream::new(reader, shutdown_rx);
|
|
383
|
+
let buffered =
|
|
384
|
+
BufferedStream::new(frame_stream, 32 * 1024, Duration::from_millis(10));
|
|
385
|
+
*response.body_mut() = HttpBody::stream(buffered);
|
|
386
|
+
self.frame_writer.write().replace(writer.clone());
|
|
387
|
+
|
|
388
|
+
thread::spawn(move || {
|
|
389
|
+
let mut buf = [0u8; 16 * 1024];
|
|
390
|
+
loop {
|
|
391
|
+
match stream.read(&mut buf) {
|
|
392
|
+
Ok(0) => break,
|
|
393
|
+
Ok(n) => {
|
|
394
|
+
if writer
|
|
395
|
+
.blocking_send(Bytes::copy_from_slice(&buf[..n]))
|
|
396
|
+
.is_err()
|
|
397
|
+
{
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
|
|
402
|
+
Err(_) => break,
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
if let Some(sender) = self.response_sender.write().take() {
|
|
408
|
+
sender
|
|
409
|
+
.send(ResponseFrame::HttpResponse(Box::new(response)))
|
|
410
|
+
.ok();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
Ok(())
|
|
415
|
+
}
|
|
416
|
+
|
|
307
417
|
pub fn is_closed(&self) -> bool {
|
|
308
418
|
self.response.read().is_none() && self.frame_writer.read().is_none()
|
|
309
419
|
}
|
|
@@ -2,7 +2,7 @@ use super::file_watcher::{self, WatcherCommand};
|
|
|
2
2
|
use crate::{
|
|
3
3
|
ruby_types::ITSI_SERVER_CONFIG,
|
|
4
4
|
server::{
|
|
5
|
-
binds::{bind::Bind, listener::Listener},
|
|
5
|
+
binds::{bind::Bind, listener::Listener, tls::DynamicAcmeManager},
|
|
6
6
|
middleware_stack::MiddlewareSet,
|
|
7
7
|
},
|
|
8
8
|
};
|
|
@@ -81,6 +81,8 @@ pub struct ServerParams {
|
|
|
81
81
|
pub binds: Vec<Bind>,
|
|
82
82
|
#[debug(skip)]
|
|
83
83
|
pub(crate) listeners: Mutex<Vec<Listener>>,
|
|
84
|
+
#[debug(skip)]
|
|
85
|
+
pub acme_managers: RwLock<Vec<(String, DynamicAcmeManager)>>,
|
|
84
86
|
listener_info: Mutex<HashMap<String, i32>>,
|
|
85
87
|
pub itsi_server_token_preference: ItsiServerTokenPreference,
|
|
86
88
|
pub preloaded: AtomicBool,
|
|
@@ -348,6 +350,7 @@ impl ServerParams {
|
|
|
348
350
|
preexisting_listeners,
|
|
349
351
|
listener_info: Mutex::new(HashMap::new()),
|
|
350
352
|
listeners: Mutex::new(Vec::new()),
|
|
353
|
+
acme_managers: RwLock::new(Vec::new()),
|
|
351
354
|
middleware_loader: middleware_loader.into(),
|
|
352
355
|
middleware: OnceLock::new(),
|
|
353
356
|
preloaded: AtomicBool::new(false),
|
|
@@ -401,8 +404,26 @@ impl ServerParams {
|
|
|
401
404
|
})
|
|
402
405
|
.collect::<Result<HashMap<String, i32>>>()?;
|
|
403
406
|
|
|
407
|
+
let has_http_listener = listeners
|
|
408
|
+
.iter()
|
|
409
|
+
.any(|listener| matches!(listener, Listener::Tcp(_)));
|
|
410
|
+
let mut acme_managers = Vec::new();
|
|
411
|
+
for listener in &listeners {
|
|
412
|
+
match listener {
|
|
413
|
+
Listener::TcpTls((_, acceptor)) | Listener::UnixTls((_, acceptor)) => {
|
|
414
|
+
acceptor.set_http01_enabled(has_http_listener);
|
|
415
|
+
acceptor.initialize_domains();
|
|
416
|
+
if let Some(manager) = acceptor.manager() {
|
|
417
|
+
acme_managers.push((listener.handover()?.0, manager));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
Listener::Tcp(_) | Listener::Unix(_) => {}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
404
424
|
*self.listener_info.lock() = listener_info;
|
|
405
425
|
*self.listeners.lock() = listeners;
|
|
426
|
+
*self.acme_managers.write() = acme_managers;
|
|
406
427
|
Ok(())
|
|
407
428
|
}
|
|
408
429
|
}
|
|
@@ -8,6 +8,7 @@ use itsi_server_config::ItsiServerConfig;
|
|
|
8
8
|
use itsi_tracing::{error, run_silently};
|
|
9
9
|
use magnus::{block::Proc, error::Result, RHash, Ruby};
|
|
10
10
|
use parking_lot::Mutex;
|
|
11
|
+
use std::collections::HashMap;
|
|
11
12
|
use std::{path::PathBuf, sync::Arc};
|
|
12
13
|
use tracing::{info, instrument};
|
|
13
14
|
mod file_watcher;
|
|
@@ -46,6 +47,105 @@ impl ItsiServer {
|
|
|
46
47
|
Ok(())
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
fn selected_acme_manager(
|
|
51
|
+
&self,
|
|
52
|
+
listener_id: Option<String>,
|
|
53
|
+
) -> Result<crate::server::binds::tls::DynamicAcmeManager> {
|
|
54
|
+
let config = self.config()?;
|
|
55
|
+
let server_params = config.server_params.read();
|
|
56
|
+
if server_params.workers > 1 {
|
|
57
|
+
return Err(magnus::Error::new(
|
|
58
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
59
|
+
"Dynamic TLS domain management currently only supports single-worker mode",
|
|
60
|
+
));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let managers = server_params.acme_managers.read();
|
|
64
|
+
if managers.is_empty() {
|
|
65
|
+
return Err(magnus::Error::new(
|
|
66
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
67
|
+
"No ACME-managed TLS bindings are configured",
|
|
68
|
+
));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if let Some(listener_id) = listener_id {
|
|
72
|
+
managers
|
|
73
|
+
.iter()
|
|
74
|
+
.find(|(id, _)| id == &listener_id)
|
|
75
|
+
.map(|(_, manager)| manager.clone())
|
|
76
|
+
.ok_or_else(|| {
|
|
77
|
+
magnus::Error::new(
|
|
78
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
79
|
+
format!("Unknown ACME TLS binding: {}", listener_id),
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
} else if managers.len() == 1 {
|
|
83
|
+
Ok(managers[0].1.clone())
|
|
84
|
+
} else {
|
|
85
|
+
Err(magnus::Error::new(
|
|
86
|
+
magnus::Ruby::get().unwrap().exception_runtime_error(),
|
|
87
|
+
"Multiple ACME TLS bindings are configured; specify a listener_id",
|
|
88
|
+
))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub fn tls_bindings(&self) -> Result<Vec<String>> {
|
|
93
|
+
let config = self.config()?;
|
|
94
|
+
let server_params = config.server_params.read();
|
|
95
|
+
let bindings = server_params
|
|
96
|
+
.acme_managers
|
|
97
|
+
.read()
|
|
98
|
+
.iter()
|
|
99
|
+
.map(|(listener_id, _)| listener_id.clone())
|
|
100
|
+
.collect();
|
|
101
|
+
Ok(bindings)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub fn tls_domains(&self, listener_id: Option<String>) -> Result<Vec<String>> {
|
|
105
|
+
let manager = self.selected_acme_manager(listener_id)?;
|
|
106
|
+
Ok(manager
|
|
107
|
+
.statuses()
|
|
108
|
+
.into_iter()
|
|
109
|
+
.map(|status| status.domain)
|
|
110
|
+
.collect())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub fn tls_domain_statuses(
|
|
114
|
+
&self,
|
|
115
|
+
listener_id: Option<String>,
|
|
116
|
+
) -> Result<Vec<HashMap<String, String>>> {
|
|
117
|
+
let manager = self.selected_acme_manager(listener_id)?;
|
|
118
|
+
Ok(manager
|
|
119
|
+
.statuses()
|
|
120
|
+
.into_iter()
|
|
121
|
+
.map(|status| {
|
|
122
|
+
let mut out = HashMap::new();
|
|
123
|
+
out.insert("domain".to_string(), status.domain);
|
|
124
|
+
out.insert("status".to_string(), status.status);
|
|
125
|
+
if let Some(last_error) = status.last_error {
|
|
126
|
+
out.insert("last_error".to_string(), last_error);
|
|
127
|
+
}
|
|
128
|
+
out
|
|
129
|
+
})
|
|
130
|
+
.collect())
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
pub fn register_tls_domain(&self, domain: String, listener_id: Option<String>) -> Result<()> {
|
|
134
|
+
let manager = self.selected_acme_manager(listener_id)?;
|
|
135
|
+
manager.register_domain(domain);
|
|
136
|
+
Ok(())
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pub fn unregister_tls_domain(
|
|
140
|
+
&self,
|
|
141
|
+
domain: String,
|
|
142
|
+
listener_id: Option<String>,
|
|
143
|
+
) -> Result<()> {
|
|
144
|
+
let manager = self.selected_acme_manager(listener_id)?;
|
|
145
|
+
manager.unregister_domain(&domain);
|
|
146
|
+
Ok(())
|
|
147
|
+
}
|
|
148
|
+
|
|
49
149
|
fn config(&self) -> Result<Arc<ItsiServerConfig>> {
|
|
50
150
|
self.config.lock().as_ref().cloned().ok_or_else(|| {
|
|
51
151
|
magnus::Error::new(
|