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 Accepted { session, url, identity, authority } = super::noq::accept(_conn, alpns).await?;
542 let request = server.accept_request(session).await?;
543 Ok(Request { transport: Transport::Quic, url, identity, authority, 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 Accepted { session, url, identity, authority } = super::quinn::accept(_conn, alpns).await?;
553 let request = server.accept_request(session).await?;
554 Ok(Request { transport: Transport::Quic, url, identity, authority, 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 Accepted { session, url, identity, authority } = super::quiche::accept(_conn, alpns).await?;
564 let request = server.accept_request(session).await?;
565 Ok(Request { transport: Transport::Quic, url, identity, authority, 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 Accepted { session, url, identity, authority } = super::iroh::accept(_conn).await?;
573 let request = server.accept_request(session).await?;
574 Ok(Request { transport: Transport::Iroh, url, identity, authority, 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 let authority = url.host_str().filter(|h| !h.is_empty()).map(str::to_owned);
586 Ok(Request { transport: Transport::WebSocket, url: Some(url), authority, identity: None, kind: RequestKind::Qmux(Box::new(request)) })
587 }.boxed());
588 }
589 Err(err) => tracing::debug!(%err, "WebSocket upgrade failed"),
593 }
594 }
595 Some(res) = self.accept.next() => {
596 match res {
597 Ok(session) => return Some(session),
598 Err(err) => tracing::debug!(%err, "failed to accept session"),
599 }
600 }
601 _ = tokio::signal::ctrl_c() => {
602 self.close().await;
603 return None;
604 }
605 }
606 }
607 }
608
609 #[cfg(feature = "iroh")]
611 pub fn iroh_endpoint(&self) -> Option<&iroh::Endpoint> {
612 self.iroh.as_ref()
613 }
614
615 pub fn local_addr(&self) -> crate::Result<net::SocketAddr> {
621 #[cfg(feature = "noq")]
622 if let Some(noq) = self.noq.as_ref() {
623 return Ok(noq.local_addr()?);
624 }
625 #[cfg(feature = "quinn")]
626 if let Some(quinn) = self.quinn.as_ref() {
627 return Ok(quinn.local_addr()?);
628 }
629 #[cfg(feature = "quiche")]
630 if let Some(quiche) = self.quiche.as_ref() {
631 return Ok(quiche.local_addr()?);
632 }
633 Err(Error::NoBackend("no QUIC listener configured"))
635 }
636
637 #[cfg(feature = "websocket")]
640 pub fn websocket_local_addr(&self) -> Option<net::SocketAddr> {
641 self.websocket.as_ref().and_then(|ws| ws.local_addr().ok())
642 }
643
644 pub async fn close(&mut self) {
649 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
650 self.streams.close().await;
651 #[cfg(feature = "noq")]
652 if let Some(noq) = self.noq.as_mut() {
653 noq.close();
654 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
655 }
656 #[cfg(feature = "quinn")]
657 if let Some(quinn) = self.quinn.as_mut() {
658 quinn.close();
659 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
660 }
661 #[cfg(feature = "quiche")]
662 if let Some(quiche) = self.quiche.as_mut() {
663 quiche.close();
664 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
665 }
666 #[cfg(feature = "iroh")]
667 if let Some(iroh) = self.iroh.take() {
668 iroh.close().await;
669 }
670 #[cfg(feature = "websocket")]
671 {
672 let _ = self.websocket.take();
673 }
674 }
675}
676
677async fn serve_session(request: Request) -> crate::Result<()> {
679 let session = request.ok().await?;
680 Err(session.closed().await.into())
681}
682
683#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
689fn stream_versions(base: &moq_net::Versions) -> moq_net::Versions {
690 let mut versions: Vec<moq_net::Version> = base.iter().copied().collect();
691 if let Ok(lite05) = "moq-lite-05".parse::<moq_net::Version>()
692 && !versions.contains(&lite05)
693 {
694 versions.push(lite05);
695 }
696 moq_net::Versions::from(versions)
697}
698
699#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
701#[derive(Clone)]
702enum StreamBind {
703 #[cfg(feature = "tcp")]
704 Tcp(net::SocketAddr),
705 #[cfg(all(feature = "uds", unix))]
706 Unix(PathBuf),
707}
708
709#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
710impl StreamBind {
711 fn name(&self) -> &'static str {
713 match self {
714 #[cfg(feature = "tcp")]
715 Self::Tcp(_) => "tcp",
716 #[cfg(all(feature = "uds", unix))]
717 Self::Unix(_) => "unix",
718 }
719 }
720}
721
722#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
729struct StreamListeners {
730 binds: Vec<StreamBind>,
731 health: Vec<crate::accept::Health>,
735 versions: moq_net::Versions,
736 #[cfg(all(feature = "uds", unix))]
737 unix_allow: Option<crate::unix::Allow>,
738 rx: Option<tokio::sync::mpsc::Receiver<Request>>,
739 tasks: Vec<tokio::task::JoinHandle<()>>,
740}
741
742#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
743impl StreamListeners {
744 fn new(
745 binds: Vec<StreamBind>,
746 versions: moq_net::Versions,
747 #[cfg(all(feature = "uds", unix))] unix_allow: Option<crate::unix::Allow>,
748 ) -> Self {
749 let health = binds
750 .iter()
751 .map(|bind| crate::accept::Health::new(bind.name()))
752 .collect();
753 Self {
754 binds,
755 health,
756 versions,
757 #[cfg(all(feature = "uds", unix))]
758 unix_allow,
759 rx: None,
760 tasks: Vec::new(),
761 }
762 }
763
764 async fn ensure_started(&mut self, server: moq_net::Server) -> crate::Result<()> {
769 if self.rx.is_some() || self.binds.is_empty() {
770 return Ok(());
771 }
772
773 let server = server.with_versions(self.versions.clone());
776
777 let (tx, rx) = tokio::sync::mpsc::channel(16);
778 if let Err(err) = self.start(&server, &tx).await {
779 for task in self.tasks.drain(..) {
785 task.abort();
786 }
787 return Err(err);
788 }
789
790 self.rx = Some(rx);
791 Ok(())
792 }
793
794 async fn start(&mut self, server: &moq_net::Server, tx: &tokio::sync::mpsc::Sender<Request>) -> crate::Result<()> {
796 let binds = self.binds.clone();
799 let health = self.health.clone();
800 for (bind, health) in binds.into_iter().zip(health) {
801 let alpns = self.versions.alpns();
802 match bind {
803 #[cfg(feature = "tcp")]
804 StreamBind::Tcp(addr) => {
805 if !addr.ip().is_loopback() {
806 tracing::warn!(%addr, "tcp listener bound to a non-loopback address; qmux is UNENCRYPTED, ensure the network is trusted");
807 }
808 let listener = crate::tcp::Listener::bind(addr)
809 .await?
810 .with_protocols(alpns)
811 .with_accept_health(health);
812 tracing::info!(%addr, "listening (tcp)");
813 self.tasks.push(spawn_tcp_loop(listener, server.clone(), tx.clone()));
814 }
815 #[cfg(all(feature = "uds", unix))]
816 StreamBind::Unix(path) => {
817 let listener = crate::unix::Listener::bind(&path)
818 .await?
819 .with_protocols(alpns)
820 .with_accept_health(health);
821 listener.set_mode(0o666)?;
824 tracing::info!(path = %path.display(), allow = ?self.unix_allow, "listening (unix)");
825 self.tasks.push(spawn_unix_loop(
826 listener,
827 server.clone(),
828 self.unix_allow.clone(),
829 tx.clone(),
830 ));
831 }
832 }
833 }
834
835 Ok(())
836 }
837
838 async fn recv(&mut self) -> Option<Request> {
840 match self.rx.as_mut() {
841 Some(rx) => rx.recv().await,
842 None => std::future::pending().await,
843 }
844 }
845
846 async fn close(&mut self) {
848 self.binds.clear();
849 self.rx = None;
850 let tasks = std::mem::take(&mut self.tasks);
851 for task in tasks {
852 task.abort();
853 let _ = task.await;
854 }
855 }
856}
857
858#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
859impl Drop for StreamListeners {
860 fn drop(&mut self) {
861 for task in &self.tasks {
863 task.abort();
864 }
865 }
866}
867
868#[cfg(feature = "tcp")]
869fn spawn_tcp_loop(
870 listener: crate::tcp::Listener,
871 server: moq_net::Server,
872 tx: tokio::sync::mpsc::Sender<Request>,
873) -> tokio::task::JoinHandle<()> {
874 tokio::spawn(async move {
875 loop {
876 match listener.accept().await {
877 Some(Ok(session)) => spawn_stream_request(session, Transport::Tcp, server.clone(), tx.clone()),
878 Some(Err(err)) => tracing::warn!(%err, "tcp qmux handshake failed"),
881 None => break,
882 }
883 }
884 })
885}
886
887#[cfg(all(feature = "uds", unix))]
888fn spawn_unix_loop(
889 listener: crate::unix::Listener,
890 server: moq_net::Server,
891 allow: Option<crate::unix::Allow>,
892 tx: tokio::sync::mpsc::Sender<Request>,
893) -> tokio::task::JoinHandle<()> {
894 tokio::spawn(async move {
895 loop {
896 match listener.accept().await {
897 Some(Ok((session, cred))) => {
898 if let Some(allow) = &allow
900 && !allow.permits(&cred)
901 {
902 tracing::warn!(uid = cred.uid, gid = cred.gid, pid = ?cred.pid, "unix connection rejected by allow list");
903 continue;
904 }
905 spawn_stream_request(session, Transport::Unix, server.clone(), tx.clone());
906 }
907 Some(Err(err)) => tracing::warn!(%err, "unix qmux handshake failed"),
909 None => break,
910 }
911 }
912 })
913}
914
915#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
918fn spawn_stream_request(
919 session: qmux::Session,
920 transport: Transport,
921 server: moq_net::Server,
922 tx: tokio::sync::mpsc::Sender<Request>,
923) {
924 tokio::spawn(async move {
925 match server.accept_request(session).await {
926 Ok(request) => {
927 let request = Request {
928 transport,
929 url: None,
930 authority: None,
931 identity: None,
932 kind: RequestKind::Qmux(Box::new(request)),
933 };
934 let _ = tx.send(request).await;
935 }
936 Err(err) => tracing::debug!(%err, "stream SETUP handshake failed"),
937 }
938 });
939}
940
941pub(crate) enum RequestKind {
948 #[cfg(feature = "noq")]
949 Noq(Box<moq_net::Request<web_transport_noq::Session>>),
950 #[cfg(feature = "quinn")]
951 Quinn(Box<moq_net::Request<web_transport_quinn::Session>>),
952 #[cfg(feature = "quiche")]
953 Quiche(Box<moq_net::Request<web_transport_quiche::Connection>>),
954 #[cfg(feature = "iroh")]
955 Iroh(Box<moq_net::Request<web_transport_iroh::Session>>),
956 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
957 Qmux(Box<moq_net::Request<qmux::Session>>),
958}
959
960#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche", feature = "iroh"))]
964pub(crate) struct Accepted<S> {
965 pub session: S,
966 pub url: Option<Url>,
967 pub identity: Option<crate::tls::PeerIdentity>,
968 pub authority: Option<String>,
969}
970
971#[non_exhaustive]
973#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
974pub enum Transport {
975 Quic,
977 Iroh,
979 WebSocket,
981 Tcp,
983 Unix,
985}
986
987impl Transport {
988 pub const fn as_str(self) -> &'static str {
990 match self {
991 Self::Quic => "quic",
992 Self::Iroh => "iroh",
993 Self::WebSocket => "websocket",
994 Self::Tcp => "tcp",
995 Self::Unix => "unix",
996 }
997 }
998}
999
1000impl std::fmt::Display for Transport {
1001 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1002 f.write_str(self.as_str())
1003 }
1004}
1005
1006pub struct Request {
1015 transport: Transport,
1016 url: Option<Url>,
1019 authority: Option<String>,
1022 identity: Option<crate::tls::PeerIdentity>,
1025 kind: RequestKind,
1026}
1027
1028macro_rules! request_ref {
1030 ($self:expr, $r:ident => $body:expr) => {
1031 match &$self.kind {
1032 #[cfg(feature = "noq")]
1033 RequestKind::Noq($r) => $body,
1034 #[cfg(feature = "quinn")]
1035 RequestKind::Quinn($r) => $body,
1036 #[cfg(feature = "quiche")]
1037 RequestKind::Quiche($r) => $body,
1038 #[cfg(feature = "iroh")]
1039 RequestKind::Iroh($r) => $body,
1040 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
1041 RequestKind::Qmux($r) => $body,
1042 }
1043 };
1044}
1045
1046macro_rules! request_into {
1048 ($kind:expr, $r:ident => $body:expr) => {
1049 match $kind {
1050 #[cfg(feature = "noq")]
1051 RequestKind::Noq($r) => $body,
1052 #[cfg(feature = "quinn")]
1053 RequestKind::Quinn($r) => $body,
1054 #[cfg(feature = "quiche")]
1055 RequestKind::Quiche($r) => $body,
1056 #[cfg(feature = "iroh")]
1057 RequestKind::Iroh($r) => $body,
1058 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
1059 RequestKind::Qmux($r) => $body,
1060 }
1061 };
1062}
1063
1064macro_rules! request_map {
1066 ($kind:expr, $r:ident => $body:expr) => {
1067 match $kind {
1068 #[cfg(feature = "noq")]
1069 RequestKind::Noq($r) => RequestKind::Noq(Box::new($body)),
1070 #[cfg(feature = "quinn")]
1071 RequestKind::Quinn($r) => RequestKind::Quinn(Box::new($body)),
1072 #[cfg(feature = "quiche")]
1073 RequestKind::Quiche($r) => RequestKind::Quiche(Box::new($body)),
1074 #[cfg(feature = "iroh")]
1075 RequestKind::Iroh($r) => RequestKind::Iroh(Box::new($body)),
1076 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
1077 RequestKind::Qmux($r) => RequestKind::Qmux(Box::new($body)),
1078 }
1079 };
1080}
1081
1082impl Request {
1083 pub async fn close(self, code: u16) -> crate::Result<()> {
1087 let err = match code {
1088 401 | 403 => moq_net::Error::Unauthorized,
1089 other => moq_net::Error::App(other),
1090 };
1091 request_into!(self.kind, request => request.close(err));
1092 Ok(())
1093 }
1094
1095 pub fn with_publisher(self, publish: impl moq_net::Consume<moq_net::origin::Consumer>) -> Self {
1097 let Request {
1098 transport,
1099 url,
1100 authority,
1101 identity,
1102 kind,
1103 } = self;
1104 let kind = request_map!(kind, request => request.with_publisher(publish));
1105 Request {
1106 transport,
1107 url,
1108 authority,
1109 identity,
1110 kind,
1111 }
1112 }
1113
1114 pub fn with_subscriber(self, subscribe: moq_net::origin::Producer) -> Self {
1116 let Request {
1117 transport,
1118 url,
1119 authority,
1120 identity,
1121 kind,
1122 } = self;
1123 let kind = request_map!(kind, request => request.with_subscriber(subscribe));
1124 Request {
1125 transport,
1126 url,
1127 authority,
1128 identity,
1129 kind,
1130 }
1131 }
1132
1133 pub fn with_peer_origin(self, origin: moq_net::Origin) -> Self {
1137 let Request {
1138 transport,
1139 url,
1140 authority,
1141 identity,
1142 kind,
1143 } = self;
1144 let kind = request_map!(kind, request => request.with_peer_origin(origin));
1145 Request {
1146 transport,
1147 url,
1148 authority,
1149 identity,
1150 kind,
1151 }
1152 }
1153
1154 pub fn with_stats(self, stats: moq_net::stats::Session) -> Self {
1156 let Request {
1157 transport,
1158 url,
1159 authority,
1160 identity,
1161 kind,
1162 } = self;
1163 let kind = request_map!(kind, request => request.with_stats(stats));
1164 Request {
1165 transport,
1166 url,
1167 authority,
1168 identity,
1169 kind,
1170 }
1171 }
1172
1173 pub async fn ok(self) -> crate::Result<Session> {
1175 let pair = request_into!(self.kind, request => request.ok().await?);
1176 Ok(crate::spawn_session(pair))
1177 }
1178
1179 pub fn transport(&self) -> Transport {
1181 self.transport
1182 }
1183
1184 pub fn url(&self) -> Option<&Url> {
1189 self.url.as_ref()
1190 }
1191
1192 pub fn authority(&self) -> Option<&str> {
1201 self.authority.as_deref()
1202 }
1203
1204 pub fn path(&self) -> &str {
1211 let setup = request_ref!(self, r => r.path());
1215 let path = if setup.is_empty() {
1216 self.url.as_ref().map(Url::path).unwrap_or("")
1217 } else {
1218 setup.split_once('?').map_or(setup, |(path, _)| path)
1219 };
1220 if path == "/" { "" } else { path }
1221 }
1222
1223 pub fn query(&self) -> Option<&str> {
1227 let setup = request_ref!(self, r => r.path());
1228 if setup.is_empty() {
1229 self.url.as_ref().and_then(Url::query)
1230 } else {
1231 setup.split_once('?').map(|(_, query)| query)
1232 }
1233 }
1234
1235 pub fn role(&self) -> Option<moq_net::Role> {
1240 request_ref!(self, r => r.role())
1241 }
1242
1243 pub fn peer_origin(&self) -> Option<moq_net::Origin> {
1251 request_ref!(self, r => r.peer_origin())
1252 }
1253
1254 pub fn peer_identity(&self) -> Option<crate::tls::PeerIdentity> {
1262 self.identity.clone()
1263 }
1264
1265 #[doc(hidden)]
1266 #[deprecated(note = "use `peer_identity` instead")]
1267 pub fn has_peer_certificate(&self) -> bool {
1268 self.peer_identity().is_some()
1269 }
1270}
1271
1272#[cfg(test)]
1273mod tests {
1274 use super::*;
1275
1276 #[test]
1277 fn version_help_lists_every_parseable_name() {
1278 let help = <ServerConfig as clap::Args>::augment_args(clap::Command::new("test"))
1279 .render_long_help()
1280 .to_string();
1281 for name in moq_net::Version::names() {
1282 assert!(help.contains(name), "missing {name} from --server-version help");
1283 }
1284 }
1285
1286 #[cfg(feature = "tcp")]
1294 #[test]
1295 fn accept_health_covers_stream_listeners_before_they_bind() {
1296 let mut config = ServerConfig::default();
1297 config.tcp.bind = Some("127.0.0.1:0".parse().unwrap());
1298 let server = Server::new(config).expect("stream-only server");
1299
1300 let names: Vec<_> = server.accept_health().iter().map(|h| h.listener()).collect();
1301 assert_eq!(names, vec!["tcp"], "the tcp listener must report before it binds");
1302 }
1303
1304 #[cfg(all(feature = "tcp", feature = "uds", unix))]
1310 #[tokio::test]
1311 async fn a_failed_listen_binds_nothing_and_can_be_retried() {
1312 let dir = tempfile::TempDir::new().unwrap();
1315 let occupied = dir.path().join("not-a-socket");
1316 std::fs::write(&occupied, b"in the way").unwrap();
1317
1318 let mut config = ServerConfig::default();
1319 config.tcp.bind = Some("127.0.0.1:0".parse().unwrap());
1320 config.unix.bind = Some(occupied);
1321 let mut server = Server::new(config).expect("stream-only server");
1322
1323 assert!(server.listen().await.is_err(), "the unix bind must fail");
1324 assert!(server.listen().await.is_err(), "a retry must not report success");
1327 }
1328
1329 #[cfg(feature = "tcp")]
1332 #[tokio::test]
1333 async fn close_releases_stream_listener_socket() {
1334 let probe = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1335 let addr = probe.local_addr().unwrap();
1336 drop(probe);
1337
1338 let mut config = ServerConfig::default();
1339 config.tcp.bind = Some(addr);
1340 let mut server = Server::new(config).expect("stream-only server");
1341 server.listen().await.expect("listen");
1342 assert!(tokio::net::TcpListener::bind(addr).await.is_err(), "listener is bound");
1343
1344 server.close().await;
1345 server.listen().await.expect("closed listener stays terminal");
1346 let _rebound = tokio::net::TcpListener::bind(addr)
1347 .await
1348 .expect("close must release the listener socket");
1349 }
1350
1351 #[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche")))]
1353 #[test]
1354 fn quic_bind_without_a_quic_backend_is_rejected() {
1355 let config = ServerConfig {
1356 bind: Some("127.0.0.1:0".to_string()),
1357 ..Default::default()
1358 };
1359
1360 assert!(matches!(Server::new(config), Err(Error::NoBackend(_))));
1361 }
1362
1363 #[cfg(all(feature = "quinn", not(feature = "tcp")))]
1366 #[test]
1367 fn accept_health_is_empty_without_a_stream_listener() {
1368 let server = ServerConfig::default().init().expect("quic server");
1369 assert!(server.accept_health().is_empty());
1370 }
1371
1372 #[test]
1373 fn transport_names_are_stable() {
1374 assert_eq!(Transport::Quic.as_str(), "quic");
1375 assert_eq!(Transport::Iroh.as_str(), "iroh");
1376 assert_eq!(Transport::WebSocket.as_str(), "websocket");
1377 assert_eq!(Transport::Tcp.as_str(), "tcp");
1378 assert_eq!(Transport::Unix.as_str(), "unix");
1379 }
1380
1381 #[cfg(feature = "quinn")]
1384 #[tokio::test]
1385 async fn certificates_expose_generated_fingerprints() {
1386 let mut config = ServerConfig {
1387 bind: Some("[::]:0".to_string()),
1388 ..Default::default()
1389 };
1390 config.tls.generate = vec!["localhost".into()];
1391
1392 let certs = config.init().expect("server init").certificates();
1393 let fingerprints = certs.fingerprints();
1394 assert_eq!(fingerprints.len(), 1, "one generated certificate");
1395 assert_eq!(fingerprints[0].len(), 64);
1397 assert!(fingerprints[0].chars().all(|c| c.is_ascii_hexdigit()));
1398 }
1399
1400 #[cfg(all(feature = "uds", unix))]
1405 #[tokio::test]
1406 async fn unix_listener_serves_the_configured_publisher() {
1407 use rand::RngExt;
1408
1409 let path = PathBuf::from(format!("/tmp/moq-native-publish-{}.sock", std::process::id()));
1412 let _ = std::fs::remove_file(&path);
1413
1414 let origin = moq_net::Origin::random().produce();
1415 let mut broadcast = origin
1416 .create_broadcast("test", moq_net::broadcast::Route::new().with_announce(true))
1417 .expect("create broadcast");
1418 let mut track = broadcast.create_track("video", None).expect("create track");
1419 let mut group = track.append_group().expect("append group");
1420 group
1421 .write_frame(moq_net::Timestamp::ZERO, b"hello".as_ref())
1422 .expect("write frame");
1423 group.finish().expect("finish group");
1424
1425 let mut config = ServerConfig::default();
1426 config.unix.bind = Some(path.clone());
1427 let server = config.init().expect("server init");
1428
1429 let serve = tokio::spawn(server.serve_publish(origin.consume()));
1431
1432 const MAX_DELAY: std::time::Duration = std::time::Duration::from_millis(100);
1436 let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5);
1437 let mut delay = std::time::Duration::from_millis(1);
1438 while let Err(err) = tokio::net::UnixStream::connect(&path).await {
1439 assert!(
1440 tokio::time::Instant::now() < deadline,
1441 "unix listener never bound: {err}"
1442 );
1443 tokio::time::sleep(delay.mul_f64(0.5 + rand::rng().random::<f64>() / 2.0)).await;
1444 delay = (delay * 2).min(MAX_DELAY);
1445 }
1446
1447 const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
1448
1449 let url: Url = format!("unix://{}", path.display()).parse().expect("parse url");
1450 let subscriber = moq_net::Origin::random().produce();
1451 let mut announced = subscriber.consume().announced();
1452 let client = crate::ClientConfig::default()
1453 .init()
1454 .expect("client init")
1455 .with_subscriber(subscriber);
1456 let session = tokio::time::timeout(TIMEOUT, client.connect(url))
1457 .await
1458 .expect("connect timeout")
1459 .expect("connect");
1460
1461 let update = tokio::time::timeout(TIMEOUT, announced.next())
1464 .await
1465 .expect("announce timeout")
1466 .expect("origin closed");
1467 assert_eq!(update.path.as_str(), "test");
1468 let broadcast = update.broadcast.expect("expected an announce");
1469
1470 let mut track = broadcast
1471 .track("video")
1472 .expect("track name")
1473 .subscribe(None)
1474 .await
1475 .expect("subscribe");
1476 let mut group = tokio::time::timeout(TIMEOUT, track.recv_group())
1477 .await
1478 .expect("recv group timeout")
1479 .expect("recv group")
1480 .expect("track closed early");
1481 let frame = tokio::time::timeout(TIMEOUT, group.read_frame())
1482 .await
1483 .expect("read frame timeout")
1484 .expect("read frame")
1485 .expect("group closed early");
1486 assert_eq!(&frame.payload[..], b"hello");
1487
1488 drop(session);
1489 serve.abort();
1490 let _ = std::fs::remove_file(&path);
1491 }
1492
1493 #[cfg(all(feature = "uds", unix))]
1496 #[tokio::test]
1497 async fn certificates_are_empty_without_a_tls_backend() {
1498 let mut config = ServerConfig::default();
1499 config.unix.bind = Some(PathBuf::from("/tmp/moq-native-certificates-test.sock"));
1500
1501 let server = config.init().expect("server init");
1502 assert!(server.certificates().fingerprints().is_empty());
1503 }
1504
1505 #[test]
1506 fn test_tls_string_or_array() {
1507 let single = r#"
1509 cert = "cert.pem"
1510 key = "key.pem"
1511 "#;
1512 let config: crate::tls::Server = toml::from_str(single).unwrap();
1513 assert_eq!(config.cert, vec![PathBuf::from("cert.pem")]);
1514 assert_eq!(config.key, vec![PathBuf::from("key.pem")]);
1515
1516 let array = r#"
1518 cert = ["a.pem", "b.pem"]
1519 key = ["a.key", "b.key"]
1520 generate = ["localhost"]
1521 root = ["ca.pem"]
1522 "#;
1523 let config: crate::tls::Server = toml::from_str(array).unwrap();
1524 assert_eq!(config.cert, vec![PathBuf::from("a.pem"), PathBuf::from("b.pem")]);
1525 assert_eq!(config.key, vec![PathBuf::from("a.key"), PathBuf::from("b.key")]);
1526 assert_eq!(config.generate, vec!["localhost".to_string()]);
1527 assert_eq!(config.root, vec![PathBuf::from("ca.pem")]);
1528 }
1529
1530 #[test]
1531 fn bind_string_or_listen_alias() {
1532 let bind: ServerConfig = toml::from_str(r#"bind = "[::]:443""#).unwrap();
1534 assert_eq!(bind.bind.as_deref(), Some("[::]:443"));
1535
1536 let alias: ServerConfig = toml::from_str(r#"listen = "0.0.0.0:4443""#).unwrap();
1537 assert_eq!(alias.bind.as_deref(), Some("0.0.0.0:4443"));
1538 }
1539
1540 #[cfg(all(feature = "uds", unix))]
1541 #[test]
1542 fn stream_listener_config_parses() {
1543 let config: ServerConfig = toml::from_str(
1544 r#"
1545bind = "[::]:443"
1546
1547[unix]
1548bind = "/run/moq.sock"
1549
1550[unix.allow]
1551uid = [1001, 1002]
1552"#,
1553 )
1554 .unwrap();
1555 assert_eq!(config.bind.as_deref(), Some("[::]:443"));
1556 assert_eq!(config.unix.bind.as_deref(), Some(std::path::Path::new("/run/moq.sock")));
1557 assert_eq!(config.unix.allow.as_ref().expect("allow").uid, vec![1001, 1002]);
1558 assert!(config.has_stream_listener());
1559 assert!(config.has_explicit_bind());
1560 }
1561
1562 #[cfg(all(feature = "uds", unix))]
1563 #[test]
1564 fn stream_only_config_has_no_quic() {
1565 let mut config = ServerConfig::default();
1567 config.unix.bind = Some(PathBuf::from("/run/moq.sock"));
1568 assert!(config.has_stream_listener());
1569 assert!(config.has_explicit_bind());
1570 assert!(config.bind.is_none());
1571
1572 assert!(!ServerConfig::default().has_stream_listener());
1574 assert!(!ServerConfig::default().has_explicit_bind());
1575 }
1576}