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    #[cfg(varnishsys_91_sslcafile)]
372    ssl_ca_file: Option<&'a CStr>,
373    probe: Option<&'a VCL_PROBE>,
374}
375
376/// Macro to generate builder setter methods
377macro_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    /// Create a new builder for a TCP/IP backend
390    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    /// Create a new builder for a Unix domain socket backend
412    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    /// Use the `PROXY` protocol v1 to connect to the backend.
494    #[must_use]
495    pub fn proxy_v1(mut self) -> Self {
496        self.proxy_header = Some(1);
497        self
498    }
499
500    /// Use the `PROXY` protocol v2 to connect to the backend.
501    #[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    /// Use TLS for the backend connection.
509    #[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    /// Build the native backend with a VCL. This can be used in cases where there's no [Ctx], like
533    /// in a background thread.
534    ///
535    /// # Safety
536    ///
537    /// The caller must ensure that `vcl` is valid for the duration of this call.
538    ///
539    /// Internally a minimal [`vrt_ctx`] is stack-allocated with only `vcl` set and passed to
540    /// `VRT_new_backend`. This is safe because `VRT_new_backend` only reads ctx during its
541    /// synchronous execution and does not retain the pointer afterward.
542    ///
543    /// We use [`ffi::vcl`] here as `VCL_VCL` isn't [Send], and the function is unsafe anyway.
544    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    /// Build the native backend
557    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        // Validate required fields
569        let endpoint_type = self
570            .endpoint
571            .expect("endpoint must be set before calling build()");
572
573        // Create the endpoint
574        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        // in case of an IP, we need a buffer that'll live until we've passed endpoint to VRT_new_backend
595        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        // Create the backend config
610        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
665// C FFI wrapper functions
666
667unsafe 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            // TODO: we should grow a VSL object
690            // SAFETY: we assume ffi::VSLbt() will not store the pointer to the string's content
691            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
760// CStr is tied to the lifetime of bep, but we only use it for error messages
761impl 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)] // fixme
783unsafe 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()); // FIXME: is this validation needed?
791    validate_vdir(be); // FIXME: is this validation needed?
792
793    match backend.get_response(&mut ctx) {
794        Ok(res) => {
795            // default to HTTP/1.1 200 if the backend didn't provide anything
796            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                    // build a vfp to wrap the VclResponse object if there's something to push
844                    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                        // we don't need to clean vfe.priv1 at the vfp level, the backend will
871                        // do it in wrap_finish
872                        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        // SystemTime->VCL_TIME can fail for times before UNIX_EPOCH. Avoid panicking
899        // across the FFI boundary; leave `*changed` untouched on conversion failure.
900        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    // FIXME: document why htc does not use a different magic number
919    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    // FIXME: shouldn't the ctx magic number be checked? If so, use validate_vrt_ctx()
947    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    // drop the VclResponse
956    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    // FIXME?: should _prev be set to NULL?
966    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}