itsi 0.2.26 → 0.2.27.rc1

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +7 -3
  3. data/Rakefile +24 -3
  4. data/crates/itsi_acme/Cargo.toml +2 -1
  5. data/crates/itsi_acme/src/acceptor.rs +1 -1
  6. data/crates/itsi_acme/src/acme.rs +31 -3
  7. data/crates/itsi_acme/src/http_challenge.rs +81 -0
  8. data/crates/itsi_acme/src/https_helper.rs +3 -1
  9. data/crates/itsi_acme/src/jose.rs +6 -2
  10. data/crates/itsi_acme/src/lib.rs +2 -0
  11. data/crates/itsi_acme/src/resolver.rs +27 -4
  12. data/crates/itsi_acme/src/state.rs +183 -22
  13. data/crates/itsi_scheduler/Cargo.toml +1 -1
  14. data/crates/itsi_scheduler/src/itsi_scheduler.rs +115 -64
  15. data/crates/itsi_scheduler/src/lib.rs +2 -1
  16. data/crates/itsi_server/Cargo.toml +2 -1
  17. data/crates/itsi_server/src/lib.rs +15 -0
  18. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +9 -0
  19. data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +95 -0
  20. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +22 -1
  21. data/crates/itsi_server/src/ruby_types/itsi_server.rs +100 -0
  22. data/crates/itsi_server/src/server/binds/listener.rs +9 -24
  23. data/crates/itsi_server/src/server/binds/tls.rs +372 -67
  24. data/crates/itsi_server/src/services/itsi_http_service.rs +46 -2
  25. data/gems/scheduler/Cargo.lock +4011 -527
  26. data/gems/scheduler/Gemfile +8 -2
  27. data/gems/scheduler/Gemfile.lock +107 -0
  28. data/gems/scheduler/Rakefile +33 -9
  29. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  30. data/gems/scheduler/lib/itsi/scheduler.rb +121 -6
  31. data/gems/scheduler/test/helpers/test_helper.rb +2 -0
  32. data/gems/scheduler/test/test_address_resolve.rb +8 -2
  33. data/gems/scheduler/test/test_itsi_scheduler.rb +80 -0
  34. data/gems/scheduler/test/test_timeout_after.rb +102 -0
  35. data/gems/server/Cargo.lock +30 -1
  36. data/gems/server/Gemfile +2 -0
  37. data/gems/server/Gemfile.lock +123 -0
  38. data/gems/server/Rakefile +18 -5
  39. data/gems/server/lib/itsi/http_request.rb +10 -0
  40. data/gems/server/lib/itsi/server/rack_interface.rb +45 -2
  41. data/gems/server/lib/itsi/server/version.rb +1 -1
  42. data/gems/server/lib/itsi/server.rb +24 -0
  43. data/gems/server/test/acme/local_acme_challenges.rb +190 -0
  44. data/gems/server/test/helpers/local_acme.rb +218 -0
  45. data/gems/server/test/helpers/test_helper.rb +7 -9
  46. data/gems/server/test/middleware/endpoint.rb +9 -6
  47. data/gems/server/test/rack/test_rack_server.rb +79 -0
  48. data/lib/itsi/version.rb +1 -1
  49. metadata +12 -6
@@ -20,8 +20,9 @@ use x509_parser::parse_x509_certificate;
20
20
 
21
21
  use crate::acceptor::AcmeAcceptor;
22
22
  use crate::acme::{
23
- Account, AcmeError, Auth, AuthStatus, Directory, Identifier, Order, OrderStatus,
23
+ Account, AcmeError, Auth, AuthStatus, Challenge, Directory, Identifier, Order, OrderStatus,
24
24
  };
25
+ use crate::http_challenge::Http01Handler;
25
26
  use crate::{AcmeConfig, Incoming, ResolvesServerCertAcme};
26
27
 
27
28
  type Timer = std::pin::Pin<Box<Sleep>>;
