Skip to main content

public_stun/
lib.rs

1//! A list of public STUN servers in crate form.
2//!
3//! Based on the public STUN server list maintained by [pradt2](https://github.com/pradt2/always-online-stun).
4//!
5//! ## Usage
6//! The crate supports to ways to get the list of STUN servers:
7//!
8//! Packaged servers:
9//! ```rust
10//! // The list is packaged with the crate and may be outdated.
11//! let servers = public_stun::packaged::list_servers();
12//! ```
13//!
14//! Fetched servers:
15//! ```rust
16//! // The list is fetched from the source repository and is always up to date.
17//! # #[tokio::main(flavor = "current_thread")]
18//! # async fn main() {
19//! # #[cfg(feature = "fetched")]
20//! let servers = public_stun::fetched::list_servers().await.unwrap();
21//! # }
22//! ```
23
24#![warn(clippy::pedantic)]
25#![allow(clippy::missing_panics_doc)]
26#![cfg_attr(docsrs, feature(doc_cfg))]
27
28use core::{
29    fmt::{self, Display},
30    str::FromStr,
31};
32use std::io;
33
34use enumflags2::{BitFlags, bitflags};
35use thiserror::Error;
36
37/// The required capabilities of a STUN server.
38#[bitflags]
39#[repr(u8)]
40#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
41pub enum Capability {
42    /// The server supports NAT detection and behaviour testing (roughly correspond to RFC5780).
43    NatTesting,
44    /// The server supports STUN via TCP.
45    TcpSupport,
46}
47
48/// A set of [`Capability`]s.
49pub type Capabilities = BitFlags<Capability>;
50
51/// The hostname and port of a STUN server.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct Server {
54    /// The hostname of the STUN server.
55    pub host: String,
56    /// The port of the STUN server.
57    pub port: u16,
58}
59
60impl Display for Server {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "{}:{}", self.host, self.port)
63    }
64}
65
66impl std::net::ToSocketAddrs for Server {
67    type Iter = <(String, u16) as std::net::ToSocketAddrs>::Iter;
68    fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
69        (self.host.as_str(), self.port).to_socket_addrs()
70    }
71}
72
73/// The error returned when parsing a [`Server`] from a [`str`].
74#[derive(Debug, Error)]
75pub enum InvalidFormat {
76    #[error("missing ':' in source")]
77    MissingDelimiter,
78    #[error("port number is not a valid u16")]
79    InvalidPort,
80}
81
82impl FromStr for Server {
83    type Err = InvalidFormat;
84
85    fn from_str(s: &str) -> Result<Self, Self::Err> {
86        let Some((host, port)) = s.split_once(':') else {
87            return Err(InvalidFormat::MissingDelimiter);
88        };
89        let host = host.to_string();
90        let Ok(port) = port.parse() else {
91            return Err(InvalidFormat::InvalidPort);
92        };
93        Ok(Self { host, port })
94    }
95}
96
97#[cfg(any(feature = "packaged", feature = "fetched"))]
98pub(crate) fn parse_raw_server_list(text: &str) -> Result<Vec<Server>, InvalidFormat> {
99    text.lines().map(|line| line.trim().parse()).collect()
100}
101
102/// Module containing the packaged version of the server list.
103#[cfg(feature = "packaged")]
104pub mod packaged;
105
106/// Module containing the fetched (on-demand) version of the server list.
107#[cfg(feature = "fetched")]
108pub mod fetched;