Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ serde = { version = "1.0.152", features = ["derive"] }
dimpl = { version = "0.2.4", default-features = false }

str0m-proto = { version = "0.1.2", path = "proto" }
str0m-ice = { version = "0.1.0", path = "ice" }

# Crypto providers
str0m-aws-lc-rs = { version = "0.1.2", path = "crypto/aws-lc-rs", optional = true }
Expand Down
29 changes: 29 additions & 0 deletions ice/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "str0m-ice"
version = "0.1.0"
authors = ["Martin Algesten <martin@algesten.se>", "Hugo Tunius <h@tunius.se>", "Davide Bertola <dade@dadeb.it>"]
description = "ICE (Interactive Connectivity Establishment) implementation for str0m"
license = "MIT OR Apache-2.0"
repository = "https://github.com/algesten/str0m"
readme = "README.md"
edition = "2021"

# MSRV
rust-version = "1.81.0"

[features]
default = []
_internal_test_exports = []
# Redacts personally identifiable information (PII) from logs debug and above
pii = []

[dependencies]
tracing = "0.1.37"
fastrand = "2.0.1"
subtle = "2.0.0"
combine = "4.6.6"
serde = { version = "1.0.152", features = ["derive"] }
crc = ">=3.0, <3.4"
str0m-proto = { version = "0.1.2", path = "../proto" }

[dev-dependencies]
38 changes: 20 additions & 18 deletions src/ice/agent.rs → ice/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ use std::sync::Arc;
use std::time::{Duration, Instant};

use serde::{Deserialize, Serialize};
use tracing::{debug, trace, warn};

use crate::crypto::Sha1HmacProvider;
use crate::ice_::preference::default_local_preference;
use crate::io::{Id, StunClass, StunMethod, StunTiming, DATAGRAM_MTU_WARN};
use crate::io::Transmit;
use crate::io::{Protocol, StunPacket};
use crate::io::{StunMessage, TransId};
use crate::io::{Transmit, DATAGRAM_MTU};
use crate::util::{NonCryptographicRng, Pii};
use crate::preference::default_local_preference;
use crate::stun::{Class, Method, StunTiming};

use super::candidate::{Candidate, CandidateKind};
use super::pair::{CandidatePair, CheckState, PairId};
use str0m_proto::crypto::Sha1HmacProvider;
use str0m_proto::{Id, NonCryptographicRng, Pii, DATAGRAM_MTU, DATAGRAM_MTU_WARN};

use crate::candidate::{Candidate, CandidateKind};
use crate::pair::{CandidatePair, CheckState, PairId};

