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