Skip to main content

varnish_sys/vcl/backend/
backend_main.rs

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