1use std::net;
2#[cfg(any(test, all(feature = "uds", unix)))]
3use std::path::PathBuf;
4
5#[cfg(feature = "iroh")]
6use crate::iroh;
7use crate::{Error, QuicBackend};
8use moq_net::Session;
9use url::Url;
10
11#[cfg(any(
14 feature = "noq",
15 feature = "quinn",
16 feature = "quiche",
17 feature = "iroh",
18 feature = "websocket"
19))]
20use futures::FutureExt;
21use futures::future::BoxFuture;
22use futures::stream::FuturesUnordered;
23use futures::stream::StreamExt;
24
25#[derive(clap::Args, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
27#[serde(deny_unknown_fields, default)]
28#[non_exhaustive]
29pub struct ServerConfig {
30 #[serde(alias = "listen")]
38 #[arg(id = "server-bind", long = "server-bind", alias = "listen", env = "MOQ_SERVER_BIND")]
39 pub bind: Option<String>,
40
41 #[cfg(feature = "tcp")]
44 #[command(flatten)]
45 #[serde(default)]
46 pub tcp: crate::tcp::Config,
47
48 #[cfg(all(feature = "uds", unix))]
51 #[command(flatten)]
52 #[serde(default)]
53 pub unix: crate::unix::Config,
54
55 #[arg(id = "server-backend", long = "server-backend", env = "MOQ_SERVER_BACKEND")]
58 pub backend: Option<QuicBackend>,
59
60 #[command(flatten)]
63 #[serde(default)]
64 pub quic: crate::quic::Server,
65
66 #[serde(default, skip_serializing_if = "Vec::is_empty")]
72 #[arg(
73 id = "server-version",
74 long = "server-version",
75 env = "MOQ_SERVER_VERSION",
76 value_parser = crate::version_parser(),
77 )]
78 pub version: Vec<moq_net::Version>,
79
80 #[command(flatten)]
83 #[serde(default)]
84 pub tls: crate::tls::Server,
85}
86
87impl ServerConfig {
88 pub fn init(self) -> crate::Result<Server> {
90 Server::new(self)
91 }
92
93 pub fn versions(&self) -> moq_net::Versions {
95 if self.version.is_empty() {
96 moq_net::Versions::all()
97 } else {
98 moq_net::Versions::from(self.version.clone())
99 }
100 }
101
102 pub fn has_explicit_bind(&self) -> bool {
104 self.bind.is_some() || self.has_stream_listener()
105 }
106
107 #[allow(unused_mut)]
112 fn has_stream_listener(&self) -> bool {
113 let mut has = false;
114 #[cfg(feature = "tcp")]
115 {
116 has |= self.tcp.bind.is_some();
117 }
118 #[cfg(all(feature = "uds", unix))]
119 {
120 has |= self.unix.bind.is_some();
121 }
122 has
123 }
124}
125
126#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
128pub(crate) const DEFAULT_BIND: &str = "[::]:443";
129
130pub struct Server {
136 moq: moq_net::Server,
137 versions: moq_net::Versions,
138 accept: FuturesUnordered<BoxFuture<'static, crate::Result<Request>>>,
139 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
140 streams: StreamListeners,
141 #[cfg(feature = "iroh")]
142 iroh: Option<iroh::Endpoint>,
143 #[cfg(feature = "noq")]
144 noq: Option<crate::noq::NoqServer>,
145 #[cfg(feature = "quinn")]
146 quinn: Option<crate::quinn::QuinnServer>,
147 #[cfg(feature = "quiche")]
148 quiche: Option<crate::quiche::QuicheServer>,
149 #[cfg(feature = "websocket")]
150 websocket: Option<crate::websocket::Listener>,
151}
152
153impl Server {
154 pub fn new(config: ServerConfig) -> crate::Result<Self> {
159 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
162 let backend = config.backend.clone().unwrap_or_else(crate::default_quic_backend);
163
164 let versions = config.versions();
165
166 config.quic.validate()?;
170
171 let build_quic = config.bind.is_some() || !config.has_stream_listener();
172 #[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche")))]
173 if config.bind.is_some() {
174 return Err(Error::NoBackend(
175 "--server-bind requires a noq, quinn, or quiche backend feature",
176 ));
177 }
178
179 if build_quic && !config.tls.root.is_empty() {
180 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
183 let mtls_supported = match backend {
184 #[cfg(feature = "quinn")]
185 QuicBackend::Quinn => true,
186 #[cfg(feature = "noq")]
187 QuicBackend::Noq => true,
188 #[cfg(feature = "quiche")]
189 QuicBackend::Quiche => true,
190 #[allow(unreachable_patterns)]
191 _ => false,
192 };
193 #[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche")))]
194 let mtls_supported = false;
195
196 if !mtls_supported {
197 return Err(Error::MtlsUnsupported);
198 }
199 }
200
201 #[cfg(feature = "noq")]
202 #[allow(unreachable_patterns)]
203 let noq = match backend {
204 QuicBackend::Noq if build_quic => Some(crate::noq::NoqServer::new(config.clone())?),
205 _ => None,
206 };
207
208 #[cfg(feature = "quinn")]
209 #[allow(unreachable_patterns)]
210 let quinn = match backend {
211 QuicBackend::Quinn if build_quic => Some(crate::quinn::QuinnServer::new(config.clone())?),
212 _ => None,
213 };
214
215 #[cfg(feature = "quiche")]
216 let quiche = match backend {
217 QuicBackend::Quiche if build_quic => Some(crate::quiche::QuicheServer::new(config.clone())?),
218 _ => None,
219 };
220
221 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
223 let mut stream_binds = Vec::new();
224 #[cfg(feature = "tcp")]
225 if let Some(addr) = config.tcp.bind {
226 stream_binds.push(StreamBind::Tcp(addr));
227 }
228 #[cfg(all(feature = "uds", unix))]
229 if let Some(path) = config.unix.bind.clone() {
230 stream_binds.push(StreamBind::Unix(path));
231 }
232 #[cfg(all(feature = "uds", unix))]
234 let unix_allow = config.unix.allow.clone().filter(|allow| !allow.is_empty());
235 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
236 let streams = StreamListeners::new(
237 stream_binds,
238 stream_versions(&versions),
239 #[cfg(all(feature = "uds", unix))]
240 unix_allow,
241 );
242
243 Ok(Server {
244 accept: Default::default(),
245 moq: moq_net::Server::new().with_versions(versions.clone()),
246 versions,
247 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
248 streams,
249 #[cfg(feature = "iroh")]
250 iroh: None,
251 #[cfg(feature = "noq")]
252 noq,
253 #[cfg(feature = "quinn")]
254 quinn,
255 #[cfg(feature = "quiche")]
256 quiche,
257 #[cfg(feature = "websocket")]
258 websocket: None,
259 })
260 }
261
262 #[cfg(feature = "websocket")]
268 pub fn with_websocket(mut self, websocket: crate::websocket::Listener) -> Self {
269 self.websocket = Some(websocket);
270 self
271 }
272
273 #[cfg(feature = "iroh")]
275 pub fn with_iroh(mut self, iroh: iroh::Endpoint) -> Self {
276 self.iroh = Some(iroh);
277 self
278 }
279
280 pub fn with_publisher(mut self, publish: impl moq_net::Consume<moq_net::origin::Consumer>) -> Self {
282 self.moq = self.moq.with_publisher(publish);
283 self
284 }
285
286 pub fn with_subscriber(mut self, subscribe: moq_net::origin::Producer) -> Self {
288 self.moq = self.moq.with_subscriber(subscribe);
289 self
290 }
291
292 pub fn with_stats(mut self, stats: moq_net::stats::Session) -> Self {
295 self.moq = self.moq.with_stats(stats);
296 self
297 }
298
299 pub async fn serve_publish(self, origin: moq_net::origin::Consumer) -> crate::Result<()> {
306 self.with_publisher(origin).serve().await
307 }
308
309 pub async fn serve_consume(self, origin: moq_net::origin::Producer) -> crate::Result<()> {
313 self.with_subscriber(origin).serve().await
314 }
315
316 pub async fn serve_both(
323 self,
324 publish: moq_net::origin::Consumer,
325 subscribe: moq_net::origin::Producer,
326 ) -> crate::Result<()> {
327 self.with_publisher(publish).with_subscriber(subscribe).serve().await
328 }
329
330 async fn serve(mut self) -> crate::Result<()> {
334 if let Ok(addr) = self.local_addr() {
335 tracing::info!(%addr, "listening");
336 }
337 while let Some(request) = self.accept().await {
338 tokio::spawn(async move {
339 if let Err(err) = serve_session(request).await {
340 tracing::warn!(%err, "session ended with error");
341 }
342 });
343 }
344 Ok(())
345 }
346
347 pub fn certificates(&self) -> crate::tls::Certificates {
356 #[cfg(feature = "noq")]
357 if let Some(noq) = self.noq.as_ref() {
358 return noq.certificates();
359 }
360 #[cfg(feature = "quinn")]
361 if let Some(quinn) = self.quinn.as_ref() {
362 return quinn.certificates();
363 }
364 #[cfg(feature = "quiche")]
365 if let Some(quiche) = self.quiche.as_ref() {
366 return quiche.certificates();
367 }
368 crate::tls::Certificates::empty()
370 }
371
372 #[cfg(not(any(
373 feature = "noq",
374 feature = "quinn",
375 feature = "quiche",
376 feature = "iroh",
377 feature = "websocket",
378 feature = "tcp",
379 all(feature = "uds", unix)
380 )))]
381 pub async fn accept(&mut self) -> Option<Request> {
385 unreachable!("no transport compiled; enable a QUIC backend, websocket, tcp, or uds feature");
386 }
387
388 pub fn accept_health(&self) -> Vec<crate::accept::Health> {
400 #[allow(unused_mut)]
401 let mut health = Vec::new();
402 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
403 health.extend(self.streams.health.iter().cloned());
404 #[cfg(feature = "websocket")]
405 health.extend(self.websocket.as_ref().map(|ws| ws.accept_health()));
406 health
407 }
408
409 pub async fn listen(&mut self) -> crate::Result<()> {
426 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
427 self.streams.ensure_started(self.moq.clone()).await?;
428 Ok(())
429 }
430
431 #[cfg(any(
442 feature = "noq",
443 feature = "quinn",
444 feature = "quiche",
445 feature = "iroh",
446 feature = "websocket",
447 feature = "tcp",
448 all(feature = "uds", unix)
449 ))]
450 pub async fn accept(&mut self) -> Option<Request> {
451 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
455 if let Err(err) = self.streams.ensure_started(self.moq.clone()).await {
456 tracing::error!(%err, "failed to bind stream listener");
457 return None;
458 }
459
460 loop {
461 #[cfg(feature = "noq")]
463 let noq_accept = async {
464 #[cfg(feature = "noq")]
465 if let Some(noq) = self.noq.as_mut() {
466 return noq.accept().await;
467 }
468 None
469 };
470 #[cfg(not(feature = "noq"))]
471 let noq_accept = async { None::<()> };
472
473 #[cfg(feature = "iroh")]
474 let iroh_accept = async {
475 #[cfg(feature = "iroh")]
476 if let Some(endpoint) = self.iroh.as_mut() {
477 return endpoint.accept().await;
478 }
479 None
480 };
481 #[cfg(not(feature = "iroh"))]
482 let iroh_accept = async { None::<()> };
483
484 #[cfg(feature = "quinn")]
485 let quinn_accept = async {
486 #[cfg(feature = "quinn")]
487 if let Some(quinn) = self.quinn.as_mut() {
488 return quinn.accept().await;
489 }
490 None
491 };
492 #[cfg(not(feature = "quinn"))]
493 let quinn_accept = async { None::<()> };
494
495 #[cfg(feature = "quiche")]
496 let quiche_accept = async {
497 #[cfg(feature = "quiche")]
498 if let Some(quiche) = self.quiche.as_mut() {
499 return quiche.accept().await;
500 }
501 None
502 };
503 #[cfg(not(feature = "quiche"))]
504 let quiche_accept = async { None::<()> };
505
506 #[cfg(feature = "websocket")]
507 let ws_ref = self.websocket.as_ref();
508 #[cfg(feature = "websocket")]
509 let ws_accept = async {
510 match ws_ref {
511 Some(ws) => ws.accept_with_url().await,
512 None => std::future::pending().await,
513 }
514 };
515 #[cfg(not(feature = "websocket"))]
516 let ws_accept = std::future::pending::<Option<crate::Result<()>>>();
517
518 #[allow(unused_variables)]
519 let server = self.moq.clone();
520 #[allow(unused_variables)]
521 let versions = self.versions.clone();
522
523 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
525 let stream_accept = self.streams.recv();
526 #[cfg(not(any(feature = "tcp", all(feature = "uds", unix))))]
527 let stream_accept = std::future::pending::<Option<Request>>();
528
529 tokio::select! {
530 Some(request) = stream_accept => {
531 return Some(request);
532 }
533 Some(_conn) = noq_accept => {
534 #[cfg(feature = "noq")]
535 {
536 let alpns = versions.alpns();
537 self.accept.push(async move {
538 let (session, url, identity) = super::noq::accept(_conn, alpns).await?;
542 let request = server.accept_request(session).await?;
543 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Noq(Box::new(request)) })
544 }.boxed());
545 }
546 }
547 Some(_conn) = quinn_accept => {
548 #[cfg(feature = "quinn")]
549 {
550 let alpns = versions.alpns();
551 self.accept.push(async move {
552 let (session, url, identity) = super::quinn::accept(_conn, alpns).await?;
553 let request = server.accept_request(session).await?;
554 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Quinn(Box::new(request)) })
555 }.boxed());
556 }
557 }
558 Some(_conn) = quiche_accept => {
559 #[cfg(feature = "quiche")]
560 {
561 let alpns = versions.alpns();
562 self.accept.push(async move {
563 let (session, url, identity) = super::quiche::accept(_conn, alpns).await?;
564 let request = server.accept_request(session).await?;
565 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Quiche(Box::new(request)) })
566 }.boxed());
567 }
568 }
569 Some(_conn) = iroh_accept => {
570 #[cfg(feature = "iroh")]
571 self.accept.push(async move {
572 let (session, url, identity) = super::iroh::accept(_conn).await?;
573 let request = server.accept_request(session).await?;
574 Ok(Request { transport: Transport::Iroh, url, identity, kind: RequestKind::Iroh(Box::new(request)) })
575 }.boxed());
576 }
577 Some(_res) = ws_accept => {
578 #[cfg(feature = "websocket")]
579 match _res {
580 Ok((session, url)) => {
581 self.accept.push(async move {
584 let request = server.accept_request(session).await?;
585 Ok(Request { transport: Transport::WebSocket, url: Some(url), identity: None, kind: RequestKind::Qmux(Box::new(request)) })
586 }.boxed());
587 }
588 Err(err) => tracing::debug!(%err, "WebSocket upgrade failed"),
592 }
593 }
594 Some(res) = self.accept.next() => {
595 match res {
596 Ok(session) => return Some(session),
597 Err(err) => tracing::debug!(%err, "failed to accept session"),
598 }
599 }
600 _ = tokio::signal::ctrl_c() => {
601 self.close().await;
602 return None;
603 }
604 }
605 }
606 }
607
608 #[cfg(feature = "iroh")]
610 pub fn iroh_endpoint(&self) -> Option<&iroh::Endpoint> {
611 self.iroh.as_ref()
612 }
613
614 pub fn local_addr(&self) -> crate::Result<net::SocketAddr> {
620 #[cfg(feature = "noq")]
621 if let Some(noq) = self.noq.as_ref() {
622 return Ok(noq.local_addr()?);
623 }
624 #[cfg(feature = "quinn")]
625 if let Some(quinn) = self.quinn.as_ref() {
626 return Ok(quinn.local_addr()?);
627 }
628 #[cfg(feature = "quiche")]
629 if let Some(quiche) = self.quiche.as_ref() {
630 return Ok(quiche.local_addr()?);
631 }
632 Err(Error::NoBackend("no QUIC listener configured"))
634 }
635
636 #[cfg(feature = "websocket")]
639 pub fn websocket_local_addr(&self) -> Option<net::SocketAddr> {
640 self.websocket.as_ref().and_then(|ws| ws.local_addr().ok())
641 }
642
643 pub async fn close(&mut self) {
648 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
649 self.streams.close().await;
650 #[cfg(feature = "noq")]
651 if let Some(noq) = self.noq.as_mut() {
652 noq.close();
653 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
654 }
655 #[cfg(feature = "quinn")]
656 if let Some(quinn) = self.quinn.as_mut() {
657 quinn.close();
658 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
659 }
660 #[cfg(feature = "quiche")]
661 if let Some(quiche) = self.quiche.as_mut() {
662 quiche.close();
663 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
664 }
665 #[cfg(feature = "iroh")]
666 if let Some(iroh) = self.iroh.take() {
667 iroh.close().await;
668 }
669 #[cfg(feature = "websocket")]
670 {
671 let _ = self.websocket.take();
672 }
673 }
674}
675
676async fn serve_session(request: Request) -> crate::Result<()> {
678 let session = request.ok().await?;
679 Err(session.closed().await.into())
680}
681
682#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
688fn stream_versions(base: &moq_net::Versions) -> moq_net::Versions {
689 let mut versions: Vec<moq_net::Version> = base.iter().copied().collect();
690 if let Ok(lite05) = "moq-lite-05".parse::<moq_net::Version>()
691 && !versions.contains(&lite05)
692 {
693 versions.push(lite05);
694 }
695 moq_net::Versions::from(versions)
696}
697
698#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
700#[derive(Clone)]
701enum StreamBind {
702 #[cfg(feature = "tcp")]
703 Tcp(net::SocketAddr),
704 #[cfg(all(feature = "uds", unix))]
705 Unix(PathBuf),
706}
707
708#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
709impl StreamBind {
710 fn name(&self) -> &'static str {
712 match self {
713 #[cfg(feature = "tcp")]
714 Self::Tcp(_) => "tcp",
715 #[cfg(all(feature = "uds", unix))]
716 Self::Unix(_) => "unix",
717 }
718 }
719}
720
721#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
728struct StreamListeners {
729 binds: Vec<StreamBind>,
730 health: Vec<crate::accept::Health>,
734 versions: moq_net::Versions,
735 #[cfg(all(feature = "uds", unix))]
736 unix_allow: Option<crate::unix::Allow>,
737 rx: Option<tokio::sync::mpsc::Receiver<Request>>,
738 tasks: Vec<tokio::task::JoinHandle<()>>,
739}
740
741#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
742impl StreamListeners {
743 fn new(
744 binds: Vec<StreamBind>,
745 versions: moq_net::Versions,
746 #[cfg(all(feature = "uds", unix))] unix_allow: Option<crate::unix::Allow>,
747 ) -> Self {
748 let health = binds
749 .iter()
750 .map(|bind| crate::accept::Health::new(bind.name()))
751 .collect();
752 Self {
753 binds,
754 health,
755 versions,
756 #[cfg(all(feature = "uds", unix))]
757 unix_allow,
758 rx: None,
759 tasks: Vec::new(),
760 }
761 }
762
763 async fn ensure_started(&mut self, server: moq_net::Server) -> crate::Result<()> {
768 if self.rx.is_some() || self.binds.is_empty() {
769 return Ok(());
770 }
771
772 let server = server.with_versions(self.versions.clone());
775
776 let (tx, rx) = tokio::sync::mpsc::channel(16);
777 if let Err(err) = self.start(&server, &tx).await {
778 for task in self.tasks.drain(..) {
784 task.abort();
785 }
786 return Err(err);
787 }
788
789 self.rx = Some(rx);
790 Ok(())
791 }
792
793 async fn start(&mut self, server: &moq_net::Server, tx: &tokio::sync::mpsc::Sender<Request>) -> crate::Result<()> {
795 let binds = self.binds.clone();
798 let health = self.health.clone();
799 for (bind, health) in binds.into_iter().zip(health) {
800 let alpns = self.versions.alpns();
801 match bind {
802 #[cfg(feature = "tcp")]
803 StreamBind::Tcp(addr) => {
804 if !addr.ip().is_loopback() {
805 tracing::warn!(%addr, "tcp listener bound to a non-loopback address; qmux is UNENCRYPTED, ensure the network is trusted");
806 }
807 let listener = crate::tcp::Listener::bind(addr)
808 .await?
809 .with_protocols(alpns)
810 .with_accept_health(health);
811 tracing::info!(%addr, "listening (tcp)");
812 self.tasks.push(spawn_tcp_loop(listener, server.clone(), tx.clone()));
813 }
814 #[cfg(all(feature = "uds", unix))]
815 StreamBind::Unix(path) => {
816 let listener = crate::unix::Listener::bind(&path)
817 .await?
818 .with_protocols(alpns)
819 .with_accept_health(health);
820 listener.set_mode(0o666)?;
823 tracing::info!(path = %path.display(), allow = ?self.unix_allow, "listening (unix)");
824 self.tasks.push(spawn_unix_loop(
825 listener,
826 server.clone(),
827 self.unix_allow.clone(),
828 tx.clone(),
829 ));
830 }
831 }
832 }
833
834 Ok(())
835 }
836
837 async fn recv(&mut self) -> Option<Request> {
839 match self.rx.as_mut() {
840 Some(rx) => rx.recv().await,
841 None => std::future::pending().await,
842 }
843 }
844
845 async fn close(&mut self) {
847 self.binds.clear();
848 self.rx = None;
849 let tasks = std::mem::take(&mut self.tasks);
850 for task in tasks {
851 task.abort();
852 let _ = task.await;
853 }
854 }
855}
856
857#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
858impl Drop for StreamListeners {
859 fn drop(&mut self) {
860 for task in &self.tasks {
862 task.abort();
863 }
864 }
865}
866
867#[cfg(feature = "tcp")]
868fn spawn_tcp_loop(
869 listener: crate::tcp::Listener,
870 server: moq_net::Server,
871 tx: tokio::sync::mpsc::Sender<Request>,
872) -> tokio::task::JoinHandle<()> {
873 tokio::spawn(async move {
874 loop {
875 match listener.accept().await {
876 Some(Ok(session)) => spawn_stream_request(session, Transport::Tcp, server.clone(), tx.clone()),
877 Some(Err(err)) => tracing::warn!(%err, "tcp qmux handshake failed"),
880 None => break,
881 }
882 }
883 })
884}
885
886#[cfg(all(feature = "uds", unix))]
887fn spawn_unix_loop(
888 listener: crate::unix::Listener,
889 server: moq_net::Server,
890 allow: Option<crate::unix::Allow>,
891 tx: tokio::sync::mpsc::Sender<Request>,
892) -> tokio::task::JoinHandle<()> {
893 tokio::spawn(async move {
894 loop {
895 match listener.accept().await {
896 Some(Ok((session, cred))) => {
897 if let Some(allow) = &allow
899 && !allow.permits(&cred)
900 {
901 tracing::warn!(uid = cred.uid, gid = cred.gid, pid = ?cred.pid, "unix connection rejected by allow list");
902 continue;
903 }
904 spawn_stream_request(session, Transport::Unix, server.clone(), tx.clone());
905 }
906 Some(Err(err)) => tracing::warn!(%err, "unix qmux handshake failed"),
908 None => break,
909 }
910 }
911 })
912}
913
914#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
917fn spawn_stream_request(
918 session: qmux::Session,
919 transport: Transport,
920 server: moq_net::Server,
921 tx: tokio::sync::mpsc::Sender<Request>,
922) {
923 tokio::spawn(async move {
924 match server.accept_request(session).await {
925 Ok(request) => {
926 let request = Request {
927 transport,
928 url: None,
929 identity: None,
930 kind: RequestKind::Qmux(Box::new(request)),
931 };
932 let _ = tx.send(request).await;
933 }
934 Err(err) => tracing::debug!(%err, "stream SETUP handshake failed"),
935 }
936 });
937}
938
939pub(crate) enum RequestKind {
946 #[cfg(feature = "noq")]
947 Noq(Box<moq_net::Request<web_transport_noq::Session>>),
948 #[cfg(feature = "quinn")]
949 Quinn(Box<moq_net::Request<web_transport_quinn::Session>>),
950 #[cfg(feature = "quiche")]
951 Quiche(Box<moq_net::Request<web_transport_quiche::Connection>>),
952 #[cfg(feature = "iroh")]
953 Iroh(Box<moq_net::Request<web_transport_iroh::Session>>),
954 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
955 Qmux(Box<moq_net::Request<qmux::Session>>),
956}
957
958#[non_exhaustive]
960#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
961pub enum Transport {
962 Quic,
964 Iroh,
966 WebSocket,
968 Tcp,
970 Unix,
972}
973
974impl Transport {
975 pub const fn as_str(self) -> &'static str {
977 match self {
978 Self::Quic => "quic",
979 Self::Iroh => "iroh",
980 Self::WebSocket => "websocket",
981 Self::Tcp => "tcp",
982 Self::Unix => "unix",
983 }
984 }
985}
986
987impl std::fmt::Display for Transport {
988 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
989 f.write_str(self.as_str())
990 }
991}
992
993pub struct Request {
1002 transport: Transport,
1003 url: Option<Url>,
1006 identity: Option<crate::tls::PeerIdentity>,
1009 kind: RequestKind,
1010}
1011
1012macro_rules! request_ref {
1014 ($self:expr, $r:ident => $body:expr) => {
1015 match &$self.kind {
1016 #[cfg(feature = "noq")]
1017 RequestKind::Noq($r) => $body,
1018 #[cfg(feature = "quinn")]
1019 RequestKind::Quinn($r) => $body,
1020 #[cfg(feature = "quiche")]
1021 RequestKind::Quiche($r) => $body,
1022 #[cfg(feature = "iroh")]
1023 RequestKind::Iroh($r) => $body,
1024 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
1025 RequestKind::Qmux($r) => $body,
1026 }
1027 };
1028}
1029
1030macro_rules! request_into {
1032 ($kind:expr, $r:ident => $body:expr) => {
1033 match $kind {
1034 #[cfg(feature = "noq")]
1035 RequestKind::Noq($r) => $body,
1036 #[cfg(feature = "quinn")]
1037 RequestKind::Quinn($r) => $body,
1038 #[cfg(feature = "quiche")]
1039 RequestKind::Quiche($r) => $body,
1040 #[cfg(feature = "iroh")]
1041 RequestKind::Iroh($r) => $body,
1042 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
1043 RequestKind::Qmux($r) => $body,
1044 }
1045 };
1046}
1047
1048macro_rules! request_map {
1050 ($kind:expr, $r:ident => $body:expr) => {
1051 match $kind {
1052 #[cfg(feature = "noq")]
1053 RequestKind::Noq($r) => RequestKind::Noq(Box::new($body)),
1054 #[cfg(feature = "quinn")]
1055 RequestKind::Quinn($r) => RequestKind::Quinn(Box::new($body)),
1056 #[cfg(feature = "quiche")]
1057 RequestKind::Quiche($r) => RequestKind::Quiche(Box::new($body)),
1058 #[cfg(feature = "iroh")]
1059 RequestKind::Iroh($r) => RequestKind::Iroh(Box::new($body)),
1060 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
1061 RequestKind::Qmux($r) => RequestKind::Qmux(Box::new($body)),
1062 }
1063 };
1064}
1065
1066impl Request {
1067 pub async fn close(self, code: u16) -> crate::Result<()> {
1071 let err = match code {
1072 401 | 403 => moq_net::Error::Unauthorized,
1073 other => moq_net::Error::App(other),
1074 };
1075 request_into!(self.kind, request => request.close(err));
1076 Ok(())
1077 }
1078
1079 pub fn with_publisher(self, publish: impl moq_net::Consume<moq_net::origin::Consumer>) -> Self {
1081 let Request {
1082 transport,
1083 url,
1084 identity,
1085 kind,
1086 } = self;
1087 let kind = request_map!(kind, request => request.with_publisher(publish));
1088 Request {
1089 transport,
1090 url,
1091 identity,
1092 kind,
1093 }
1094 }
1095
1096 pub fn with_subscriber(self, subscribe: moq_net::origin::Producer) -> Self {
1098 let Request {
1099 transport,
1100 url,
1101 identity,
1102 kind,
1103 } = self;
1104 let kind = request_map!(kind, request => request.with_subscriber(subscribe));
1105 Request {
1106 transport,
1107 url,
1108 identity,
1109 kind,
1110 }
1111 }
1112
1113 pub fn with_peer_origin(self, origin: moq_net::Origin) -> Self {
1117 let Request {
1118 transport,
1119 url,
1120 identity,
1121 kind,
1122 } = self;
1123 let kind = request_map!(kind, request => request.with_peer_origin(origin));
1124 Request {
1125 transport,
1126 url,
1127 identity,
1128 kind,
1129 }
1130 }
1131
1132 pub fn with_stats(self, stats: moq_net::stats::Session) -> Self {
1134 let Request {
1135 transport,
1136 url,
1137 identity,
1138 kind,
1139 } = self;
1140 let kind = request_map!(kind, request => request.with_stats(stats));
1141 Request {
1142 transport,
1143 url,
1144 identity,
1145 kind,
1146 }
1147 }
1148
1149 pub async fn ok(self) -> crate::Result<Session> {
1151 let pair = request_into!(self.kind, request => request.ok().await?);
1152 Ok(crate::spawn_session(pair))
1153 }
1154
1155 pub fn transport(&self) -> Transport {
1157 self.transport
1158 }
1159
1160 pub fn url(&self) -> Option<&Url> {
1165 self.url.as_ref()
1166 }
1167
1168 pub fn path(&self) -> &str {
1175 let setup = request_ref!(self, r => r.path());
1179 let path = if setup.is_empty() {
1180 self.url.as_ref().map(Url::path).unwrap_or("")
1181 } else {
1182 setup.split_once('?').map_or(setup, |(path, _)| path)
1183 };
1184 if path == "/" { "" } else { path }
1185 }
1186
1187 pub fn query(&self) -> Option<&str> {
1191 let setup = request_ref!(self, r => r.path());
1192 if setup.is_empty() {
1193 self.url.as_ref().and_then(Url::query)
1194 } else {
1195 setup.split_once('?').map(|(_, query)| query)
1196 }
1197 }
1198
1199 pub fn role(&self) -> Option<moq_net::Role> {
1204 request_ref!(self, r => r.role())
1205 }
1206
1207 pub fn peer_origin(&self) -> Option<moq_net::Origin> {
1215 request_ref!(self, r => r.peer_origin())
1216 }
1217
1218 pub fn peer_identity(&self) -> Option<crate::tls::PeerIdentity> {
1226 self.identity.clone()
1227 }
1228
1229 #[doc(hidden)]
1230 #[deprecated(note = "use `peer_identity` instead")]
1231 pub fn has_peer_certificate(&self) -> bool {
1232 self.peer_identity().is_some()
1233 }
1234}
1235
1236#[cfg(test)]
1237mod tests {
1238 use super::*;
1239
1240 #[test]
1241 fn version_help_lists_every_parseable_name() {
1242 let help = <ServerConfig as clap::Args>::augment_args(clap::Command::new("test"))
1243 .render_long_help()
1244 .to_string();
1245 for name in moq_net::Version::names() {
1246 assert!(help.contains(name), "missing {name} from --server-version help");
1247 }
1248 }
1249
1250 #[cfg(feature = "tcp")]
1258 #[test]
1259 fn accept_health_covers_stream_listeners_before_they_bind() {
1260 let mut config = ServerConfig::default();
1261 config.tcp.bind = Some("127.0.0.1:0".parse().unwrap());
1262 let server = Server::new(config).expect("stream-only server");
1263
1264 let names: Vec<_> = server.accept_health().iter().map(|h| h.listener()).collect();
1265 assert_eq!(names, vec!["tcp"], "the tcp listener must report before it binds");
1266 }
1267
1268 #[cfg(all(feature = "tcp", feature = "uds", unix))]
1274 #[tokio::test]
1275 async fn a_failed_listen_binds_nothing_and_can_be_retried() {
1276 let dir = tempfile::TempDir::new().unwrap();
1279 let occupied = dir.path().join("not-a-socket");
1280 std::fs::write(&occupied, b"in the way").unwrap();
1281
1282 let mut config = ServerConfig::default();
1283 config.tcp.bind = Some("127.0.0.1:0".parse().unwrap());
1284 config.unix.bind = Some(occupied);
1285 let mut server = Server::new(config).expect("stream-only server");
1286
1287 assert!(server.listen().await.is_err(), "the unix bind must fail");
1288 assert!(server.listen().await.is_err(), "a retry must not report success");
1291 }
1292
1293 #[cfg(feature = "tcp")]
1296 #[tokio::test]
1297 async fn close_releases_stream_listener_socket() {
1298 let probe = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1299 let addr = probe.local_addr().unwrap();
1300 drop(probe);
1301
1302 let mut config = ServerConfig::default();
1303 config.tcp.bind = Some(addr);
1304 let mut server = Server::new(config).expect("stream-only server");
1305 server.listen().await.expect("listen");
1306 assert!(tokio::net::TcpListener::bind(addr).await.is_err(), "listener is bound");
1307
1308 server.close().await;
1309 server.listen().await.expect("closed listener stays terminal");
1310 let _rebound = tokio::net::TcpListener::bind(addr)
1311 .await
1312 .expect("close must release the listener socket");
1313 }
1314
1315 #[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche")))]
1317 #[test]
1318 fn quic_bind_without_a_quic_backend_is_rejected() {
1319 let config = ServerConfig {
1320 bind: Some("127.0.0.1:0".to_string()),
1321 ..Default::default()
1322 };
1323
1324 assert!(matches!(Server::new(config), Err(Error::NoBackend(_))));
1325 }
1326
1327 #[cfg(all(feature = "quinn", not(feature = "tcp")))]
1330 #[test]
1331 fn accept_health_is_empty_without_a_stream_listener() {
1332 let server = ServerConfig::default().init().expect("quic server");
1333 assert!(server.accept_health().is_empty());
1334 }
1335
1336 #[test]
1337 fn transport_names_are_stable() {
1338 assert_eq!(Transport::Quic.as_str(), "quic");
1339 assert_eq!(Transport::Iroh.as_str(), "iroh");
1340 assert_eq!(Transport::WebSocket.as_str(), "websocket");
1341 assert_eq!(Transport::Tcp.as_str(), "tcp");
1342 assert_eq!(Transport::Unix.as_str(), "unix");
1343 }
1344
1345 #[cfg(feature = "quinn")]
1348 #[tokio::test]
1349 async fn certificates_expose_generated_fingerprints() {
1350 let mut config = ServerConfig {
1351 bind: Some("[::]:0".to_string()),
1352 ..Default::default()
1353 };
1354 config.tls.generate = vec!["localhost".into()];
1355
1356 let certs = config.init().expect("server init").certificates();
1357 let fingerprints = certs.fingerprints();
1358 assert_eq!(fingerprints.len(), 1, "one generated certificate");
1359 assert_eq!(fingerprints[0].len(), 64);
1361 assert!(fingerprints[0].chars().all(|c| c.is_ascii_hexdigit()));
1362 }
1363
1364 #[cfg(all(feature = "uds", unix))]
1369 #[tokio::test]
1370 async fn unix_listener_serves_the_configured_publisher() {
1371 use rand::RngExt;
1372
1373 let path = PathBuf::from(format!("/tmp/moq-native-publish-{}.sock", std::process::id()));
1376 let _ = std::fs::remove_file(&path);
1377
1378 let origin = moq_net::Origin::random().produce();
1379 let mut broadcast = origin
1380 .create_broadcast("test", moq_net::broadcast::Route::new().with_announce(true))
1381 .expect("create broadcast");
1382 let mut track = broadcast.create_track("video", None).expect("create track");
1383 let mut group = track.append_group().expect("append group");
1384 group
1385 .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref())
1386 .expect("write frame");
1387 group.finish().expect("finish group");
1388
1389 let mut config = ServerConfig::default();
1390 config.unix.bind = Some(path.clone());
1391 let server = config.init().expect("server init");
1392
1393 let serve = tokio::spawn(server.serve_publish(origin.consume()));
1395
1396 const MAX_DELAY: std::time::Duration = std::time::Duration::from_millis(100);
1400 let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5);
1401 let mut delay = std::time::Duration::from_millis(1);
1402 while let Err(err) = tokio::net::UnixStream::connect(&path).await {
1403 assert!(
1404 tokio::time::Instant::now() < deadline,
1405 "unix listener never bound: {err}"
1406 );
1407 tokio::time::sleep(delay.mul_f64(0.5 + rand::rng().random::<f64>() / 2.0)).await;
1408 delay = (delay * 2).min(MAX_DELAY);
1409 }
1410
1411 const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
1412
1413 let url: Url = format!("unix://{}", path.display()).parse().expect("parse url");
1414 let subscriber = moq_net::Origin::random().produce();
1415 let mut announced = subscriber.consume().announced();
1416 let client = crate::ClientConfig::default()
1417 .init()
1418 .expect("client init")
1419 .with_subscriber(subscriber);
1420 let session = tokio::time::timeout(TIMEOUT, client.connect(url))
1421 .await
1422 .expect("connect timeout")
1423 .expect("connect");
1424
1425 let update = tokio::time::timeout(TIMEOUT, announced.next())
1428 .await
1429 .expect("announce timeout")
1430 .expect("origin closed");
1431 assert_eq!(update.path.as_str(), "test");
1432 let broadcast = update.broadcast.expect("expected an announce");
1433
1434 let mut track = broadcast
1435 .track("video")
1436 .expect("track name")
1437 .subscribe(None)
1438 .await
1439 .expect("subscribe");
1440 let mut group = tokio::time::timeout(TIMEOUT, track.recv_group())
1441 .await
1442 .expect("recv group timeout")
1443 .expect("recv group")
1444 .expect("track closed early");
1445 let frame = tokio::time::timeout(TIMEOUT, group.read_frame())
1446 .await
1447 .expect("read frame timeout")
1448 .expect("read frame")
1449 .expect("group closed early");
1450 assert_eq!(&frame.payload[..], b"hello");
1451
1452 drop(session);
1453 serve.abort();
1454 let _ = std::fs::remove_file(&path);
1455 }
1456
1457 #[cfg(all(feature = "uds", unix))]
1460 #[tokio::test]
1461 async fn certificates_are_empty_without_a_tls_backend() {
1462 let mut config = ServerConfig::default();
1463 config.unix.bind = Some(PathBuf::from("/tmp/moq-native-certificates-test.sock"));
1464
1465 let server = config.init().expect("server init");
1466 assert!(server.certificates().fingerprints().is_empty());
1467 }
1468
1469 #[test]
1470 fn test_tls_string_or_array() {
1471 let single = r#"
1473 cert = "cert.pem"
1474 key = "key.pem"
1475 "#;
1476 let config: crate::tls::Server = toml::from_str(single).unwrap();
1477 assert_eq!(config.cert, vec![PathBuf::from("cert.pem")]);
1478 assert_eq!(config.key, vec![PathBuf::from("key.pem")]);
1479
1480 let array = r#"
1482 cert = ["a.pem", "b.pem"]
1483 key = ["a.key", "b.key"]
1484 generate = ["localhost"]
1485 root = ["ca.pem"]
1486 "#;
1487 let config: crate::tls::Server = toml::from_str(array).unwrap();
1488 assert_eq!(config.cert, vec![PathBuf::from("a.pem"), PathBuf::from("b.pem")]);
1489 assert_eq!(config.key, vec![PathBuf::from("a.key"), PathBuf::from("b.key")]);
1490 assert_eq!(config.generate, vec!["localhost".to_string()]);
1491 assert_eq!(config.root, vec![PathBuf::from("ca.pem")]);
1492 }
1493
1494 #[test]
1495 fn bind_string_or_listen_alias() {
1496 let bind: ServerConfig = toml::from_str(r#"bind = "[::]:443""#).unwrap();
1498 assert_eq!(bind.bind.as_deref(), Some("[::]:443"));
1499
1500 let alias: ServerConfig = toml::from_str(r#"listen = "0.0.0.0:4443""#).unwrap();
1501 assert_eq!(alias.bind.as_deref(), Some("0.0.0.0:4443"));
1502 }
1503
1504 #[cfg(all(feature = "uds", unix))]
1505 #[test]
1506 fn stream_listener_config_parses() {
1507 let config: ServerConfig = toml::from_str(
1508 r#"
1509bind = "[::]:443"
1510
1511[unix]
1512bind = "/run/moq.sock"
1513
1514[unix.allow]
1515uid = [1001, 1002]
1516"#,
1517 )
1518 .unwrap();
1519 assert_eq!(config.bind.as_deref(), Some("[::]:443"));
1520 assert_eq!(config.unix.bind.as_deref(), Some(std::path::Path::new("/run/moq.sock")));
1521 assert_eq!(config.unix.allow.as_ref().expect("allow").uid, vec![1001, 1002]);
1522 assert!(config.has_stream_listener());
1523 assert!(config.has_explicit_bind());
1524 }
1525
1526 #[cfg(all(feature = "uds", unix))]
1527 #[test]
1528 fn stream_only_config_has_no_quic() {
1529 let mut config = ServerConfig::default();
1531 config.unix.bind = Some(PathBuf::from("/run/moq.sock"));
1532 assert!(config.has_stream_listener());
1533 assert!(config.has_explicit_bind());
1534 assert!(config.bind.is_none());
1535
1536 assert!(!ServerConfig::default().has_stream_listener());
1538 assert!(!ServerConfig::default().has_explicit_bind());
1539 }
1540}