/// Handles the ICE protocol for a given peer.
///
Expand Down Expand Up @@ -970,13 +972,13 @@ impl IceAgent {
let method = message.method();
let class = message.class();
match (method, class) {
(StunMethod::Binding, StunClass::Indication) => {
(Method::Binding, Class::Indication) => {
// https://datatracker.ietf.org/doc/html/rfc8489#section-6.3.2
// An Indication can be safely ignored, its purpose is to refresh NATs in the
// network path. Some clients MAY omit USERNAME attribute.
false
}
(StunMethod::Binding, StunClass::Request) => {
(Method::Binding, Class::Request) => {
// The username for the credential is formed by concatenating the
// username fragment provided by the peer with the username fragment of
// the ICE agent sending the request, separated by a colon (":").
Expand Down Expand Up @@ -1006,7 +1008,7 @@ impl IceAgent {

do_integrity_check(true)
}
(StunMethod::Binding, StunClass::Success | StunClass::Failure) => {
(Method::Binding, Class::Success | Class::Failure) => {
let belongs_to_a_candidate_pair = self
.candidate_pairs
.iter()
Expand All @@ -1019,23 +1021,23 @@ impl IceAgent {

do_integrity_check(false)
}
(StunMethod::Binding, StunClass::Unknown) => {
(Method::Binding, Class::Unknown) => {
// Without a known class, it's impossible to know how to validate the message
trace!("Message rejected, unknown STUN class");
false
}
(StunMethod::Unknown, _) => {
(Method::Unknown, _) => {
// Without a known method, it's impossible to know how to validate the message
trace!("Message rejected, unknown STUN method");
false
}
(
StunMethod::Allocate
| StunMethod::Refresh
| StunMethod::Send
| StunMethod::Data
| StunMethod::CreatePermission
| StunMethod::ChannelBind,
Method::Allocate
| Method::Refresh
| Method::Send
| Method::Data
| Method::CreatePermission
| Method::ChannelBind,
_,
) => {
// Unexpected TURN related message
Expand Down
18 changes: 12 additions & 6 deletions src/ice/candidate.rs → ice/src/candidate.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::IceError;
use crate::error::IceError;
use crate::io::{Protocol, TcpType};
use crate::sdp::parse_candidate;
use combine::Parser;
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize, Serializer};
use std::collections::hash_map::DefaultHasher;
Expand Down Expand Up @@ -320,7 +320,10 @@ impl Candidate {

/// Creates a new ICE candidate from a string.
pub fn from_sdp_string(s: &str) -> Result<Self, IceError> {
parse_candidate(s).map_err(|e| IceError::BadCandidate(format!("{}: {}", s, e)))
crate::sdp::candidate()
.parse(s)
.map(|(c, _)| c)
.map_err(|e| IceError::BadCandidate(format!("{}: {}", s, e)))
}

/// Creates a peer reflexive ICE candidate.
Expand Down Expand Up @@ -533,15 +536,18 @@ impl Candidate {
self.discarded
}

pub(crate) fn set_ufrag(&mut self, ufrag: &str) {
#[doc(hidden)] // Private API.
pub fn set_ufrag(&mut self, ufrag: &str) {
self.ufrag = Some(ufrag.into());
}

pub(crate) fn ufrag(&self) -> Option<&str> {
#[doc(hidden)] // Private API.
pub fn ufrag(&self) -> Option<&str> {
self.ufrag.as_deref()
}

pub(crate) fn clear_ufrag(&mut self) {
#[doc(hidden)] // Private API.
pub fn clear_ufrag(&mut self) {
self.ufrag = None;
}

Expand Down
17 changes: 17 additions & 0 deletions src/io/error.rs → ice/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@ use std::error::Error;
use std::fmt;
use std::io;

/// Errors from the ICE agent.
#[allow(missing_docs)]
#[derive(Debug)]
pub enum IceError {
BadCandidate(String),
}

impl fmt::Display for IceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IceError::BadCandidate(msg) => write!(f, "ICE bad candidate: {}", msg),
}
}
}

impl Error for IceError {}

/// A STUN message could not be parsed or processed.
#[derive(Debug)]
pub enum StunError {
Expand Down
19 changes: 19 additions & 0 deletions ice/src/io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Network I/O types and STUN protocol implementation.

use std::net::SocketAddr;

pub use crate::stun::{StunMessage, TransId};
pub use str0m_proto::{Protocol, TcpType, Transmit};

/// An incoming STUN packet.
#[derive(Debug)]
pub struct StunPacket<'a> {
/// The protocol the socket this received data originated from is using.
pub proto: Protocol,
/// The socket this received data originated from.
pub source: SocketAddr,
/// The destination socket of the datagram.
pub destination: SocketAddr,
/// The STUN message.
pub message: StunMessage<'a>,
}
65 changes: 65 additions & 0 deletions ice/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! ICE (Interactive Connectivity Establishment) implementation for str0m.
//!
//! This crate provides a Sans-I/O implementation of the ICE protocol for establishing
//! peer-to-peer connections through NATs and firewalls.
//!
//! # Overview
//!
//! ICE is a protocol for establishing peer-to-peer connections through NATs and firewalls.
//! This implementation follows RFC 8445 (ICE) and RFC 8838 (Trickle ICE).
//!
//! The main entry point is the [`IceAgent`] which handles:
//! - Managing local and remote candidates
//! - Performing connectivity checks
//! - Nominating candidate pairs
//! - Handling STUN binding requests and responses
//!
//! # Example
//!
//! ```no_run
//! use str0m_ice::{IceAgent, IceCreds, Candidate};
//! use std::time::Instant;
//!
//! // Create an ICE agent
//! let mut agent = IceAgent::new(Instant::now());
//!
//! // Set credentials
//! let creds = IceCreds::new();
//! agent.set_local_credentials(creds);
//!
//! // Add a local candidate
//! let addr = "192.168.1.100:5000".parse().unwrap();
//! let candidate = Candidate::host(addr, "udp").unwrap();
//! agent.add_local_candidate(candidate);
//! ```

#![allow(clippy::new_without_default)]
#![allow(clippy::bool_to_int_with_if)]

mod agent;
mod candidate;
mod error;
mod io;
mod pair;
mod preference;
mod sdp;

pub mod stun;

// Re-export common types from str0m-proto
pub use str0m_proto::{NonCryptographicRng, Pii, Protocol, TcpType, Transmit};

// Re-export crypto traits from str0m-proto
pub use str0m_proto::crypto::Sha1HmacProvider;

pub use agent::{
IceAgent, IceAgentEvent, IceAgentStats, IceConnectionState, IceCreds, LocalPreference,
};
pub use candidate::{Candidate, CandidateBuilder, CandidateKind};
pub use error::{IceError, NetError, StunError};
pub use io::StunPacket;
pub use preference::default_local_preference;
pub use stun::{StunMessage, StunMessageBuilder, TransId};

#[doc(hidden)]
pub use sdp::candidate;
8 changes: 5 additions & 3 deletions src/ice/pair.rs → ice/src/pair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use std::collections::VecDeque;
use std::fmt;
use std::time::{Duration, Instant};

use crate::io::{Id, StunTiming, TransId, DEFAULT_MAX_RETRANSMITS};
use tracing::{debug, trace};

use crate::stun::{StunTiming, TransId, DEFAULT_MAX_RETRANSMITS};
use crate::Candidate;
use crate::Pii;
use str0m_proto::{Id, Pii};

use super::CandidateKind;
use crate::candidate::CandidateKind;

// When running ice-lite we need a cutoff when we consider the remote definitely gone.
const RECENT_BINDING_REQUEST: Duration = Duration::from_secs(15);
Expand Down
File renamed without changes.
Loading
Loading