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 probe: Option<&'a VCL_PROBE>,
372}
373
374macro_rules! builder_setter {
376 ($name:ident, $type:ty, $doc:expr) => {
377 #[doc = $doc]
378 #[must_use]
379 pub fn $name(mut self, $name: $type) -> Self {
380 self.$name = Some($name);
381 self
382 }
383 };
384}
385
386impl<'a> NativeBackendBuilder<'a> {
387 pub fn new_ip(vcl_name: &'a CStr, addr: SocketAddr) -> Self {
389 Self {
390 endpoint: Some(BackendEndpoint::Ip(addr)),
391 vcl_name,
392 hosthdr: None,
393 authority: None,
394 connect_timeout: None,
395 first_byte_timeout: None,
396 between_bytes_timeout: None,
397 backend_wait_timeout: None,
398 max_connections: None,
399 proxy_header: None,
400 backend_wait_limit: None,
401 #[cfg(varnishsys_90_sslflags)]
402 sslflags: 0,
403 probe: None,
404 }
405 }
406
407 pub fn new_uds(vcl_name: &'a CStr, path: &'a CStr) -> Self {
409 Self {
410 endpoint: Some(BackendEndpoint::Uds(path)),
411 vcl_name,
412 hosthdr: None,
413 authority: None,
414 connect_timeout: None,
415 first_byte_timeout: None,
416 between_bytes_timeout: None,
417 backend_wait_timeout: None,
418 max_connections: None,
419 proxy_header: None,
420 backend_wait_limit: None,
421 #[cfg(varnishsys_90_sslflags)]
422 sslflags: 0,
423 probe: None,
424 }
425 }
426
427 builder_setter!(
428 authority,
429 &'a CStr,
430 "Set the authority for this backend when connecting with the `PROXY`
431 protocol"
432 );
433
434 builder_setter!(
435 connect_timeout,
436 std::time::Duration,
437 " Set the connection timeout to the backend. Negative will count as 0s.
438 Native backends pool their connections, meaning that connecting may not
439 be necessary for all request."
440 );
441
442 builder_setter!(
443 first_byte_timeout,
444 std::time::Duration,
445 "Set the timeout for the first byte of the backend response."
446 );
447
448 builder_setter!(
449 between_bytes_timeout,
450 std::time::Duration,
451 " Set the timeout for receving each bytes. In pratice, it's like more of
452 a \"between TCP packet\"."
453 );
454
455 builder_setter!(
456 max_connections,
457 u32,
458 "Set the number of connections to pool. If a new connection needs to be
459 created while already at the limit, the request will be queued. See
460 also `backend_wait_limit` and `backend_wait_timeout`."
461 );
462
463 builder_setter!(
464 backend_wait_limit,
465 u32,
466 "Set how many requests can be queue while waiting for a connection. If
467 the queue is full, new request will go directly to `vcl_backend_error`."
468 );
469
470 builder_setter!(
471 backend_wait_timeout,
472 std::time::Duration,
473 "Set the time a request can wait for a connection if `max_connections`
474 is at its maximum."
475 );
476
477 #[cfg(varnishsys_90_sslflags)]
478 builder_setter!(
479 hosthdr,
480 &'a CStr,
481 "Set the Host header to use sending a request that doesn't have a
482 `Host` header. "
483 );
484
485 builder_setter!(probe, &'a VCL_PROBE, "Set the probe for health checks.");
486
487 #[must_use]
489 pub fn proxy_v1(mut self) -> Self {
490 self.proxy_header = Some(1);
491 self
492 }
493
494 #[must_use]
496 pub fn proxy_v2(mut self) -> Self {
497 self.proxy_header = Some(2);
498 self
499 }
500
501 #[cfg(varnishsys_90_sslflags)]
502 #[must_use]
504 pub fn tls(mut self, verify_host: bool, verify_peer: bool) -> Self {
505 self.sslflags |= BSSL_F_ENABLE;
506 if verify_host {
507 self.sslflags |= BSSL_F_VERIFY_HOST;
508 } else {
509 self.sslflags &= !BSSL_F_VERIFY_HOST;
510 }
511 if verify_peer {
512 self.sslflags &= !BSSL_F_NOVERIFY;
513 } else {
514 self.sslflags |= BSSL_F_NOVERIFY;
515 }
516 self
517 }
518
519 pub unsafe fn build_with_vcl(
532 self,
533 vcl: *mut ffi::vcl,
534 ) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
535 let raw_ctx = vrt_ctx {
536 magic: VRT_CTX_MAGIC,
537 vcl: VCL_VCL(vcl),
538 ..Default::default()
539 };
540 self.build_with_raw_ctx(&raw const raw_ctx)
541 }
542
543 pub fn build(
545 self,
546 ctx: &mut Ctx,
547 ) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
548 unsafe { self.build_with_raw_ctx(ctx.raw) }
549 }
550
551 unsafe fn build_with_raw_ctx(
552 self,
553 raw: *const vrt_ctx,
554 ) -> VclResult<Backend<NativeVclBackendShim, NativeVclResponseShim>> {
555 let endpoint_type = self
557 .endpoint
558 .expect("endpoint must be set before calling build()");
559
560 let mut endpoint = Box::new(ffi::vrt_endpoint {
562 magic: ffi::VRT_ENDPOINT_MAGIC,
563 ipv4: VCL_IP(null()),
564 ipv6: VCL_IP(null()),
565 uds_path: null(),
566 preamble: null(),
567 #[cfg(varnishsys_90_sslflags)]
568 hosthdr: match self.hosthdr {
569 Some(s) => s.as_ptr(),
570 None => null(),
571 },
572 #[cfg(varnishsys_90_sslflags)]
573 sslflags: self.sslflags,
574 });
575
576 let mut sa_buf = vec![0u8; vsa_suckaddr_len];
578 match endpoint_type {
579 BackendEndpoint::Uds(path) => {
580 endpoint.uds_path = path.as_ptr();
581 }
582 BackendEndpoint::Ip(addr) => {
583 crate::vcl::convert::write_ip_to_buf(addr, &mut sa_buf);
584 match addr {
585 SocketAddr::V4(_) => endpoint.ipv4 = VCL_IP(sa_buf.as_ptr().cast()),
586 SocketAddr::V6(_) => endpoint.ipv6 = VCL_IP(sa_buf.as_ptr().cast()),
587 }
588 }
589 }
590
591 let backend_config = Box::new(ffi::vrt_backend {
593 magic: ffi::VRT_BACKEND_MAGIC,
594 endpoint: &raw const *endpoint,
595 vcl_name: self.vcl_name.as_ptr(),
596 hosthdr: self.hosthdr.map_or(null(), CStr::as_ptr),
597 authority: self.authority.map_or(null(), CStr::as_ptr),
598 connect_timeout: ffi::vtim_dur(self.connect_timeout.map_or(-1.0, |d| d.as_secs_f64())),
599 first_byte_timeout: ffi::vtim_dur(
600 self.first_byte_timeout.map_or(-1.0, |d| d.as_secs_f64()),
601 ),
602 between_bytes_timeout: ffi::vtim_dur(
603 self.between_bytes_timeout.map_or(-1.0, |d| d.as_secs_f64()),
604 ),
605 backend_wait_timeout: ffi::vtim_dur(
606 self.backend_wait_timeout.map_or(-1.0, |d| d.as_secs_f64()),
607 ),
608 max_connections: self.max_connections.unwrap_or(0),
609 proxy_header: self.proxy_header.unwrap_or(0),
610 backend_wait_limit: self.backend_wait_limit.unwrap_or(0),
611 probe: match self.probe {
612 None => VCL_PROBE(null()),
613 Some(p) => p.to_owned(),
614 },
615 });
616
617 let bep = ffi::VRT_new_backend(
618 raw.cast_mut(),
619 &raw const *backend_config,
620 VCL_BACKEND(null()),
621 );
622
623 if bep.0.is_null() {
624 return Err(format!(
625 "VRT_new_backend returned null for {}",
626 self.vcl_name.to_string_lossy()
627 )
628 .into());
629 }
630
631 let methods = Box::new(ffi::vdi_methods::default());
632
633 let backend_ref =
634 BackendRef::new_without_refcount(bep).expect("Backend pointer should never be null");
635
636 Ok(Backend {
637 methods,
638 inner: Box::new(NativeVclBackendShim),
639 ctype: CString::new("native").expect("\"native\" is a valid C string"),
640 phantom: PhantomData,
641 backend_ref,
642 native_configuration: Some((endpoint, backend_config)),
643 })
644 }
645}
646
647unsafe extern "C" fn vfp_pull<T: VclResponse>(
650 ctxp: *mut ffi::vfp_ctx,
651 vfep: *mut ffi::vfp_entry,
652 ptr: *mut c_void,
653 len: *mut isize,
654) -> VfpStatus {
655 let ctx = validate_vfp_ctx(ctxp);
656 let vfe = validate_vfp_entry(vfep);
657
658 let buf = std::slice::from_raw_parts_mut(ptr.cast::<u8>(), *len as usize);
659 if buf.is_empty() {
660 *len = 0;
661 return VfpStatus::Ok;
662 }
663
664 let reader = vfe
665 .priv1
666 .cast::<T>()
667 .as_mut()
668 .expect("vfp_entry priv1 must not be null during pull");
669 match reader.read(buf) {
670 Err(e) => {
671 let msg = ffi::txt::from_str(e.as_str().as_ref());
674 ffi::VSLbt(
675 ctx.req
676 .as_ref()
677 .expect("req must be set when VFP is active")
678 .vsl,
679 ffi::VslTag::Error,
680 msg,
681 );
682 VfpStatus::Error
683 }
684 Ok(0) => {
685 *len = 0;
686 VfpStatus::End
687 }
688 Ok(l) => {
689 *len = l as isize;
690 VfpStatus::Ok
691 }
692 }
693}
694
695unsafe extern "C" fn wrap_event<S: VclBackend<T>, T: VclResponse>(be: VCL_BACKEND, ev: VclEvent) {
696 let backend: &S = get_backend(validate_director(be));
697 backend.event(ev);
698}
699
700unsafe extern "C" fn wrap_list<S: VclBackend<T>, T: VclResponse>(
701 ctxp: *const vrt_ctx,
702 be: VCL_BACKEND,
703 vsbp: *mut ffi::vsb,
704 detailed: i32,
705 json: i32,
706) {
707 let mut ctx = Ctx::from_ptr(ctxp);
708 let mut vsb = Buffer::from_ptr(vsbp);
709 let backend: &S = get_backend(validate_director(be));
710 match (json != 0, detailed != 0) {
711 (true, true) => backend.report_details_json(&mut ctx, &mut vsb),
712 (true, false) => backend.report_json(&mut ctx, &mut vsb),
713 (false, true) => backend.report_details(&mut ctx, &mut vsb),
714 (false, false) => backend.report(&mut ctx, &mut vsb),
715 }
716}
717
718unsafe extern "C" fn wrap_panic<S: VclBackend<T>, T: VclResponse>(
719 be: VCL_BACKEND,
720 vsbp: *mut ffi::vsb,
721) {
722 let mut vsb = Buffer::from_ptr(vsbp);
723 let backend: &S = get_backend(validate_director(be));
724 backend.panic(&mut vsb);
725}
726
727unsafe extern "C" fn wrap_pipe<S: VclBackend<T>, T: VclResponse>(
728 ctxp: *const vrt_ctx,
729 be: VCL_BACKEND,
730) -> ffi::stream_close_t {
731 let mut ctx = Ctx::from_ptr(ctxp);
732 let req = ctx.raw.validated_req();
733 let sp = req.validated_session();
734 let fd = sp.fd;
735 assert_ne!(fd, 0);
736 let tcp_stream = TcpStream::from_raw_fd(fd);
737
738 let backend: &S = get_backend(validate_director(be));
739 sc_to_ptr(backend.pipe(&mut ctx, tcp_stream))
740}
741
742impl VCL_BACKEND {
744 unsafe fn get_type(&self) -> &str {
745 CStr::from_ptr(
746 self.0
747 .as_ref()
748 .expect("VCL_BACKEND pointer must not be null")
749 .vdir
750 .as_ref()
751 .expect("director vdir must not be null")
752 .methods
753 .as_ref()
754 .expect("director methods must not be null")
755 .type_
756 .as_ref()
757 .expect("director type_ pointer must not be null"),
758 )
759 .to_str()
760 .expect("director type string must be valid UTF-8")
761 }
762}
763
764#[allow(clippy::too_many_lines)] unsafe extern "C" fn wrap_gethdrs<S: VclBackend<T>, T: VclResponse>(
766 ctxp: *const vrt_ctx,
767 bep: VCL_BACKEND,
768) -> c_int {
769 let mut ctx = Ctx::from_ptr(ctxp);
770 let be = validate_director(bep);
771 let backend: &S = get_backend(be);
772 assert!(!be.vcl_name.is_null()); validate_vdir(be); match backend.get_response(&mut ctx) {
776 Ok(res) => {
777 let beresp = ctx
779 .http_beresp
780 .as_mut()
781 .expect("http_beresp must be set during backend gethdrs");
782 if beresp.status().is_none() {
783 beresp.set_status(200);
784 }
785 if beresp.proto().is_none() {
786 if let Err(e) = beresp.set_proto("HTTP/1.1") {
787 ctx.fail(format!("{:?}: {e}", bep.get_type()));
788 return 1;
789 }
790 }
791 let bo = ctx
792 .raw
793 .bo
794 .as_mut()
795 .expect("busyobj must not be null during backend gethdrs");
796 let Some(htc) = ffi::WS_Alloc(bo.ws.as_mut_ptr(), size_of::<ffi::http_conn>() as u32)
797 .cast::<ffi::http_conn>()
798 .as_mut()
799 else {
800 ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
801 return -1;
802 };
803 htc.magic = ffi::HTTP_CONN_MAGIC;
804 htc.doclose = &raw const ffi::SC_REM_CLOSE[0];
805 htc.content_length = 0;
806 match res {
807 None => {
808 htc.body_status = ffi::BS_NONE.as_ptr();
809 }
810 Some(transfer) => {
811 match transfer.len() {
812 None => {
813 htc.body_status = ffi::BS_CHUNKED.as_ptr();
814 htc.content_length = -1;
815 }
816 Some(0) => {
817 htc.body_status = ffi::BS_NONE.as_ptr();
818 }
819 Some(l) => {
820 htc.body_status = ffi::BS_LENGTH.as_ptr();
821 htc.content_length = l as isize;
822 }
823 }
824 htc.priv_ = Box::into_raw(Box::new(transfer)).cast::<c_void>();
825 if htc.body_status != ffi::BS_NONE.as_ptr() {
827 let Some(vfp) =
828 ffi::WS_Alloc(bo.ws.as_mut_ptr(), size_of::<ffi::vfp>() as u32)
829 .cast::<ffi::vfp>()
830 .as_mut()
831 else {
832 ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
833 return -1;
834 };
835 let Ok(t) = Workspace::from_ptr(bo.ws.as_mut_ptr())
836 .copy_bytes_with_null(bep.get_type())
837 else {
838 ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
839 return -1;
840 };
841
842 vfp.name = t.b;
843 vfp.init = None;
844 vfp.pull = Some(vfp_pull::<T>);
845 vfp.fini = None;
846 vfp.priv1 = null();
847
848 let Some(vfe) = ffi::VFP_Push(bo.vfc, vfp).as_mut() else {
849 ctx.fail(format!("{}: couldn't insert vfp", bep.get_type()));
850 return -1;
851 };
852 vfe.priv1 = htc.priv_;
855 }
856 }
857 }
858
859 bo.htc = htc;
860 0
861 }
862 Err(s) => {
863 let typ = bep.get_type();
864 ctx.log(LogTag::FetchError, format!("{typ}: {s}"));
865 1
866 }
867 }
868}
869
870unsafe extern "C" fn wrap_healthy<S: VclBackend<T>, T: VclResponse>(
871 ctxp: *const vrt_ctx,
872 be: VCL_BACKEND,
873 changed: *mut VCL_TIME,
874) -> VCL_BOOL {
875 let backend: &S = get_backend(validate_director(be));
876
877 let mut ctx = Ctx::from_ptr(ctxp);
878 let (healthy, when) = backend.probe(&mut ctx);
879 if !changed.is_null() {
880 if let Ok(t) = when.try_into() {
883 *changed = t;
884 }
885 }
886 healthy.into()
887}
888
889unsafe extern "C" fn wrap_getip<T: VclResponse>(ctxp: *const vrt_ctx, _be: VCL_BACKEND) -> VCL_IP {
890 let ctxp = validate_vrt_ctx(ctxp);
891 let bo = ctxp
892 .bo
893 .as_ref()
894 .expect("busyobj must not be null during getip");
895 assert_eq!(bo.magic, ffi::BUSYOBJ_MAGIC);
896 let htc = bo
897 .htc
898 .as_ref()
899 .expect("http_conn must not be null during getip");
900 assert_eq!(htc.magic, ffi::BUSYOBJ_MAGIC);
902 let transfer = htc
903 .priv_
904 .cast::<T>()
905 .as_ref()
906 .expect("http_conn priv_ must not be null during getip");
907
908 let mut ctx = Ctx::from_ptr(ctxp);
909
910 transfer
911 .get_ip()
912 .and_then(|ip| match ip {
913 Some(ip) => Ok(ip.into_vcl(&mut ctx.ws)?),
914 None => Ok(VCL_IP(null())),
915 })
916 .unwrap_or_else(|e| {
917 ctx.fail(format!("{e}"));
918 VCL_IP(null())
919 })
920}
921
922unsafe extern "C" fn wrap_finish<S: VclBackend<T>, T: VclResponse>(
923 ctxp: *const vrt_ctx,
924 be: VCL_BACKEND,
925) {
926 let prev_backend: &S = get_backend(validate_director(be));
927
928 let ctx = ctxp
930 .as_ref()
931 .expect("vrt_ctx pointer must not be null during backend finish");
932 let bo = ctx
933 .bo
934 .as_mut()
935 .expect("busyobj must not be null during backend finish");
936
937 if let Some(htc) = ptr::replace(&raw mut bo.htc, null_mut()).as_mut() {
939 if let Some(val) = ptr::replace(&raw mut htc.priv_, null_mut())
940 .cast::<T>()
941 .as_mut()
942 {
943 drop(Box::from_raw(val));
944 }
945 }
946
947 prev_backend.finish(&mut Ctx::from_ptr(ctx));
949}
950
951#[cfg(all(test, varnishsys_90_sslflags))]
952mod tests {
953 use std::net::SocketAddr;
954
955 use super::*;
956 use crate::ffi::BSSL_F_ENABLE;
957
958 fn builder() -> NativeBackendBuilder<'static> {
959 let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
960 NativeBackendBuilder::new_ip(c"test", addr)
961 }
962
963 #[test]
964 fn fresh_builder_has_tls_disabled() {
965 assert_eq!(builder().sslflags & BSSL_F_ENABLE, 0);
966 }
967
968 #[test]
969 fn tls_sets_enable_flag_when_verify_all() {
970 let b = builder().tls(true, true);
971 assert_ne!(
972 b.sslflags & BSSL_F_ENABLE,
973 0,
974 "tls(true, true) must set BSSL_F_ENABLE, got sslflags={:#x}",
975 b.sslflags,
976 );
977 }
978
979 #[test]
980 fn tls_sets_enable_flag_when_verify_none() {
981 let b = builder().tls(false, false);
982 assert_ne!(
983 b.sslflags & BSSL_F_ENABLE,
984 0,
985 "tls(false, false) must set BSSL_F_ENABLE, got sslflags={:#x}",
986 b.sslflags,
987 );
988 }
989
990 #[test]
991 fn tls_sets_enable_flag_when_verify_peer_only() {
992 let b = builder().tls(false, true);
993 assert_ne!(
994 b.sslflags & BSSL_F_ENABLE,
995 0,
996 "tls(false, true) must set BSSL_F_ENABLE, got sslflags={:#x}",
997 b.sslflags,
998 );
999 }
1000}