@@ -36,6 +37,9 @@ pub struct AcmeState<EC: Debug = Infallible, EA: Debug = EC> {
36
37
  config: Arc<AcmeConfig<EC, EA>>,
37
38
  resolver: Arc<ResolvesServerCertAcme>,
38
39
  account_key: Option<Vec<u8>>,
40
+ http01_handler: Arc<Http01Handler>,
41
+ http01_enabled: bool,
42
+ managed_domain: Option<String>,
39
43
 
40
44
  early_action: Option<BoxFuture<Event<EC, EA>>>,
41
45
  load_cert: Option<BoxFuture<Result<Option<Vec<u8>>, EC>>>,
@@ -128,12 +132,29 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
128
132
  pub fn resolver(&self) -> Arc<ResolvesServerCertAcme> {
129
133
  self.resolver.clone()
130
134
  }
135
+ pub fn http01_handler(&self) -> Arc<Http01Handler> {
136
+ self.http01_handler.clone()
137
+ }
138
+ pub fn set_http01_enabled(&mut self, enabled: bool) {
139
+ self.http01_enabled = enabled;
140
+ }
131
141
  pub fn new(config: AcmeConfig<EC, EA>) -> Self {
142
+ Self::new_with_resolver(config, ResolvesServerCertAcme::new(), Arc::new(Http01Handler::new()), None)
143
+ }
144
+ pub fn new_with_resolver(
145
+ config: AcmeConfig<EC, EA>,
146
+ resolver: Arc<ResolvesServerCertAcme>,
147
+ http01_handler: Arc<Http01Handler>,
148
+ managed_domain: Option<String>,
149
+ ) -> Self {
132
150
  let config = Arc::new(config);
133
151
  Self {
134
152
  config: config.clone(),
135
- resolver: ResolvesServerCertAcme::new(),
153
+ resolver,
136
154
  account_key: None,
155
+ http01_handler,
156
+ http01_enabled: false,
157
+ managed_domain,
137
158
  early_action: None,
138
159
  load_cert: Some(Box::pin({
139
160
  let config = config.clone();
@@ -195,7 +216,11 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
195
216
  }
196
217
  }
197
218
  };
198
- self.resolver.set_cert(Arc::new(cert));
219
+ let cert = Arc::new(cert);
220
+ match self.managed_domain.as_ref() {
221
+ Some(domain) => self.resolver.set_cert_for_domain(domain.clone(), cert),
222
+ None => self.resolver.set_cert(cert),
223
+ }
199
224
  let wait_duration = (validity[1] - (validity[1] - validity[0]) / 3 - Utc::now())
200
225
  .max(chrono::Duration::zero())
201
226
  .to_std()
@@ -220,6 +245,8 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
220
245
  async fn order(
221
246
  config: Arc<AcmeConfig<EC, EA>>,
222
247
  resolver: Arc<ResolvesServerCertAcme>,
248
+ http01_handler: Arc<Http01Handler>,
249
+ http01_enabled: bool,
223
250
  key_pair: Vec<u8>,
224
251
  ) -> Result<Vec<u8>, OrderError> {
225
252
  let directory = Directory::discover(&config.client_config, &config.directory_url).await?;
@@ -242,10 +269,16 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
242
269
  loop {
243
270
  match order.status {
244
271
  OrderStatus::Pending => {
245
- let auth_futures = order
246
- .authorizations
247
- .iter()
248
- .map(|url| Self::authorize(&config, &resolver, &account, url));
272
+ let auth_futures = order.authorizations.iter().map(|url| {
273
+ Self::authorize(
274
+ &config,
275
+ &resolver,
276
+ &http01_handler,
277
+ http01_enabled,
278
+ &account,
279
+ url,
280
+ )
281
+ });
249
282
  try_join_all(auth_futures).await?;
250
283
  log::info!("completed all authorizations");
251
284
  order = account.order(&config.client_config, &order_url).await?;
@@ -289,40 +322,147 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
289
322
  async fn authorize(
290
323
  config: &AcmeConfig<EC, EA>,
291
324
  resolver: &ResolvesServerCertAcme,
325
+ http01_handler: &Http01Handler,
326
+ http01_enabled: bool,
292
327
  account: &Account,
293
328
  url: &String,
294
329
  ) -> Result<(), OrderError> {
295
330
  let auth = account.auth(&config.client_config, url).await?;
296
- let (domain, challenge_url) = match auth.status {
331
+ let (domain, challenges) = match auth.status {
297
332
  AuthStatus::Pending => {
298
333
  let Identifier::Dns(domain) = auth.identifier;
299
- log::info!("trigger challenge for {}", &domain);
334
+ (domain, auth.challenges)
335
+ }
336
+ AuthStatus::Valid => return Ok(()),
337
+ _ => return Err(OrderError::BadAuth(auth)),
338
+ };
339
+
340
+ log::info!("trigger challenge for {}", &domain);
341
+
342
+ let primary = if http01_enabled {
343
+ ChallengeKind::Http01
344
+ } else {
345
+ ChallengeKind::TlsAlpn01
346
+ };
347
+ let secondary = match primary {
348
+ ChallengeKind::Http01 => ChallengeKind::TlsAlpn01,
349
+ ChallengeKind::TlsAlpn01 => ChallengeKind::Http01,
350
+ };
351
+
352
+ match Self::attempt_authorization(
353
+ config,
354
+ resolver,
355
+ http01_handler,
356
+ account,
357
+ &domain,
358
+ url,
359
+ &challenges,
360
+ primary,
361
+ )
362
+ .await?
363
+ {
364
+ AttemptOutcome::Validated => return Ok(()),
365
+ AttemptOutcome::Unavailable | AttemptOutcome::RetryableFailure => {}
366
+ }
367
+
368
+ match Self::attempt_authorization(
369
+ config,
370
+ resolver,
371
+ http01_handler,
372
+ account,
373
+ &domain,
374
+ url,
375
+ &challenges,
376
+ secondary,
377
+ )
378
+ .await?
379
+ {
380
+ AttemptOutcome::Validated => Ok(()),
381
+ AttemptOutcome::Unavailable | AttemptOutcome::RetryableFailure => {
382
+ Err(OrderError::TooManyAttemptsAuth(domain))
383
+ }
384
+ }
385
+ }
386
+
387
+ async fn attempt_authorization(
388
+ config: &AcmeConfig<EC, EA>,
389
+ resolver: &ResolvesServerCertAcme,
390
+ http01_handler: &Http01Handler,
391
+ account: &Account,
392
+ domain: &str,
393
+ auth_url: &str,
394
+ challenges: &[Challenge],
395
+ kind: ChallengeKind,
396
+ ) -> Result<AttemptOutcome, OrderError> {
397
+ match kind {
398
+ ChallengeKind::TlsAlpn01 => {
300
399
  let (challenge, auth_key) =
301
- account.tls_alpn_01(&auth.challenges, domain.clone())?;
302
- resolver.set_auth_key(domain.clone(), Arc::new(auth_key));
400
+ match account.tls_alpn_01(challenges, domain.to_string()) {
401
+ Ok(value) => value,
402
+ Err(AcmeError::NoTlsAlpn01Challenge) => {
403
+ return Ok(AttemptOutcome::Unavailable);
404
+ }
405
+ Err(error) => return Err(OrderError::Acme(error)),
406
+ };
407
+
408
+ resolver.set_auth_key(domain.to_string(), Arc::new(auth_key));
303
409
  account
304
410
  .challenge(&config.client_config, &challenge.url)
305
411
  .await?;
306
- (domain, challenge.url.clone())
412
+ let outcome =
413
+ Self::poll_authorization(config, account, domain, auth_url, &challenge.url)
414
+ .await?;
415
+ resolver.remove_auth_key(domain);
416
+ Ok(outcome)
307
417
  }
308
- AuthStatus::Valid => return Ok(()),
309
- _ => return Err(OrderError::BadAuth(auth)),
310
- };
418
+ ChallengeKind::Http01 => {
419
+ let (challenge, key_authorization) = match account.http_01(challenges) {
420
+ Ok(value) => value,
421
+ Err(AcmeError::NoHttp01Challenge) => return Ok(AttemptOutcome::Unavailable),
422
+ Err(error) => return Err(OrderError::Acme(error)),
423
+ };
424
+ let token = challenge
425
+ .token
426
+ .clone()
427
+ .ok_or(OrderError::Acme(AcmeError::MissingChallengeToken))?;
428
+
429
+ http01_handler.add_challenge(token.clone(), key_authorization);
430
+ account
431
+ .challenge(&config.client_config, &challenge.url)
432
+ .await?;
433
+ let outcome =
434
+ Self::poll_authorization(config, account, domain, auth_url, &challenge.url)
435
+ .await?;
436
+ http01_handler.remove_challenge(&token);
437
+ Ok(outcome)
438
+ }
439
+ }
440
+ }
441
+
442
+ async fn poll_authorization(
443
+ config: &AcmeConfig<EC, EA>,
444
+ account: &Account,
445
+ domain: &str,
446
+ auth_url: &str,
447
+ challenge_url: &str,
448
+ ) -> Result<AttemptOutcome, OrderError> {
311
449
  for i in 0u64..5 {
312
450
  after(Duration::from_secs(1u64 << i)).await;
313
- let auth = account.auth(&config.client_config, url).await?;
451
+ let auth = account.auth(&config.client_config, auth_url).await?;
314
452
  match auth.status {
315
453
  AuthStatus::Pending => {
316
- log::info!("authorization for {} still pending", &domain);
454
+ log::info!("authorization for {} still pending", domain);
317
455
  account
318
- .challenge(&config.client_config, &challenge_url)
319
- .await?
456
+ .challenge(&config.client_config, challenge_url)
457
+ .await?;
320
458
  }
321
- AuthStatus::Valid => return Ok(()),
459
+ AuthStatus::Valid => return Ok(AttemptOutcome::Validated),
460
+ AuthStatus::Invalid => return Ok(AttemptOutcome::RetryableFailure),
322
461
  _ => return Err(OrderError::BadAuth(auth)),
323
462
  }
324
463
  }
325
- Err(OrderError::TooManyAttemptsAuth(domain))
464
+
465
+ Ok(AttemptOutcome::RetryableFailure)
326
466
  }
327
467
  fn poll_next_infinite(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Event<EC, EA>> {
328
468
  loop {
@@ -408,13 +548,34 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
408
548
  };
409
549
  let config = self.config.clone();
410
550
  let resolver = self.resolver.clone();
551
+ let http01_handler = self.http01_handler.clone();
552
+ let http01_enabled = self.http01_enabled;
411
553
  self.order = Some(Box::pin({
412
- Self::order(config.clone(), resolver.clone(), account_key)
554
+ Self::order(
555
+ config.clone(),
556
+ resolver.clone(),
557
+ http01_handler,
558
+ http01_enabled,
559
+ account_key,
560
+ )
413
561
  }));
414
562
  }
415
563
  }
416
564
  }
417
565
 
566
+ #[derive(Clone, Copy, Debug)]
567
+ enum ChallengeKind {
568
+ Http01,
569
+ TlsAlpn01,
570
+ }
571
+
572
+ #[derive(Clone, Copy, Debug, Eq, PartialEq)]
573
+ enum AttemptOutcome {
574
+ Validated,
575
+ RetryableFailure,
576
+ Unavailable,
577
+ }
578
+
418
579
  impl<EC: 'static + Debug, EA: 'static + Debug> Stream for AcmeState<EC, EA> {
419
580
  type Item = Event<EC, EA>;
420
581
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-scheduler"
3
- version = "0.2.26"
3
+ version = "0.2.27-rc1"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -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::{call_without_gvl, create_ruby_thread};
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::{Mutex, RwLock};
10
+ use parking_lot::Mutex;
15
11
  use std::{
16
12
  collections::{BinaryHeap, HashMap, VecDeque},
13
+ ffi::CString,
17
14
  os::fd::RawFd,
18
- sync::Arc,
15
+ ptr,
19
16
  time::Duration,
20
17
  };
21
18
  use timer::Timer;
22
- use tracing::{debug, error, info, warn};
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
- ruby: &Ruby,
266
- rself: &Self,
269
+ _ruby: &Ruby,
270
+ _rself: &Self,
267
271
  hostname: String,
268
272
  ) -> MagnusResult<Option<Vec<String>>> {
269
- let result: Option<Vec<String>> = rself.run_blocking_in_thread(ruby, move || {
270
- use std::net::ToSocketAddrs;
271
- let addrs_res = (hostname.as_str(), 0).to_socket_addrs();
272
- match addrs_res {
273
- Ok(addrs) => {
274
- let ips: Vec<String> = addrs.map(|s| s.ip().to_string()).collect();
275
- Some(ips)
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
- Err(_) => None,
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
- "address_resolve",
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))?;
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-server"
3
- version = "0.2.26"
3
+ version = "0.2.27-rc1"
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"
@@ -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",
@@ -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