1#[cfg(varnishsys_90_sslflags)]
2use std::ffi::c_uint;
3use std::ffi::{c_char, c_int, c_void, CStr, CString};
4use std::marker::PhantomData;
5use std::mem::size_of;
6use std::net::{SocketAddr, TcpStream};
7use std::os::unix::io::FromRawFd;
8use std::ptr;
9use std::ptr::{null, null_mut};
10use std::time::SystemTime;
11
12use crate::ffi::{
13 vrt_ctx, vsa_suckaddr_len, VclEvent, VfpStatus, VCL_BACKEND, VCL_BOOL, VCL_IP, VCL_PROBE,
14 VCL_TIME, VCL_VCL, VRT_CTX_MAGIC,
15};
16#[cfg(varnishsys_90_sslflags)]
17use crate::ffi::{BSSL_F_ENABLE, BSSL_F_NOVERIFY, BSSL_F_VERIFY_HOST};
18use crate::utils::get_backend;
19use crate::vcl::{Buffer, Ctx, IntoVCL, LogTag, VclError, VclResult, Workspace};
20use crate::{
21 ffi, validate_director, validate_vdir, validate_vfp_ctx, validate_vfp_entry, validate_vrt_ctx,
22};
23
24use super::BackendRef;
25
26#[derive(Debug)]
31pub struct NativeVclBackendShim;
32
33impl VclBackend<NativeVclResponseShim> for NativeVclBackendShim {
34 fn get_response(&self, _ctx: &mut Ctx) -> Result<Option<NativeVclResponseShim>, VclError> {
35 Ok(None)
36 }
37}
38
39pub type NativeBackend = Backend<NativeVclBackendShim, NativeVclResponseShim>;
44#[derive(Debug)]
49pub struct NativeVclResponseShim;
50
51impl VclResponse for NativeVclResponseShim {}
52
53#[derive(Debug)]
64pub struct Backend<S: VclBackend<T>, T: VclResponse> {
65 #[expect(dead_code)]
66 methods: Box<ffi::vdi_methods>,
67 inner: Box<S>,
68 #[expect(dead_code)]
69 ctype: CString,
70 phantom: PhantomData<T>,
71 #[allow(clippy::struct_field_names)]
72 backend_ref: BackendRef,
73 native_configuration: Option<(Box<ffi::vrt_endpoint>, Box<ffi::vrt_backend>)>,
74}
75
76impl<S: VclBackend<T>, T: VclResponse> Backend<S, T> {
77 pub fn get_inner(&self) -> &S {
80 &self.inner
81 }
82
83 pub fn new(
87 ctx: &mut Ctx,
88 backend_type: &str,
89 backend_id: &str,
90 be: S,
91 has_probe: bool,
92 ) -> VclResult<Self> {
93 let mut inner = Box::new(be);
94 let ctype: CString = CString::new(backend_type).map_err(|e| e.to_string())?;
95 let cname: CString = CString::new(backend_id).map_err(|e| e.to_string())?;
96 let methods = Box::new(ffi::vdi_methods {
97 type_: ctype.as_ptr(),
98 magic: ffi::VDI_METHODS_MAGIC,
99 destroy: None,
100 event: Some(wrap_event::<S, T>),
101 finish: Some(wrap_finish::<S, T>),
102 gethdrs: Some(wrap_gethdrs::<S, T>),
103 getip: Some(wrap_getip::<T>),
104 healthy: has_probe.then_some(wrap_healthy::<S, T>),
105 http1pipe: Some(wrap_pipe::<S, T>),
106 list: Some(wrap_list::<S, T>),
107 panic: Some(wrap_panic::<S, T>),
108 resolve: None,
109 release: None,
110 });
111
112 let bep = unsafe {
113 ffi::VRT_AddDirector(
114 ctx.raw,
115 &raw const *methods,
116 ptr::from_mut::<S>(&mut *inner).cast::<c_void>(),
117 c"%.*s".as_ptr(),
118 cname.as_bytes().len(),
119 cname.as_ptr().cast::<c_char>(),
120 )
121 };
122 if bep.0.is_null() {
123 return Err(format!("VRT_AddDirector return null while creating {backend_id}").into());
124 }
125
126 let backend_ref = unsafe {
127 BackendRef::new_without_refcount(bep).expect("Backend pointer should never be null")
128 };
129
130 Ok(Backend {
131 ctype,
132 inner,
133 methods,
134 phantom: PhantomData,
135 backend_ref,
136 native_configuration: None,
137 })
138 }
139}
140
141pub trait VclBackend<T: VclResponse> {
149 fn get_response(&self, _ctx: &mut Ctx) -> Result<Option<T>, VclError>;
158
159 fn finish(&self, _ctx: &mut Ctx) {}
162
163 fn probe(&self, _ctx: &mut Ctx) -> (bool, SystemTime) {
165 (true, SystemTime::UNIX_EPOCH)
166 }
167
168 fn pipe(&self, ctx: &mut Ctx, _tcp_stream: TcpStream) -> StreamClose {
174 ctx.log(LogTag::Error, "Backend does not support pipe");
175 StreamClose::TxError
176 }
177
178 fn event(&self, _event: VclEvent) {}
181
182 fn panic(&self, _vsb: &mut Buffer) {}
183
184 fn report(&self, ctx: &mut Ctx, vsb: &mut Buffer) {
188 let state = if self.probe(ctx).0 { "healthy" } else { "sick" };
189 vsb.write(&"0/0\t").expect("VSB write must succeed");
190 vsb.write(&state).expect("VSB write must succeed");
191 }
192
193 fn report_details(&self, _ctx: &mut Ctx, _vsb: &mut Buffer) {}
197
198 fn report_json(&self, ctx: &mut Ctx, vsb: &mut Buffer) {
202 let state = if self.probe(ctx).0 { "healthy" } else { "sick" };
203 vsb.write(&"[0, 0, ").expect("VSB write must succeed");
204 vsb.write(&state).expect("VSB write must succeed");
205 vsb.write(&"]").expect("VSB write must succeed");
206 }
207
208 fn report_details_json(&self, _ctx: &mut Ctx, vsb: &mut Buffer) {
212 let _ = vsb.write(&"{}");
213 }
214}
215
216#[expect(clippy::len_without_is_empty)] pub trait VclResponse {
227 fn read(&mut self, buf: &mut [u8]) -> Result<usize, VclError> {
234 let _ = buf;
235 Ok(0)
236 }
237
238 fn len(&self) -> Option<usize> {
242 None
243 }
244
245 fn get_ip(&self) -> Result<Option<SocketAddr>, VclError> {
248 Ok(None)
249 }
250}
251
252impl VclResponse for () {
253 fn read(&mut self, _buf: &mut [u8]) -> Result<usize, VclError> {
254 Ok(0)
255 }
256}
257
258impl<S: VclBackend<T>, T: VclResponse> Drop for Backend<S, T> {
259 fn drop(&mut self) {
260 unsafe {
261 let mut bep = self.backend_ref.vcl_ptr();
262 if self.native_configuration.is_some() {
263 ffi::VRT_delete_backend(null(), &raw mut bep);
264 } else {
265 ffi::VRT_DelDirector(&raw mut bep);
266 }
267 };
268 }
269}
270
271impl<S: VclBackend<T>, T: VclResponse> AsRef<BackendRef> for Backend<S, T> {
272 fn as_ref(&self) -> &BackendRef {
273 &self.backend_ref
274 }
275}
276
277#[derive(Debug, Clone, Copy)]
282pub enum StreamClose {
283 RemClose,
284 ReqClose,
285 ReqHttp10,
286 RxBad,
287 RxBody,
288 RxJunk,
289 RxOverflow,
290 RxTimeout,
291 RxCloseIdle,
292 TxPipe,
293 TxError,
294 TxEof,
295 RespClose,
296 Overload,
297 PipeOverflow,
298 RangeShort,
299 ReqHttp20,
300 VclFailure,
301}
302
303pub(crate) fn sc_to_ptr(sc: StreamClose) -> ffi::stream_close_t {
304 unsafe {
305 match sc {
306 StreamClose::RemClose => ffi::SC_REM_CLOSE.as_ptr(),
307 StreamClose::ReqClose => ffi::SC_REQ_CLOSE.as_ptr(),
308 StreamClose::ReqHttp10 => ffi::SC_REQ_HTTP10.as_ptr(),
309 StreamClose::RxBad => ffi::SC_RX_BAD.as_ptr(),
310 StreamClose::RxBody => ffi::SC_RX_BODY.as_ptr(),
311 StreamClose::RxJunk => ffi::SC_RX_JUNK.as_ptr(),
312 StreamClose::RxOverflow => ffi::SC_RX_OVERFLOW.as_ptr(),
313 StreamClose::RxTimeout => ffi::SC_RX_TIMEOUT.as_ptr(),
314 StreamClose::RxCloseIdle => ffi::SC_RX_CLOSE_IDLE.as_ptr(),
315 StreamClose::TxPipe => ffi::SC_TX_PIPE.as_ptr(),
316 StreamClose::TxError => ffi::SC_TX_ERROR.as_ptr(),
317 StreamClose::TxEof => ffi::SC_TX_EOF.as_ptr(),
318 StreamClose::RespClose => ffi::SC_RESP_CLOSE.as_ptr(),
319 StreamClose::Overload => ffi::SC_OVERLOAD.as_ptr(),
320 StreamClose::PipeOverflow => ffi::SC_PIPE_OVERFLOW.as_ptr(),
321 StreamClose::RangeShort => ffi::SC_RANGE_SHORT.as_ptr(),
322 StreamClose::ReqHttp20 => ffi::SC_REQ_HTTP20.as_ptr(),
323 StreamClose::VclFailure => ffi::SC_VCL_FAILURE.as_ptr(),
324 }
325 }
326}
327
328#[derive(Debug, Clone, Copy)]
348enum BackendEndpoint<'a> {
349 Ip(SocketAddr),
350 Uds(&'a CStr),
351}
352
353#[derive(Debug)]
357pub struct NativeBackendBuilder<'a> {
358 endpoint: Option<BackendEndpoint<'a>>,
359 vcl_name: &'a CStr,
360 hosthdr: Option<&'a CStr>,
361 authority: Option<&'a CStr>,
362 connect_timeout: Option<std::time::Duration>,
363 first_byte_timeout: Option<std::time::Duration>,
364 between_bytes_timeout: Option<std::time::Duration>,
365 backend_wait_timeout: Option<std::time::Duration>,
366 max_connections: Option<u32>,
367 proxy_header: Option<u32>,
368 backend_wait_limit: Option<u32>,
369 #[cfg(varnishsys_90_sslflags)]
370 sslflags: c_uint,
371 #[cfg(varnishsys_91_sslcafile)]
372 ssl_ca_file: Option<&'a CStr>,
373 probe: Option<&'a VCL_PROBE>,
374}
375
376macro_rules! builder_setter {
378 ($name:ident, $type:ty, $doc:expr) => {
379 #[doc = $doc]
380 #[must_use]
381 pub fn $name(mut self, $name: $type) -> Self {
382 self.$name = Some($name);
383 self
384 }
385 };
386}
387
388impl<'a> NativeBackendBuilder<'a> {
389 pub fn new_ip(vcl_name: &'a CStr, addr: SocketAddr) -> Self {
391 Self {
392 endpoint: Some(BackendEndpoint::Ip(addr)),
393 vcl_name,
394 hosthdr: None,
395 authority: None,
396 connect_timeout: None,
397 first_byte_timeout: None,
398 between_bytes_timeout: None,
399 backend_wait_timeout: None,
400 max_connections: None,
401 proxy_header: None,
402 backend_wait_limit: None,
403 #[cfg(varnishsys_90_sslflags)]
404 sslflags: 0,
405 #[cfg(varnishsys_91_sslcafile)]
406 ssl_ca_file: None,
407 probe: None,
408 }
409 }
410
411 pub fn new_uds(vcl_name: &'a CStr, path: &'a CStr) -> Self {
413 Self {
414 endpoint: Some(BackendEndpoint::Uds(path)),
415 vcl_name,
416 hosthdr: None,
417 authority: None,
418 connect_timeout: None,
419 first_byte_timeout: None,
420 between_bytes_timeout: None,
421 backend_wait_timeout: None,
422 max_connections: None,
423 proxy_header: None,
424 backend_wait_limit: None,
425 #[cfg(varnishsys_90_sslflags)]
426 sslflags: 0,
427 #[cfg(varnishsys_91_sslcafile)]
428 ssl_ca_file: None,
429 probe: None,
430 }
431 }
432
433 builder_setter!(
434 authority,
435 &'a CStr,
436 "Set the authority for this backend when connecting with the `PROXY`
437 protocol"
438 );
439
440 builder_setter!(
441 connect_timeout,
442 std::time::Duration,
443 " Set the connection timeout to the backend. Negative will count as 0s.
444 Native backends pool their connections, meaning that connecting may not
445 be necessary for all request."
446 );
447
448 builder_setter!(
449 first_byte_timeout,
450 std::time::Duration,
451 "Set the timeout for the first byte of the backend response."
452 );
453
454 builder_setter!(
455 between_bytes_timeout,
456 std::time::Duration,
457 " Set the timeout for receving each bytes. In pratice, it's like more of
458 a \"between TCP packet\"."
459 );
460
461 builder_setter!(
462 max_connections,
463 u32,
464 "Set the number of connections to pool. If a new connection needs to be
465 created while already at the limit, the request will be queued. See
466 also `backend_wait_limit` and `backend_wait_timeout`."
467 );
468
469 builder_setter!(
470 backend_wait_limit,
471 u32,
472 "Set how many requests can be queue while waiting for a connection. If
473 the queue is full, new request will go directly to `vcl_backend_error`."
474 );
475
476 builder_setter!(
477 backend_wait_timeout,
478 std::time::Duration,
479 "Set the time a request can wait for a connection if `max_connections`
480 is at its maximum."
481 );
482
483 #[cfg(varnishsys_90_sslflags)]
484 builder_setter!(
485 hosthdr,
486 &'a CStr,
487 "Set the Host header to use sending a request that doesn't have a
488 `Host` header. "
489 );
490
491 builder_setter!(probe, &'a VCL_PROBE, "Set the probe for health checks.");
492
493 #[must_use]
495 pub fn proxy_v1(mut self) -> Self {
496 self.proxy_header = Some(1);
497 self
498 }
499
500 #[must_use]
502 pub fn proxy_v2(mut self) -> Self {
503 self.proxy_header = Some(2);
504 self
505 }
506
507 #[cfg(varnishsys_90_sslflags)]
508 #[must_use]
510 pub fn tls(mut self, verify_host: bool, verify_peer: bool) -> Self {
511 self.sslflags |= BSSL_F_ENABLE;
512 if verify_host {
513 self.sslflags |= BSSL_F_VERIFY_HOST;
514 } else {
515 self.sslflags &= !BSSL_F_VERIFY_HOST;
516 }
517 if verify_peer {
518 self.sslflags &= !BSSL_F_NOVERIFY;
519 } else {
520 self.sslflags |= BSSL_F_NOVERIFY;
521 }
522 self
523 }
524
525 #[cfg(varnishsys_91_sslcafile)]
526 builder_setter!(
527 ssl_ca_file,
528 &'a CStr,
529 "Set the path to a CA certificate file used to verify the backend's TLS certificate."
530 );
531
532 pub unsafe fn build_with_vcl(
545 self,
546 vcl: *mut ffi::vcl,
547 ) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
548 let raw_ctx = vrt_ctx {
549 magic: VRT_CTX_MAGIC,
550 vcl: VCL_VCL(vcl),
551 ..Default::default()
552 };
553 self.build_with_raw_ctx(&raw const raw_ctx)
554 }
555
556 pub fn build(
558 self,
559 ctx: &mut Ctx,
560 ) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
561 unsafe { self.build_with_raw_ctx(ctx.raw) }
562 }
563
564 unsafe fn build_with_raw_ctx(
565 self,
566 raw: *const vrt_ctx,
567 ) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
568 let endpoint_type = self
570 .endpoint
571 .expect("endpoint must be set before calling build()");
572
573 let mut endpoint = Box::new(ffi::vrt_endpoint {
575 magic: ffi::VRT_ENDPOINT_MAGIC,
576 ipv4: VCL_IP(null()),
577 ipv6: VCL_IP(null()),
578 uds_path: null(),
579 preamble: null(),
580 #[cfg(varnishsys_90_sslflags)]
581 hosthdr: match self.hosthdr {
582 Some(s) => s.as_ptr(),
583 None => null(),
584 },
585 #[cfg(varnishsys_90_sslflags)]
586 sslflags: self.sslflags,
587 #[cfg(varnishsys_91_sslcafile)]
588 ssl_ca_file: match self.ssl_ca_file {
589 Some(s) => s.as_ptr(),
590 None => null(),
591 },
592 });
593
594 let mut sa_buf = vec![0u8; vsa_suckaddr_len];
596 match endpoint_type {
597 BackendEndpoint::Uds(path) => {
598 endpoint.uds_path = path.as_ptr();
599 }
600 BackendEndpoint::Ip(addr) => {
601 crate::vcl::convert::write_ip_to_buf(addr, &mut sa_buf);
602 match addr {
603 SocketAddr::V4(_) => endpoint.ipv4 = VCL_IP(sa_buf.as_ptr().cast()),
604 SocketAddr::V6(_) => endpoint.ipv6 = VCL_IP(sa_buf.as_ptr().cast()),
605 }
606 }
607 }
608
609 let backend_config = Box::new(ffi::vrt_backend {
611 magic: ffi::VRT_BACKEND_MAGIC,
612 endpoint: &raw const *endpoint,
613 vcl_name: self.vcl_name.as_ptr(),
614 hosthdr: self.hosthdr.map_or(null(), CStr::as_ptr),
615 authority: self.authority.map_or(null(), CStr::as_ptr),
616 connect_timeout: ffi::vtim_dur(self.connect_timeout.map_or(-1.0, |d| d.as_secs_f64())),
617 first_byte_timeout: ffi::vtim_dur(
618 self.first_byte_timeout.map_or(-1.0, |d| d.as_secs_f64()),
619 ),
620 between_bytes_timeout: ffi::vtim_dur(
621 self.between_bytes_timeout.map_or(-1.0, |d| d.as_secs_f64()),
622 ),
623 backend_wait_timeout: ffi::vtim_dur(
624 self.backend_wait_timeout.map_or(-1.0, |d| d.as_secs_f64()),
625 ),
626 max_connections: self.max_connections.unwrap_or(0),
627 proxy_header: self.proxy_header.unwrap_or(0),
628 backend_wait_limit: self.backend_wait_limit.unwrap_or(0),
629 probe: match self.probe {
630 None => VCL_PROBE(null()),
631 Some(p) => p.to_owned(),
632 },
633 });
634
635 let bep = ffi::VRT_new_backend(
636 raw.cast_mut(),
637 &raw const *backend_config,
638 VCL_BACKEND(null()),
639 );
640
641 if bep.0.is_null() {
642 return Err(format!(
643 "VRT_new_backend returned null for {}",
644 self.vcl_name.to_string_lossy()
645 )
646 .into());
647 }
648
649 let methods = Box::new(ffi::vdi_methods::default());
650
651 let backend_ref =
652 BackendRef::new_without_refcount(bep).expect("Backend pointer should never be null");
653
654 Ok(Backend {
655 methods,
656 inner: Box::new(NativeVclBackendShim),
657 ctype: CString::new("native").expect("\"native\" is a valid C string"),
658 phantom: PhantomData,
659 backend_ref,
660 native_configuration: Some((endpoint, backend_config)),
661 })
662 }
663}
664
665unsafe extern "C" fn vfp_pull<T: VclResponse>(
668 ctxp: *mut ffi::vfp_ctx,
669 vfep: *mut ffi::vfp_entry,
670 ptr: *mut c_void,
671 len: *mut isize,
672) -> VfpStatus {
673 let ctx = validate_vfp_ctx(ctxp);
674 let vfe = validate_vfp_entry(vfep);
675
676 let buf = std::slice::from_raw_parts_mut(ptr.cast::<u8>(), *len as usize);
677 if buf.is_empty() {
678 *len = 0;
679 return VfpStatus::Ok;
680 }
681
682 let reader = vfe
683 .priv1
684 .cast::<T>()
685 .as_mut()
686 .expect("vfp_entry priv1 must not be null during pull");
687 match reader.read(buf) {
688 Err(e) => {
689 let msg = ffi::txt::from_str(e.as_str().as_ref());
692 ffi::VSLbt(
693 ctx.req
694 .as_ref()
695 .expect("req must be set when VFP is active")
696 .vsl,
697 ffi::VslTag::Error,
698 msg,
699 );
700 VfpStatus::Error
701 }
702 Ok(0) => {
703 *len = 0;
704 VfpStatus::End
705 }
706 Ok(l) => {
707 *len = l as isize;
708 VfpStatus::Ok
709 }
710 }
711}
712
713unsafe extern "C" fn wrap_event<S: VclBackend<T>, T: VclResponse>(be: VCL_BACKEND, ev: VclEvent) {
714 let backend: &S = get_backend(validate_director(be));
715 backend.event(ev);
716}
717
718unsafe extern "C" fn wrap_list<S: VclBackend<T>, T: VclResponse>(
719 ctxp: *const vrt_ctx,
720 be: VCL_BACKEND,
721 vsbp: *mut ffi::vsb,
722 detailed: i32,
723 json: i32,
724) {
725 let mut ctx = Ctx::from_ptr(ctxp);
726 let mut vsb = Buffer::from_ptr(vsbp);
727 let backend: &S = get_backend(validate_director(be));
728 match (json != 0, detailed != 0) {
729 (true, true) => backend.report_details_json(&mut ctx, &mut vsb),
730 (true, false) => backend.report_json(&mut ctx, &mut vsb),
731 (false, true) => backend.report_details(&mut ctx, &mut vsb),
732 (false, false) => backend.report(&mut ctx, &mut vsb),
733 }
734}
735
736unsafe extern "C" fn wrap_panic<S: VclBackend<T>, T: VclResponse>(
737 be: VCL_BACKEND,
738 vsbp: *mut ffi::vsb,
739) {
740 let mut vsb = Buffer::from_ptr(vsbp);
741 let backend: &S = get_backend(validate_director(be));
742 backend.panic(&mut vsb);
743}
744
745unsafe extern "C" fn wrap_pipe<S: VclBackend<T>, T: VclResponse>(
746 ctxp: *const vrt_ctx,
747 be: VCL_BACKEND,
748) -> ffi::stream_close_t {
749 let mut ctx = Ctx::from_ptr(ctxp);
750 let req = ctx.raw.validated_req();
751 let sp = req.validated_session();
752 let fd = sp.fd;
753 assert_ne!(fd, 0);
754 let tcp_stream = TcpStream::from_raw_fd(fd);
755
756 let backend: &S = get_backend(validate_director(be));
757 sc_to_ptr(backend.pipe(&mut ctx, tcp_stream))
758}
759
760impl VCL_BACKEND {
762 unsafe fn get_type(&self) -> &str {
763 CStr::from_ptr(
764 self.0
765 .as_ref()
766 .expect("VCL_BACKEND pointer must not be null")
767 .vdir
768 .as_ref()
769 .expect("director vdir must not be null")
770 .methods
771 .as_ref()
772 .expect("director methods must not be null")
773 .type_
774 .as_ref()
775 .expect("director type_ pointer must not be null"),
776 )
777 .to_str()
778 .expect("director type string must be valid UTF-8")
779 }
780}
781
782#[allow(clippy::too_many_lines)] unsafe extern "C" fn wrap_gethdrs<S: VclBackend<T>, T: VclResponse>(
784 ctxp: *const vrt_ctx,
785 bep: VCL_BACKEND,
786) -> c_int {
787 let mut ctx = Ctx::from_ptr(ctxp);
788 let be = validate_director(bep);
789 let backend: &S = get_backend(be);
790 assert!(!be.vcl_name.is_null()); validate_vdir(be); match backend.get_response(&mut ctx) {
794 Ok(res) => {
795 let beresp = ctx
797 .http_beresp
798 .as_mut()
799 .expect("http_beresp must be set during backend gethdrs");
800 if beresp.status().is_none() {
801 beresp.set_status(200);
802 }
803 if beresp.proto().is_none() {
804 if let Err(e) = beresp.set_proto("HTTP/1.1") {
805 ctx.fail(format!("{:?}: {e}", bep.get_type()));
806 return 1;
807 }
808 }
809 let bo = ctx
810 .raw
811 .bo
812 .as_mut()
813 .expect("busyobj must not be null during backend gethdrs");
814 let Some(htc) = ffi::WS_Alloc(bo.ws.as_mut_ptr(), size_of::<ffi::http_conn>() as u32)
815 .cast::<ffi::http_conn>()
816 .as_mut()
817 else {
818 ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
819 return -1;
820 };
821 htc.magic = ffi::HTTP_CONN_MAGIC;
822 htc.doclose = &raw const ffi::SC_REM_CLOSE[0];
823 htc.content_length = 0;
824 match res {
825 None => {
826 htc.body_status = ffi::BS_NONE.as_ptr();
827 }
828 Some(transfer) => {
829 match transfer.len() {
830 None => {
831 htc.body_status = ffi::BS_CHUNKED.as_ptr();
832 htc.content_length = -1;
833 }
834 Some(0) => {
835 htc.body_status = ffi::BS_NONE.as_ptr();
836 }
837 Some(l) => {
838 htc.body_status = ffi::BS_LENGTH.as_ptr();
839 htc.content_length = l as isize;
840 }
841 }
842 htc.priv_ = Box::into_raw(Box::new(transfer)).cast::<c_void>();
843 if htc.body_status != ffi::BS_NONE.as_ptr() {
845 let Some(vfp) =
846 ffi::WS_Alloc(bo.ws.as_mut_ptr(), size_of::<ffi::vfp>() as u32)
847 .cast::<ffi::vfp>()
848 .as_mut()
849 else {
850 ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
851 return -1;
852 };
853 let Ok(t) = Workspace::from_ptr(bo.ws.as_mut_ptr())
854 .copy_bytes_with_null(bep.get_type())
855 else {
856 ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
857 return -1;
858 };
859
860 vfp.name = t.b;
861 vfp.init = None;
862 vfp.pull = Some(vfp_pull::<T>);
863 vfp.fini = None;
864 vfp.priv1 = null();
865
866 let Some(vfe) = ffi::VFP_Push(bo.vfc, vfp).as_mut() else {
867 ctx.fail(format!("{}: couldn't insert vfp", bep.get_type()));
868 return -1;
869 };
870 vfe.priv1 = htc.priv_;
873 }
874 }
875 }
876
877 bo.htc = htc;
878 0
879 }
880 Err(s) => {
881 let typ = bep.get_type();
882 ctx.log(LogTag::FetchError, format!("{typ}: {s}"));
883 1
884 }
885 }
886}
887
888unsafe extern "C" fn wrap_healthy<S: VclBackend<T>, T: VclResponse>(
889 ctxp: *const vrt_ctx,
890 be: VCL_BACKEND,
891 changed: *mut VCL_TIME,
892) -> VCL_BOOL {
893 let backend: &S = get_backend(validate_director(be));
894
895 let mut ctx = Ctx::from_ptr(ctxp);
896 let (healthy, when) = backend.probe(&mut ctx);
897 if !changed.is_null() {
898 if let Ok(t) = when.try_into() {
901 *changed = t;
902 }
903 }
904 healthy.into()
905}
906
907unsafe extern "C" fn wrap_getip<T: VclResponse>(ctxp: *const vrt_ctx, _be: VCL_BACKEND) -> VCL_IP {
908 let ctxp = validate_vrt_ctx(ctxp);
909 let bo = ctxp
910 .bo
911 .as_ref()
912 .expect("busyobj must not be null during getip");
913 assert_eq!(bo.magic, ffi::BUSYOBJ_MAGIC);
914 let htc = bo
915 .htc
916 .as_ref()
917 .expect("http_conn must not be null during getip");
918 assert_eq!(htc.magic, ffi::BUSYOBJ_MAGIC);
920 let transfer = htc
921 .priv_
922 .cast::<T>()
923 .as_ref()
924 .expect("http_conn priv_ must not be null during getip");
925
926 let mut ctx = Ctx::from_ptr(ctxp);
927
928 transfer
929 .get_ip()
930 .and_then(|ip| match ip {
931 Some(ip) => Ok(ip.into_vcl(&mut ctx.ws)?),
932 None => Ok(VCL_IP(null())),
933 })
934 .unwrap_or_else(|e| {
935 ctx.fail(format!("{e}"));
936 VCL_IP(null())
937 })
938}
939
940unsafe extern "C" fn wrap_finish<S: VclBackend<T>, T: VclResponse>(
941 ctxp: *const vrt_ctx,
942 be: VCL_BACKEND,
943) {
944 let prev_backend: &S = get_backend(validate_director(be));
945
946 let ctx = ctxp
948 .as_ref()
949 .expect("vrt_ctx pointer must not be null during backend finish");
950 let bo = ctx
951 .bo
952 .as_mut()
953 .expect("busyobj must not be null during backend finish");
954
955 if let Some(htc) = ptr::replace(&raw mut bo.htc, null_mut()).as_mut() {
957 if let Some(val) = ptr::replace(&raw mut htc.priv_, null_mut())
958 .cast::<T>()
959 .as_mut()
960 {
961 drop(Box::from_raw(val));
962 }
963 }
964
965 prev_backend.finish(&mut Ctx::from_ptr(ctx));
967}
968
969#[cfg(all(test, varnishsys_90_sslflags))]
970mod tests {
971 use std::net::SocketAddr;
972
973 use super::*;
974 use crate::ffi::BSSL_F_ENABLE;
975
976 fn builder() -> NativeBackendBuilder<'static> {
977 let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
978 NativeBackendBuilder::new_ip(c"test", addr)
979 }
980
981 #[test]
982 fn fresh_builder_has_tls_disabled() {
983 assert_eq!(builder().sslflags & BSSL_F_ENABLE, 0);
984 }
985
986 #[test]
987 fn tls_sets_enable_flag_when_verify_all() {
988 let b = builder().tls(true, true);
989 assert_ne!(
990 b.sslflags & BSSL_F_ENABLE,
991 0,
992 "tls(true, true) must set BSSL_F_ENABLE, got sslflags={:#x}",
993 b.sslflags,
994 );
995 }
996
997 #[test]
998 fn tls_sets_enable_flag_when_verify_none() {
999 let b = builder().tls(false, false);
1000 assert_ne!(
1001 b.sslflags & BSSL_F_ENABLE,
1002 0,
1003 "tls(false, false) must set BSSL_F_ENABLE, got sslflags={:#x}",
1004 b.sslflags,
1005 );
1006 }
1007
1008 #[test]
1009 fn tls_sets_enable_flag_when_verify_peer_only() {
1010 let b = builder().tls(false, true);
1011 assert_ne!(
1012 b.sslflags & BSSL_F_ENABLE,
1013 0,
1014 "tls(false, true) must set BSSL_F_ENABLE, got sslflags={:#x}",
1015 b.sslflags,
1016 );
1017 }
1018}