Skip to main content

varnish_sys/vcl/backend/
backend_main.rs

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/// Placeholder backend implementation for native Varnish backends.
27///
28/// This type exists only to satisfy the trait bounds for `Backend<S, T>` when
29/// wrapping native backends. None of its methods should ever be called.
30#[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
39/// `NativeBackend` can be created by a [`NativeBackendBuilder`] to implement IP or UDS backends.
40///
41/// Once created, you will generated only use it to create a [`BackendRef`] to return to the VCL.
42///
43pub type NativeBackend = Backend<NativeVclBackendShim, NativeVclResponseShim>;
44/// Placeholder response implementation for native Varnish backends.
45///
46/// This type exists only to satisfy the trait bounds for `Backend<S, T>` when
47/// wrapping native backends. None of its methods should ever be called.
48#[derive(Debug)]
49pub struct NativeVclResponseShim;
50
51impl VclResponse for NativeVclResponseShim {}
52
53/// Fat wrapper around [`VCL_BACKEND`].
54///
55/// It will handle almost all the necessary boilerplate needed to create a custom backend. Most importantly,
56/// it destroys/unregisters the backend as part of it's `Drop` implementation, and
57/// will convert the C methods to something more idiomatic.
58///
59/// Once created, a [`Backend`]'s sole purpose is to exist as a C reference for the VCL. As a
60/// result, you don't want to drop it until after all the transfers are done. The most common way
61/// is just to have the backend be part of a vmod object because the object won't be dropped until
62/// the VCL is discarded and that can only happen once all the backend fetches are done.
63#[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    /// Access the inner type wrapped by [Backend]. Note that it isn't `mut` as other threads are
78    /// likely to have access to it too.
79    pub fn get_inner(&self) -> &S {
80        &self.inner
81    }
82
83    /// Create a new builder, wrapping the `inner` structure (that implements [`VclBackend`]),
84    /// calling the backend `backend_id`. If the backend has a probe attached to it, set `has_probe` to
85    /// true.
86    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
141/// The trait to implement to "be" a backend
142///
143/// [`VclBackend`] maps to the `vdi_methods` structure of the C api, but presented in a more
144/// "rusty" form. Apart from [`VclBackend::get_response`] all methods are optional.
145///
146/// If your backend doesn't return any content body, you can implement `VclBackend<()>` as `()` has a default
147/// [`VclResponse`] implementation.
148pub trait VclBackend<T: VclResponse> {
149    /// If the VCL pick this backend (or a director ended up choosing it), this method gets called
150    /// so that the [`VclBackend`] implementer can:
151    /// - inspect the request headers (`ctx.http_bereq`)
152    /// - fill the response headers (`ctx.http_beresp`)
153    /// - possibly return a [`VclResponse`] object that will generate the response body
154    ///
155    /// If this function returns a `Ok(_)` without having set the method and protocol of
156    /// `ctx.http_beresp`, we'll default to `HTTP/1.1 200 OK`
157    fn get_response(&self, _ctx: &mut Ctx) -> Result<Option<T>, VclError>;
158
159    /// Once a backend transaction is finished, the [`Backend`] has a chance to clean up, collect
160    /// data and others in the finish methods.
161    fn finish(&self, _ctx: &mut Ctx) {}
162
163    /// Is your backend healthy, and when did its health change for the last time.
164    fn probe(&self, _ctx: &mut Ctx) -> (bool, SystemTime) {
165        (true, SystemTime::UNIX_EPOCH)
166    }
167
168    /// If your backend is used inside `vcl_pipe`, this method is in charge of sending the request
169    /// headers that Varnish already read, and then the body. The second argument, a `TcpStream` is
170    /// the raw client stream that Varnish was using (converted from a raw fd).
171    ///
172    /// Once done, you should return a `StreamClose` describing how/why the transaction ended.
173    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    /// The method will get called when the VCL changes temperature or is discarded. It's notably a
179    /// chance to start/stop probes to consume fewer resources.
180    fn event(&self, _event: VclEvent) {}
181
182    fn panic(&self, _vsb: &mut Buffer) {}
183
184    /// Generate simple report output for `varnishadm backend.list` (no flags)
185    ///
186    /// Corresponds to the `list` callback in `vdi_methods` when neither `-p` nor `-j` is passed.
187    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    /// Generate detailed report output for `varnishadm backend.list -p`
194    ///
195    /// Corresponds to the `list` callback in `vdi_methods` when `-p` is passed.
196    fn report_details(&self, _ctx: &mut Ctx, _vsb: &mut Buffer) {}
197
198    /// Generate simple JSON report output for `varnishadm backend.list -j`
199    ///
200    /// Corresponds to the `list` callback in `vdi_methods` when `-j` is passed.
201    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    /// Generate detailed JSON report output for `varnishadm backend.list -j -p`
209    ///
210    /// Corresponds to the `list` callback in `vdi_methods` when both `-j` and `-p` are passed.
211    fn report_details_json(&self, _ctx: &mut Ctx, vsb: &mut Buffer) {
212        let _ = vsb.write(&"{}");
213    }
214}
215
216/// An in-flight response body
217///
218/// When [`VclBackend::get_response`] get called, the backend [`Backend`] can return a
219/// `Result<Option<VclResponse>>`:
220/// - `Err(_)`: something went wrong, the error will be logged and synthetic backend response will be
221///   generated by Varnish
222/// - `Ok(None)`: headers are set, but the response as no content body.
223/// - `Ok(Some(VclResponse))`: headers are set, and Varnish will use the [`VclResponse`] object to build
224///   the response body.
225#[expect(clippy::len_without_is_empty)] // FIXME: should there be an is_empty() method?
226pub trait VclResponse {
227    /// The only mandatory method, it will be called repeated so that the [`VclResponse`] object can
228    /// fill `buf`. The transfer will stop if any of its calls returns an error, and it will
229    /// complete successfully when `Ok(0)` is returned.
230    ///
231    /// `.read()` will never be called on an empty buffer, and the implementer must return the
232    /// number of bytes written (which therefore must be less than the buffer size).
233    fn read(&mut self, buf: &mut [u8]) -> Result<usize, VclError> {
234        let _ = buf;
235        Ok(0)
236    }
237
238    /// If returning `Some(_)`, we know the size of the body generated, and it'll be used to fill the
239    /// `content-length` header of the response. Otherwise, chunked encoding will be used, which is
240    /// what's assumed by default.
241    fn len(&self) -> Option<usize> {
242        None
243    }
244
245    /// Potentially return the IP:port pair that the backend is using to transfer the body. It
246    /// might not make sense for your implementation.
247    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/// Return type for [`VclBackend::pipe`]
278///
279/// When piping a response, the backend is in charge of closing the file descriptor (which is done
280/// automatically by the rust layer), but also to provide how/why it got closed.
281#[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/// A native Varnish backend created via `VRT_new_backend()`
329///
330/// It wraps a regular Varnish backend (the kind you'd normally define in VCL)
331/// but created dynamically from Rust code. Unlike custom backends, which allow you
332/// to implement backend logic in Rust, `NativeBackend` creates a standard HTTP/1
333/// backend that connects to a real server.
334///
335/// Use [`NativeBackendBuilder`] to construct instances with a fluent API.
336///
337/// # Example
338///
339/// ```ignore
340/// let backend = NativeBackendBuilder::new_ip(c"my_backend", "127.0.0.1:8080".parse()?)
341///     .connect_timeout(Duration::from_secs(5))
342///     .build(ctx)?;
343///
344/// let backend_ref = backend.as_ref();
345/// ```
346/// Internal enum to store the backend endpoint type
347#[derive(Debug, Clone, Copy)]
348enum BackendEndpoint<'a> {
349    Ip(SocketAddr),
350    Uds(&'a CStr),
351}
352
353/// Builder for creating a [`NativeBackend`]
354///
355/// Provides a fluent interface for configuring and creating native Varnish backends.
356#[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
374/// Macro to generate builder setter methods
375macro_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    /// Create a new builder for a TCP/IP backend
388    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    /// Create a new builder for a Unix domain socket backend
408    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    /// Use the `PROXY` protocol v1 to connect to the backend.
488    #[must_use]
489    pub fn proxy_v1(mut self) -> Self {
490        self.proxy_header = Some(1);
491        self
492    }
493
494    /// Use the `PROXY` protocol v2 to connect to the backend.
495    #[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    /// Use TLS for the backend connection.
503    #[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    /// Build the native backend with a VCL. This can be used in cases where there's no [Ctx], like
520    /// in a background thread.
521    ///
522    /// # Safety
523    ///
524    /// The caller must ensure that `vcl` is valid for the duration of this call.
525    ///
526    /// Internally a minimal [`vrt_ctx`] is stack-allocated with only `vcl` set and passed to
527    /// `VRT_new_backend`. This is safe because `VRT_new_backend` only reads ctx during its
528    /// synchronous execution and does not retain the pointer afterward.
529    ///
530    /// We use [`ffi::vcl`] here as `VCL_VCL` isn't [Send], and the function is unsafe anyway.
531    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    /// Build the native backend
544    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        // Validate required fields
556        let endpoint_type = self
557            .endpoint
558            .expect("endpoint must be set before calling build()");
559
560        // Create the endpoint
561        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        // in case of an IP, we need a buffer that'll live until we've passed endpoint to VRT_new_backend
577        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        // Create the backend config
592        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
647// C FFI wrapper functions
648
649unsafe 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            // TODO: we should grow a VSL object
672            // SAFETY: we assume ffi::VSLbt() will not store the pointer to the string's content
673            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
742// CStr is tied to the lifetime of bep, but we only use it for error messages
743impl 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)] // fixme
765unsafe 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()); // FIXME: is this validation needed?
773    validate_vdir(be); // FIXME: is this validation needed?
774
775    match backend.get_response(&mut ctx) {
776        Ok(res) => {
777            // default to HTTP/1.1 200 if the backend didn't provide anything
778            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                    // build a vfp to wrap the VclResponse object if there's something to push
826                    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                        // we don't need to clean vfe.priv1 at the vfp level, the backend will
853                        // do it in wrap_finish
854                        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        // SystemTime->VCL_TIME can fail for times before UNIX_EPOCH. Avoid panicking
881        // across the FFI boundary; leave `*changed` untouched on conversion failure.
882        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    // FIXME: document why htc does not use a different magic number
901    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    // FIXME: shouldn't the ctx magic number be checked? If so, use validate_vrt_ctx()
929    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    // drop the VclResponse
938    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    // FIXME?: should _prev be set to NULL?
948    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}