varnish_sys/vcl/
backend.rs

1//! Facilities to create a VMOD backend
2//!
3//! [`VCL_BACKEND`] can be a bit confusing to create and manipulate, notably as they
4//! involve a bunch of structures with different lifetimes and quite a lot of casting. This
5//! module hopes to alleviate those issues by handling the most of them and by offering a more
6//! idiomatic interface centered around vmod objects.
7//!
8//! Here's what's in the toolbox:
9//! - the [`Backend`] type wraps a [`VclBackend`]-implementing struct into a C backend
10//! - the [`VclBackend`] trait defines which methods to implement to act as a backend, and includes
11//!   default implementations for most methods.
12//! - the [`VclResponse`] trait provides a way to generate a response body,notably handling the
13//!   transfer-encoding for you.
14//!
15//! Note: You can check out the [example/vmod_be
16//! code](https://github.com/varnish-rs/varnish-rs/blob/main/examples/vmod_be/src/lib.rs) for a
17//! fully commented vmod.
18//!
19//! For a very simple example, let's build a backend that just replies with 'A' a predetermined
20//! number of times.
21//!
22//! ```
23//! # mod varnish { pub use varnish_sys::vcl; }
24//! use varnish::vcl::{Ctx, Backend, VclBackend, VclResponse, VclError};
25//!
26//! // First we need to define a struct that implement `VclResponse`:
27//! struct BodyResponse {
28//!     left: usize,
29//! }
30//!
31//! impl VclResponse for BodyResponse {
32//!     fn read(&mut self, buf: &mut [u8]) -> Result<usize, VclError> {
33//!         let mut done = 0;
34//!         for p in buf {
35//!              if self.left == 0 {
36//!                  break;
37//!              }
38//!              *p = 'A' as u8;
39//!              done += 1;
40//!              self.left -= 1;
41//!         }
42//!         Ok(done)
43//!     }
44//! }
45//!
46//! // Then, we need a struct implementing `VclBackend` to build the headers and return a BodyResponse
47//! // Here, MyBe only needs to know how many times to repeat the character
48//! struct MyBe {
49//!     n: usize
50//! }
51//!
52//! impl VclBackend<BodyResponse> for MyBe {
53//!      fn get_response(&self, ctx: &mut Ctx) -> Result<Option<BodyResponse>, VclError> {
54//!          Ok(Some(
55//!            BodyResponse { left: self.n },
56//!          ))
57//!      }
58//! }
59//!
60//! // Finally, we create a `Backend` wrapping a `MyBe`, and we can ask for a pointer to give to the C
61//! // layers.
62//! fn some_vmod_function(ctx: &mut Ctx) {
63//!     let backend = Backend::new(ctx, "Arepeater", "repeat42", MyBe { n: 42 }, false).expect("couldn't create the backend");
64//!     let ptr = backend.vcl_ptr();
65//! }
66//! ```
67use std::ffi::{c_char, c_int, c_void, CStr, CString};
68use std::marker::PhantomData;
69use std::mem::size_of;
70use std::net::{SocketAddr, TcpStream};
71use std::os::unix::io::FromRawFd;
72use std::ptr;
73use std::ptr::{null, null_mut};
74use std::time::SystemTime;
75
76use crate::ffi::{VclEvent, VfpStatus, VCL_BACKEND, VCL_BOOL, VCL_IP, VCL_TIME};
77use crate::utils::get_backend;
78use crate::vcl::{Buffer, Ctx, IntoVCL, LogTag, VclError, VclResult, Workspace};
79use crate::{
80    ffi, validate_director, validate_vdir, validate_vfp_ctx, validate_vfp_entry, validate_vrt_ctx,
81};
82
83/// Fat wrapper around [`VCL_BACKEND`].
84///
85/// It will handle almost all the necessary boilerplate needed to create a vmod. Most importantly,
86/// it destroys/unregisters the backend as part of it's `Drop` implementation, and
87/// will convert the C methods to something more idiomatic.
88///
89/// Once created, a [`Backend`]'s sole purpose is to exist as a C reference for the VCL. As a
90/// result, you don't want to drop it until after all the transfers are done. The most common way
91/// is just to have the backend be part of a vmod object because the object won't be dropped until
92/// the VCL is discarded and that can only happen once all the backend fetches are done.
93#[derive(Debug)]
94pub struct Backend<S: VclBackend<T>, T: VclResponse> {
95    bep: VCL_BACKEND,
96    #[expect(dead_code)]
97    methods: Box<ffi::vdi_methods>,
98    inner: Box<S>,
99    #[expect(dead_code)]
100    ctype: CString,
101    phantom: PhantomData<T>,
102}
103
104impl<S: VclBackend<T>, T: VclResponse> Backend<S, T> {
105    /// Access the inner type wrapped by [Backend]. Note that it isn't `mut` as other threads are
106    /// likely to have access to it too.
107    pub fn get_inner(&self) -> &S {
108        &self.inner
109    }
110
111    /// Return the C pointer wrapped by the [`Backend`]. Conventionally used by the `.backend()`
112    /// methods of VCL objects.
113    pub fn vcl_ptr(&self) -> VCL_BACKEND {
114        self.bep
115    }
116
117    /// Create a new builder, wrapping the `inner` structure (that implements [`VclBackend`]),
118    /// calling the backend `backend_id`. If the backend has a probe attached to it, set `has_probe` to
119    /// true.
120    pub fn new(
121        ctx: &mut Ctx,
122        backend_type: &str,
123        backend_id: &str,
124        be: S,
125        has_probe: bool,
126    ) -> VclResult<Self> {
127        let mut inner = Box::new(be);
128        let ctype: CString = CString::new(backend_type).map_err(|e| e.to_string())?;
129        let cname: CString = CString::new(backend_id).map_err(|e| e.to_string())?;
130        let methods = Box::new(ffi::vdi_methods {
131            type_: ctype.as_ptr(),
132            magic: ffi::VDI_METHODS_MAGIC,
133            destroy: None,
134            event: Some(wrap_event::<S, T>),
135            finish: Some(wrap_finish::<S, T>),
136            gethdrs: Some(wrap_gethdrs::<S, T>),
137            getip: Some(wrap_getip::<T>),
138            healthy: has_probe.then_some(wrap_healthy::<S, T>),
139            http1pipe: Some(wrap_pipe::<S, T>),
140            list: Some(wrap_list::<S, T>),
141            panic: Some(wrap_panic::<S, T>),
142            resolve: None,
143            release: None,
144        });
145
146        let bep = unsafe {
147            ffi::VRT_AddDirector(
148                ctx.raw,
149                &raw const *methods,
150                ptr::from_mut::<S>(&mut *inner).cast::<c_void>(),
151                c"%.*s".as_ptr(),
152                cname.as_bytes().len(),
153                cname.as_ptr().cast::<c_char>(),
154            )
155        };
156        if bep.0.is_null() {
157            return Err(format!("VRT_AddDirector return null while creating {backend_id}").into());
158        }
159
160        Ok(Backend {
161            bep,
162            ctype,
163            inner,
164            methods,
165            phantom: PhantomData,
166        })
167    }
168}
169
170/// The trait to implement to "be" a backend
171///
172/// [`VclBackend`] maps to the `vdi_methods` structure of the C api, but presented in a more
173/// "rusty" form. Apart from [`VclBackend::get_response`] all methods are optional.
174///
175/// If your backend doesn't return any content body, you can implement `VclBackend<()>` as `()` has a default
176/// [`VclResponse`] implementation.
177pub trait VclBackend<T: VclResponse> {
178    /// If the VCL pick this backend (or a director ended up choosing it), this method gets called
179    /// so that the [`VclBackend`] implementer can:
180    /// - inspect the request headers (`ctx.http_bereq`)
181    /// - fill the response headers (`ctx.http_beresp`)
182    /// - possibly return a [`VclResponse`] object that will generate the response body
183    ///
184    /// If this function returns a `Ok(_)` without having set the method and protocol of
185    /// `ctx.http_beresp`, we'll default to `HTTP/1.1 200 OK`
186    fn get_response(&self, _ctx: &mut Ctx) -> Result<Option<T>, VclError>;
187
188    /// Once a backend transaction is finished, the [`Backend`] has a chance to clean up, collect
189    /// data and others in the finish methods.
190    fn finish(&self, _ctx: &mut Ctx) {}
191
192    /// Is your backend healthy, and when did its health change for the last time.
193    fn healthy(&self, _ctx: &mut Ctx) -> (bool, SystemTime) {
194        (true, SystemTime::UNIX_EPOCH)
195    }
196
197    /// If your backend is used inside `vcl_pipe`, this method is in charge of sending the request
198    /// headers that Varnish already read, and then the body. The second argument, a `TcpStream` is
199    /// the raw client stream that Varnish was using (converted from a raw fd).
200    ///
201    /// Once done, you should return a `StreamClose` describing how/why the transaction ended.
202    fn pipe(&self, ctx: &mut Ctx, _tcp_stream: TcpStream) -> StreamClose {
203        ctx.log(LogTag::Error, "Backend does not support pipe");
204        StreamClose::TxError
205    }
206
207    /// The method will get called when the VCL changes temperature or is discarded. It's notably a
208    /// chance to start/stop probes to consume fewer resources.
209    fn event(&self, _event: VclEvent) {}
210
211    fn panic(&self, _vsb: &mut Buffer) {}
212
213    /// Convenience function for the implementors to call if they don't have a probe. This one is
214    /// not used by Varnish directly.
215    fn list_without_probe(&self, ctx: &mut Ctx, vsb: &mut Buffer, detailed: bool, json: bool) {
216        if detailed {
217            return;
218        }
219        let state = if self.healthy(ctx).0 {
220            "healthy"
221        } else {
222            "sick"
223        };
224        if json {
225            vsb.write(&"[0, 0, ").unwrap();
226            vsb.write(&state).unwrap();
227            vsb.write(&"]").unwrap();
228        } else {
229            vsb.write(&"0/0\t").unwrap();
230            vsb.write(&state).unwrap();
231        }
232    }
233
234    /// Used to generate the output of `varnishadm backend.list`. `detailed` means the `-p`
235    /// argument was passed and `json` means `-j` was passed.
236    fn list(&self, ctx: &mut Ctx, vsb: &mut Buffer, detailed: bool, json: bool) {
237        self.list_without_probe(ctx, vsb, detailed, json);
238    }
239}
240
241/// An in-flight response body
242///
243/// When [`VclBackend::get_response`] get called, the backend [`Backend`] can return a
244/// `Result<Option<VclResponse>>`:
245/// - `Err(_)`: something went wrong, the error will be logged and synthetic backend response will be
246///   generated by Varnish
247/// - `Ok(None)`: headers are set, but the response as no content body.
248/// - `Ok(Some(VclResponse))`: headers are set, and Varnish will use the [`VclResponse`] object to build
249///   the response body.
250#[expect(clippy::len_without_is_empty)] // FIXME: should there be an is_empty() method?
251pub trait VclResponse {
252    /// The only mandatory method, it will be called repeated so that the [`VclResponse`] object can
253    /// fill `buf`. The transfer will stop if any of its calls returns an error, and it will
254    /// complete successfully when `Ok(0)` is returned.
255    ///
256    /// `.read()` will never be called on an empty buffer, and the implementer must return the
257    /// number of bytes written (which therefore must be less than the buffer size).
258    fn read(&mut self, buf: &mut [u8]) -> Result<usize, VclError>;
259
260    /// If returning `Some(_)`, we know the size of the body generated, and it'll be used to fill the
261    /// `content-length` header of the response. Otherwise, chunked encoding will be used, which is
262    /// what's assumed by default.
263    fn len(&self) -> Option<usize> {
264        None
265    }
266
267    /// Potentially return the IP:port pair that the backend is using to transfer the body. It
268    /// might not make sense for your implementation.
269    fn get_ip(&self) -> Result<Option<SocketAddr>, VclError> {
270        Ok(None)
271    }
272}
273
274impl VclResponse for () {
275    fn read(&mut self, _buf: &mut [u8]) -> Result<usize, VclError> {
276        Ok(0)
277    }
278}
279
280unsafe extern "C" fn vfp_pull<T: VclResponse>(
281    ctxp: *mut ffi::vfp_ctx,
282    vfep: *mut ffi::vfp_entry,
283    ptr: *mut c_void,
284    len: *mut isize,
285) -> VfpStatus {
286    let ctx = validate_vfp_ctx(ctxp);
287    let vfe = validate_vfp_entry(vfep);
288
289    let buf = std::slice::from_raw_parts_mut(ptr.cast::<u8>(), *len as usize);
290    if buf.is_empty() {
291        *len = 0;
292        return VfpStatus::Ok;
293    }
294
295    let reader = vfe.priv1.cast::<T>().as_mut().unwrap();
296    match reader.read(buf) {
297        Err(e) => {
298            // TODO: we should grow a VSL object
299            // SAFETY: we assume ffi::VSLbt() will not store the pointer to the string's content
300            let msg = ffi::txt::from_str(e.as_str().as_ref());
301            ffi::VSLbt(ctx.req.as_ref().unwrap().vsl, ffi::VslTag::Error, msg);
302            VfpStatus::Error
303        }
304        Ok(0) => {
305            *len = 0;
306            VfpStatus::End
307        }
308        Ok(l) => {
309            *len = l as isize;
310            VfpStatus::Ok
311        }
312    }
313}
314
315unsafe extern "C" fn wrap_event<S: VclBackend<T>, T: VclResponse>(be: VCL_BACKEND, ev: VclEvent) {
316    let backend: &S = get_backend(validate_director(be));
317    backend.event(ev);
318}
319
320unsafe extern "C" fn wrap_list<S: VclBackend<T>, T: VclResponse>(
321    ctxp: *const ffi::vrt_ctx,
322    be: VCL_BACKEND,
323    vsbp: *mut ffi::vsb,
324    detailed: i32,
325    json: i32,
326) {
327    let mut ctx = Ctx::from_ptr(ctxp);
328    let mut vsb = Buffer::from_ptr(vsbp);
329    let backend: &S = get_backend(validate_director(be));
330    backend.list(&mut ctx, &mut vsb, detailed != 0, json != 0);
331}
332
333unsafe extern "C" fn wrap_panic<S: VclBackend<T>, T: VclResponse>(
334    be: VCL_BACKEND,
335    vsbp: *mut ffi::vsb,
336) {
337    let mut vsb = Buffer::from_ptr(vsbp);
338    let backend: &S = get_backend(validate_director(be));
339    backend.panic(&mut vsb);
340}
341
342unsafe extern "C" fn wrap_pipe<S: VclBackend<T>, T: VclResponse>(
343    ctxp: *const ffi::vrt_ctx,
344    be: VCL_BACKEND,
345) -> ffi::stream_close_t {
346    let mut ctx = Ctx::from_ptr(ctxp);
347    let req = ctx.raw.validated_req();
348    let sp = req.validated_session();
349    let fd = sp.fd;
350    assert_ne!(fd, 0);
351    let tcp_stream = TcpStream::from_raw_fd(fd);
352
353    let backend: &S = get_backend(validate_director(be));
354    sc_to_ptr(backend.pipe(&mut ctx, tcp_stream))
355}
356
357// CStr is tied to the lifetime of bep, but we only use it for error messages
358impl VCL_BACKEND {
359    unsafe fn get_type(&self) -> &str {
360        CStr::from_ptr(
361            self.0
362                .as_ref()
363                .unwrap()
364                .vdir
365                .as_ref()
366                .unwrap()
367                .methods
368                .as_ref()
369                .unwrap()
370                .type_
371                .as_ref()
372                .unwrap(),
373        )
374        .to_str()
375        .unwrap()
376    }
377}
378
379#[allow(clippy::too_many_lines)] // fixme
380unsafe extern "C" fn wrap_gethdrs<S: VclBackend<T>, T: VclResponse>(
381    ctxp: *const ffi::vrt_ctx,
382    bep: VCL_BACKEND,
383) -> c_int {
384    let mut ctx = Ctx::from_ptr(ctxp);
385    let be = validate_director(bep);
386    let backend: &S = get_backend(be);
387    assert!(!be.vcl_name.is_null()); // FIXME: is this validation needed?
388    validate_vdir(be); // FIXME: is this validation needed?
389
390    match backend.get_response(&mut ctx) {
391        Ok(res) => {
392            // default to HTTP/1.1 200 if the backend didn't provide anything
393            let beresp = ctx.http_beresp.as_mut().unwrap();
394            if beresp.status().is_none() {
395                beresp.set_status(200);
396            }
397            if beresp.proto().is_none() {
398                if let Err(e) = beresp.set_proto("HTTP/1.1") {
399                    ctx.fail(format!("{:?}: {e}", bep.get_type()));
400                    return 1;
401                }
402            }
403            let bo = ctx.raw.bo.as_mut().unwrap();
404            let Some(htc) = ffi::WS_Alloc(bo.ws.as_mut_ptr(), size_of::<ffi::http_conn>() as u32)
405                .cast::<ffi::http_conn>()
406                .as_mut()
407            else {
408                ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
409                return -1;
410            };
411            htc.magic = ffi::HTTP_CONN_MAGIC;
412            htc.doclose = &raw const ffi::SC_REM_CLOSE[0];
413            htc.content_length = 0;
414            match res {
415                None => {
416                    htc.body_status = ffi::BS_NONE.as_ptr();
417                }
418                Some(transfer) => {
419                    match transfer.len() {
420                        None => {
421                            htc.body_status = ffi::BS_CHUNKED.as_ptr();
422                            htc.content_length = -1;
423                        }
424                        Some(0) => {
425                            htc.body_status = ffi::BS_NONE.as_ptr();
426                        }
427                        Some(l) => {
428                            htc.body_status = ffi::BS_LENGTH.as_ptr();
429                            htc.content_length = l as isize;
430                        }
431                    }
432                    htc.priv_ = Box::into_raw(Box::new(transfer)).cast::<c_void>();
433                    // build a vfp to wrap the VclResponse object if there's something to push
434                    if htc.body_status != ffi::BS_NONE.as_ptr() {
435                        let Some(vfp) =
436                            ffi::WS_Alloc(bo.ws.as_mut_ptr(), size_of::<ffi::vfp>() as u32)
437                                .cast::<ffi::vfp>()
438                                .as_mut()
439                        else {
440                            ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
441                            return -1;
442                        };
443                        let Ok(t) = Workspace::from_ptr(bo.ws.as_mut_ptr())
444                            .copy_bytes_with_null(bep.get_type())
445                        else {
446                            ctx.fail(format!("{}: insufficient workspace", bep.get_type()));
447                            return -1;
448                        };
449
450                        vfp.name = t.b;
451                        vfp.init = None;
452                        vfp.pull = Some(vfp_pull::<T>);
453                        vfp.fini = None;
454                        vfp.priv1 = null();
455
456                        let Some(vfe) = ffi::VFP_Push(bo.vfc, vfp).as_mut() else {
457                            ctx.fail(format!("{}: couldn't insert vfp", bep.get_type()));
458                            return -1;
459                        };
460                        // we don't need to clean vfe.priv1 at the vfp level, the backend will
461                        // do it in wrap_finish
462                        vfe.priv1 = htc.priv_;
463                    }
464                }
465            }
466
467            bo.htc = htc;
468            0
469        }
470        Err(s) => {
471            let typ = bep.get_type();
472            ctx.log(LogTag::FetchError, format!("{typ}: {s}"));
473            1
474        }
475    }
476}
477
478unsafe extern "C" fn wrap_healthy<S: VclBackend<T>, T: VclResponse>(
479    ctxp: *const ffi::vrt_ctx,
480    be: VCL_BACKEND,
481    changed: *mut VCL_TIME,
482) -> VCL_BOOL {
483    let backend: &S = get_backend(validate_director(be));
484
485    let mut ctx = Ctx::from_ptr(ctxp);
486    let (healthy, when) = backend.healthy(&mut ctx);
487    if !changed.is_null() {
488        *changed = when.try_into().unwrap(); // FIXME: on error?
489    }
490    healthy.into()
491}
492
493unsafe extern "C" fn wrap_getip<T: VclResponse>(
494    ctxp: *const ffi::vrt_ctx,
495    _be: VCL_BACKEND,
496) -> VCL_IP {
497    let ctxp = validate_vrt_ctx(ctxp);
498    let bo = ctxp.bo.as_ref().unwrap();
499    assert_eq!(bo.magic, ffi::BUSYOBJ_MAGIC);
500    let htc = bo.htc.as_ref().unwrap();
501    // FIXME: document why htc does not use a different magic number
502    assert_eq!(htc.magic, ffi::BUSYOBJ_MAGIC);
503    let transfer = htc.priv_.cast::<T>().as_ref().unwrap();
504
505    let mut ctx = Ctx::from_ptr(ctxp);
506
507    transfer
508        .get_ip()
509        .and_then(|ip| match ip {
510            Some(ip) => Ok(ip.into_vcl(&mut ctx.ws)?),
511            None => Ok(VCL_IP(null())),
512        })
513        .unwrap_or_else(|e| {
514            ctx.fail(format!("{e}"));
515            VCL_IP(null())
516        })
517}
518
519unsafe extern "C" fn wrap_finish<S: VclBackend<T>, T: VclResponse>(
520    ctxp: *const ffi::vrt_ctx,
521    be: VCL_BACKEND,
522) {
523    let prev_backend: &S = get_backend(validate_director(be));
524
525    // FIXME: shouldn't the ctx magic number be checked? If so, use validate_vrt_ctx()
526    let ctx = ctxp.as_ref().unwrap();
527    let bo = ctx.bo.as_mut().unwrap();
528
529    // drop the VclResponse
530    if let Some(htc) = ptr::replace(&raw mut bo.htc, null_mut()).as_mut() {
531        if let Some(val) = ptr::replace(&raw mut htc.priv_, null_mut())
532            .cast::<T>()
533            .as_mut()
534        {
535            drop(Box::from_raw(val));
536        }
537    }
538
539    // FIXME?: should _prev be set to NULL?
540    prev_backend.finish(&mut Ctx::from_ptr(ctx));
541}
542
543impl<S: VclBackend<T>, T: VclResponse> Drop for Backend<S, T> {
544    fn drop(&mut self) {
545        unsafe {
546            ffi::VRT_DelDirector(&raw mut self.bep);
547        };
548    }
549}
550
551/// Return type for [`VclBackend::pipe`]
552///
553/// When piping a response, the backend is in charge of closing the file descriptor (which is done
554/// automatically by the rust layer), but also to provide how/why it got closed.
555#[derive(Debug, Clone, Copy)]
556pub enum StreamClose {
557    RemClose,
558    ReqClose,
559    ReqHttp10,
560    RxBad,
561    RxBody,
562    RxJunk,
563    RxOverflow,
564    RxTimeout,
565    RxCloseIdle,
566    TxPipe,
567    TxError,
568    TxEof,
569    RespClose,
570    Overload,
571    PipeOverflow,
572    RangeShort,
573    ReqHttp20,
574    VclFailure,
575}
576
577fn sc_to_ptr(sc: StreamClose) -> ffi::stream_close_t {
578    unsafe {
579        match sc {
580            StreamClose::RemClose => ffi::SC_REM_CLOSE.as_ptr(),
581            StreamClose::ReqClose => ffi::SC_REQ_CLOSE.as_ptr(),
582            StreamClose::ReqHttp10 => ffi::SC_REQ_HTTP10.as_ptr(),
583            StreamClose::RxBad => ffi::SC_RX_BAD.as_ptr(),
584            StreamClose::RxBody => ffi::SC_RX_BODY.as_ptr(),
585            StreamClose::RxJunk => ffi::SC_RX_JUNK.as_ptr(),
586            StreamClose::RxOverflow => ffi::SC_RX_OVERFLOW.as_ptr(),
587            StreamClose::RxTimeout => ffi::SC_RX_TIMEOUT.as_ptr(),
588            StreamClose::RxCloseIdle => ffi::SC_RX_CLOSE_IDLE.as_ptr(),
589            StreamClose::TxPipe => ffi::SC_TX_PIPE.as_ptr(),
590            StreamClose::TxError => ffi::SC_TX_ERROR.as_ptr(),
591            StreamClose::TxEof => ffi::SC_TX_EOF.as_ptr(),
592            StreamClose::RespClose => ffi::SC_RESP_CLOSE.as_ptr(),
593            StreamClose::Overload => ffi::SC_OVERLOAD.as_ptr(),
594            StreamClose::PipeOverflow => ffi::SC_PIPE_OVERFLOW.as_ptr(),
595            StreamClose::RangeShort => ffi::SC_RANGE_SHORT.as_ptr(),
596            StreamClose::ReqHttp20 => ffi::SC_REQ_HTTP20.as_ptr(),
597            StreamClose::VclFailure => ffi::SC_VCL_FAILURE.as_ptr(),
598        }
599    }
600}