1use 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#[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 pub fn get_inner(&self) -> &S {
108 &self.inner
109 }
110
111 pub fn vcl_ptr(&self) -> VCL_BACKEND {
114 self.bep
115 }
116
117 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
170pub trait VclBackend<T: VclResponse> {
178 fn get_response(&self, _ctx: &mut Ctx) -> Result<Option<T>, VclError>;
187
188 fn finish(&self, _ctx: &mut Ctx) {}
191
192 fn healthy(&self, _ctx: &mut Ctx) -> (bool, SystemTime) {
194 (true, SystemTime::UNIX_EPOCH)
195 }
196
197 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 fn event(&self, _event: VclEvent) {}
210
211 fn panic(&self, _vsb: &mut Buffer) {}
212
213 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 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#[expect(clippy::len_without_is_empty)] pub trait VclResponse {
252 fn read(&mut self, buf: &mut [u8]) -> Result<usize, VclError>;
259
260 fn len(&self) -> Option<usize> {
264 None
265 }
266
267 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 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
357impl 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)] unsafe 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()); validate_vdir(be); match backend.get_response(&mut ctx) {
391 Ok(res) => {
392 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 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 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(); }
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 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 let ctx = ctxp.as_ref().unwrap();
527 let bo = ctx.bo.as_mut().unwrap();
528
529 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 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#[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}