├── .gitignore ├── fuzz ├── .gitignore ├── corpus │ └── parse_candidate │ │ └── candidate1 ├── Cargo.toml └── fuzz_targets │ └── parse_candidate.rs ├── rice-io ├── src │ └── lib.rs ├── cbindgen.toml └── Cargo.toml ├── rice-ctypes ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── .github └── workflows │ ├── rust-fmt.yml │ ├── rust-doc.yml │ ├── deny.yml │ ├── rust-fuzz.yml │ ├── cbuild.yml │ ├── rust-coverage.yml │ └── rust.yml ├── rice-proto ├── cbindgen.toml ├── src │ ├── rand.rs │ ├── tcp.rs │ ├── turn.rs │ └── lib.rs ├── check_meson_wraps.py ├── Cargo.toml ├── README.md └── benches │ └── sendrecv.rs ├── rice-stun-types ├── Cargo.toml ├── README.md └── src │ ├── attribute │ ├── mod.rs │ ├── use_candidate.rs │ ├── priority.rs │ └── tie_breaker.rs │ └── lib.rs ├── LICENSE-MIT ├── librice ├── src │ ├── utils.rs │ ├── gathering.rs │ ├── lib.rs │ ├── runtime │ │ ├── tokio.rs │ │ ├── smol.rs │ │ └── mod.rs │ ├── component.rs │ └── socket.rs ├── Cargo.toml ├── README.md ├── tests │ ├── stund.rs │ └── common │ │ └── mod.rs └── examples │ └── icegather.rs ├── rice-c ├── Cargo.toml ├── src │ ├── ffi.rs │ ├── component.rs │ ├── lib.rs │ └── turn.rs ├── README.md └── build.rs ├── Cargo.toml ├── deny.toml ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.swp 3 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/corpus/parse_candidate/candidate1: -------------------------------------------------------------------------------- 1 | candidate 1 1 UDP 123456789 192.168.1.2 54321 host 2 | -------------------------------------------------------------------------------- /rice-io/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #![deny(missing_debug_implementations)] 10 | 11 | mod capi; 12 | -------------------------------------------------------------------------------- /rice-ctypes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rice-ctypes" 3 | description = "Internal helper crate for providing C types for the rice API" 4 | version.workspace = true 5 | authors = ["Matthew Waters "] 6 | license = "MIT OR Apache-2.0" 7 | keywords = ["STUN", "ICE"] 8 | categories = ["network-programming", ] 9 | documentation = "https://docs.rs/rice-ctypes" 10 | edition.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | workspace = ".." 14 | -------------------------------------------------------------------------------- /.github/workflows/rust-fmt.yml: -------------------------------------------------------------------------------- 1 | name: Rust Format 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | cargo_fmt: 14 | name: Cargo format 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: stable 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: fmt 24 | args: -- --check 25 | -------------------------------------------------------------------------------- /rice-proto/cbindgen.toml: -------------------------------------------------------------------------------- 1 | header = "// SPDX-License-Identifier: MIT OR Apache-2.0" 2 | includes = [] 3 | include_guard = "LIBRICE_PROTO_H" 4 | tab_width = 4 5 | language = "C" 6 | cpp_compat = true 7 | usize_is_size_t = true 8 | 9 | [export] 10 | exclude = ["MAGIC_COOKIE", "BINDING", "RTP", "RTCP"] 11 | item_types = ["enums", "structs", "opaque", "functions"] 12 | 13 | [export.rename] 14 | "CandidateType" = "RiceCandidateType" 15 | "ComponentConnectionState" = "RiceComponentConnectionState" 16 | 17 | [fn] 18 | args = "vertical" 19 | 20 | [enum] 21 | rename_variants = "QualifiedScreamingSnakeCase" 22 | -------------------------------------------------------------------------------- /rice-io/cbindgen.toml: -------------------------------------------------------------------------------- 1 | header = "// SPDX-License-Identifier: MIT OR Apache-2.0" 2 | includes = ["rice-proto.h"] 3 | include_guard = "LIBRICE_IO_H" 4 | tab_width = 4 5 | language = "C" 6 | cpp_compat = true 7 | usize_is_size_t = true 8 | 9 | [export] 10 | exclude = ["MAGIC_COOKIE", "BINDING", "RTP", "RTCP"] 11 | item_types = ["enums", "structs", "opaque", "functions", "typedefs"] 12 | 13 | [export.rename] 14 | "CandidateType" = "RiceCandidateType" 15 | "ComponentConnectionState" = "RiceComponentConnectionState" 16 | 17 | [fn] 18 | args = "vertical" 19 | 20 | [enum] 21 | rename_variants = "QualifiedScreamingSnakeCase" 22 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "rice-fuzz" 4 | version.workspace = true 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition.workspace = true 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | arbitrary.workspace = true 14 | libfuzzer-sys = "0.4" 15 | tracing.workspace = true 16 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 17 | 18 | [dependencies.rice-proto] 19 | path = "../rice-proto" 20 | features = ["arbitrary"] 21 | 22 | [[bin]] 23 | name = "parse_candidate" 24 | path = "fuzz_targets/parse_candidate.rs" 25 | test = false 26 | doc = false 27 | -------------------------------------------------------------------------------- /.github/workflows/rust-doc.yml: -------------------------------------------------------------------------------- 1 | name: Rust docs 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUSTDOCFLAGS: -Dwarnings 12 | 13 | jobs: 14 | cargo_doc: 15 | name: Cargo doc 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | - name: Install cargo-c 23 | uses: taiki-e/cache-cargo-install-action@v2 24 | with: 25 | tool: cargo-c 26 | - uses: actions-rs/cargo@v1 27 | with: 28 | command: doc 29 | args: --no-deps 30 | -------------------------------------------------------------------------------- /.github/workflows/deny.yml: -------------------------------------------------------------------------------- 1 | name: Deny 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | cargo-deny: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | checks: 18 | - advisories 19 | - bans licenses sources 20 | 21 | # Prevent sudden announcement of a new advisory from failing ci: 22 | continue-on-error: ${{ matrix.checks == 'advisories' }} 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: EmbarkStudios/cargo-deny-action@v2 27 | with: 28 | command: check ${{ matrix.checks }} 29 | arguments: --all-features 30 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_candidate.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | #[macro_use] 5 | extern crate tracing; 6 | use tracing_subscriber::EnvFilter; 7 | 8 | use rice_proto::candidate::*; 9 | 10 | use std::{str::FromStr, sync::Once}; 11 | 12 | #[derive(arbitrary::Arbitrary, Debug)] 13 | struct Data<'data> { 14 | data: &'data str, 15 | } 16 | 17 | pub fn debug_init() { 18 | static TRACING: Once = Once::new(); 19 | 20 | TRACING.call_once(|| { 21 | if let Ok(filter) = EnvFilter::try_from_default_env() { 22 | tracing_subscriber::fmt().with_env_filter(filter).init(); 23 | } 24 | }); 25 | } 26 | 27 | fuzz_target!(|data: Data| { 28 | debug_init(); 29 | let res = Candidate::from_str(data.data); 30 | debug!("candidate result {:?}", res); 31 | }); 32 | -------------------------------------------------------------------------------- /.github/workflows/rust-fuzz.yml: -------------------------------------------------------------------------------- 1 | name: Quick Fuzzing 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUST_BACKTRACE: 1 12 | 13 | jobs: 14 | fuzz: 15 | name: Fuzzing 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Install nightly toolchain 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: nightly 25 | override: true 26 | 27 | - name: Install cargo-fuzz 28 | uses: taiki-e/cache-cargo-install-action@v2 29 | with: 30 | tool: cargo-fuzz 31 | 32 | - name: Run cargo-fuzz 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: 'fuzz' 36 | args: 'run parse_candidate -- -max_total_time=20' 37 | 38 | -------------------------------------------------------------------------------- /rice-stun-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rice-stun-types" 3 | description = "STUN types for the ICE (RFC8445) protocol" 4 | version.workspace = true 5 | authors = ["Matthew Waters "] 6 | license = "MIT OR Apache-2.0" 7 | keywords = ["STUN", "ICE"] 8 | categories = ["network-programming", ] 9 | documentation = "https://docs.rs/rice-stun-types" 10 | edition.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | workspace = ".." 14 | 15 | [features] 16 | default = ["std"] 17 | std = ["stun-types/std"] 18 | 19 | [dependencies] 20 | arbitrary = { workspace = true, optional = true } 21 | byteorder.workspace = true 22 | rand.workspace = true 23 | stun-types.workspace = true 24 | tracing.workspace = true 25 | tracing-subscriber = { workspace = true, optional = true } 26 | 27 | [dev-dependencies] 28 | tracing = { workspace = true, features = ["std"] } 29 | tracing-subscriber.workspace = true 30 | -------------------------------------------------------------------------------- /.github/workflows/cbuild.yml: -------------------------------------------------------------------------------- 1 | name: C Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Install cargo-c 18 | uses: taiki-e/cache-cargo-install-action@v2 19 | with: 20 | tool: cargo-c 21 | - name: CBuild rice-proto 22 | run: cargo cbuild -p rice-proto --verbose 23 | - name: CBuild rice-io 24 | run: cargo cbuild -p rice-io --verbose 25 | test: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Install cargo-c 30 | uses: taiki-e/cache-cargo-install-action@v2 31 | with: 32 | tool: cargo-c 33 | - name: CTest rice-proto 34 | run: cargo ctest -p rice-proto --verbose 35 | - name: CTest rice-io 36 | run: cargo ctest -p rice-io --verbose 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /librice/src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[derive(Clone)] 10 | pub(crate) struct DebugWrapper(&'static str, T); 11 | 12 | impl std::fmt::Debug for DebugWrapper { 13 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 14 | write!(f, "{}", self.0) 15 | } 16 | } 17 | impl std::ops::Deref for DebugWrapper { 18 | type Target = T; 19 | fn deref(&self) -> &Self::Target { 20 | &self.1 21 | } 22 | } 23 | impl std::ops::DerefMut for DebugWrapper { 24 | fn deref_mut(&mut self) -> &mut Self::Target { 25 | &mut self.1 26 | } 27 | } 28 | impl DebugWrapper { 29 | pub(crate) fn wrap(obj: T, name: &'static str) -> Self { 30 | Self(name, obj) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rice-c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rice-c" 3 | description = "ICE (RFC8445) implementation protocol" 4 | version.workspace = true 5 | authors = ["Matthew Waters "] 6 | license = "MIT OR Apache-2.0" 7 | keywords = ["STUN", "ICE"] 8 | categories = ["network-programming", ] 9 | documentation = "https://docs.rs/rice-c" 10 | edition.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | workspace = ".." 14 | 15 | [features] 16 | default = ["rustls", "openssl"] 17 | rustls = [] 18 | openssl = [] 19 | 20 | [build-dependencies] 21 | system-deps = "7" 22 | bindgen = "0.72" 23 | 24 | [dependencies] 25 | tracing.workspace = true 26 | sans-io-time = "0.1" 27 | thiserror = { workspace = true, features = ["std"] } 28 | 29 | [dev-dependencies] 30 | tracing = { workspace = true, features = ["std"] } 31 | tracing-subscriber.workspace = true 32 | 33 | [package.metadata.system-deps] 34 | rice-proto = "0.2.1" 35 | 36 | [package.metadata.docs.rs] 37 | all-features = true 38 | rustc-args = ["--cfg", "docsrs"] 39 | rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] 40 | -------------------------------------------------------------------------------- /librice/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "librice" 3 | description = "ICE (RFC8445) implementation" 4 | version.workspace = true 5 | authors = ["Matthew Waters "] 6 | license = "MIT OR Apache-2.0" 7 | keywords = ["STUN", "ICE"] 8 | categories = ["network-programming", ] 9 | documentation = "https://docs.rs/librice" 10 | edition.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | workspace = ".." 14 | 15 | [features] 16 | default = ["runtime-smol", "runtime-tokio"] 17 | runtime-smol = ["dep:smol", "dep:async-io"] 18 | runtime-tokio = ["dep:tokio"] 19 | 20 | [dependencies] 21 | rice-c.workspace = true 22 | byteorder.workspace = true 23 | get_if_addrs.workspace = true 24 | futures = "0.3" 25 | futures-timer = "3" 26 | rand.workspace = true 27 | tracing.workspace = true 28 | tracing-futures = { version = "0.2", default-features = false, features = ["std", "std-future", "futures-03"] } 29 | tracing-subscriber.workspace = true 30 | 31 | smol = { version = "2", optional = true } 32 | async-io = { version = "2", optional = true } 33 | 34 | tokio = { version = "1", optional = true, features = ["net", "time", "rt-multi-thread"] } 35 | 36 | [dev-dependencies] 37 | stun-proto.workspace = true 38 | turn-server-proto = { workspace = true, features = ["std"] } 39 | clap = { version = "4", features = ["derive"]} 40 | -------------------------------------------------------------------------------- /rice-proto/src/rand.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use alloc::string::String; 10 | 11 | use rand::prelude::*; 12 | 13 | pub(crate) fn generate_random_ice_string(alphabet: &[u8], length: usize) -> String { 14 | #[cfg(not(feature = "std"))] 15 | { 16 | use rand::TryRngCore; 17 | let mut rng = rand::rngs::OsRng.unwrap_err(); 18 | String::from_iter((0..length).map(|_| *alphabet.choose(&mut rng).unwrap() as char)) 19 | } 20 | #[cfg(feature = "std")] 21 | { 22 | let mut rng = rand::rng(); 23 | String::from_iter((0..length).map(|_| *alphabet.choose(&mut rng).unwrap() as char)) 24 | } 25 | } 26 | 27 | pub(crate) fn rand_u64() -> u64 { 28 | #[cfg(not(feature = "std"))] 29 | { 30 | use rand::Rng; 31 | use rand::TryRngCore; 32 | let mut rng = rand::rngs::OsRng.unwrap_err(); 33 | rng.random() 34 | } 35 | #[cfg(feature = "std")] 36 | { 37 | use rand::Rng; 38 | let mut rng = rand::rng(); 39 | rng.random() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/rust-coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | coverage: 14 | name: Coverage 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Install nightly toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: nightly 24 | override: true 25 | 26 | - name: Install cargo-tarpaulin 27 | uses: taiki-e/cache-cargo-install-action@v2 28 | with: 29 | tool: cargo-tarpaulin@0.32 30 | 31 | - name: Install cargo-c 32 | uses: taiki-e/cache-cargo-install-action@v2 33 | with: 34 | tool: cargo-c 35 | 36 | - name: Run cargo-tarpaulin 37 | uses: actions-rs/cargo@v1 38 | with: 39 | command: 'tarpaulin' 40 | args: '--out Xml --lib --tests --doc -- --test-threads 1' 41 | 42 | - name: Upload to codecov.io 43 | uses: codecov/codecov-action@v5 44 | with: 45 | token: ${{secrets.CODECOV_TOKEN}} 46 | 47 | - name: Archive code coverage results 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: code-coverage-report 51 | path: cobertura.xml 52 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install cargo-c 20 | uses: taiki-e/cache-cargo-install-action@v2 21 | with: 22 | tool: cargo-c 23 | - name: Build 24 | run: cargo build --verbose 25 | - name: Build (No default features) 26 | run: cargo build --verbose --no-default-features 27 | test: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | - name: Install cargo-c 32 | uses: taiki-e/cache-cargo-install-action@v2 33 | with: 34 | tool: cargo-c 35 | - name: Run tests 36 | run: cargo test --verbose 37 | - name: Run tests (No default features + Tokio) 38 | run: cargo test --verbose --no-default-features --features runtime-tokio 39 | - name: Run tests (No default features + Smol) 40 | run: cargo test --verbose --no-default-features --features runtime-smol 41 | clippy: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v2 45 | - name: Install cargo-c 46 | uses: taiki-e/cache-cargo-install-action@v2 47 | with: 48 | tool: cargo-c 49 | - name: clippy 50 | run: cargo clippy --verbose -- -Dwarnings 51 | -------------------------------------------------------------------------------- /rice-io/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rice-io" 3 | description = "ICE (RFC8445) implementation protocol" 4 | version.workspace = true 5 | authors = ["Matthew Waters "] 6 | license = "MIT OR Apache-2.0" 7 | keywords = ["STUN", "ICE"] 8 | categories = ["network-programming", ] 9 | #documentation = "https://docs.rs/rice-io" 10 | edition.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | workspace = ".." 14 | 15 | [features] 16 | capi = [] 17 | 18 | [dependencies] 19 | arbitrary = { workspace = true, optional = true } 20 | async-io = "2" 21 | async-lock = "3" 22 | async-task = "4" 23 | byteorder.workspace = true 24 | flume = "0.12" 25 | futures-lite = "2" 26 | get_if_addrs.workspace = true 27 | libc = "0.2" 28 | rice-ctypes.workspace = true 29 | stun-proto.workspace = true 30 | tracing.workspace = true 31 | tracing-subscriber.workspace = true 32 | 33 | [dev-dependencies] 34 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 35 | 36 | [lib] 37 | crate-type = ["cdylib", "staticlib"] 38 | 39 | [package.metadata.capi] 40 | min_version = "0.9.21" 41 | 42 | [package.metadata.capi.header] 43 | subdirectory = "rice" 44 | name = "rice-io" 45 | 46 | [package.metadata.capi.library] 47 | name = "rice-io" 48 | version_suffix_components = 1 49 | rustflags = "-Cpanic=abort" 50 | 51 | [package.metadata.capi.pkg_config] 52 | name = "rice-io" 53 | filename = "rice-io" 54 | requires = "rice-proto" 55 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["rice-proto", "librice", "rice-io", "rice-c", "rice-stun-types", "rice-ctypes", "fuzz"] 3 | default-members = ["librice", "rice-proto", "rice-c", "rice-stun-types", "rice-ctypes"] 4 | resolver = "2" 5 | 6 | [workspace.package] 7 | version = "0.2.1" 8 | repository = "https://github.com/ystreet/librice" 9 | edition = "2024" 10 | rust-version = "1.85.0" 11 | 12 | [workspace.dependencies] 13 | smallvec = "1" 14 | stun-proto = { version = "1.0.0", default-features = false } 15 | stun-types = { version = "1.0.0", default-features = false } 16 | turn-client-proto = { version = "0.4.1", default-features = false } 17 | turn-server-proto = { version = "0.4.1", default-features = false } 18 | thiserror = { version = "2", default-features = false } 19 | arbitrary = { version = "1", features = ["derive"] } 20 | byteorder = { version = "1", default-features = false } 21 | get_if_addrs = "0.5" 22 | rand = { version = "0.9", default-features = false, features = ["os_rng"] } 23 | tracing = { version = "0.1", default-features = false, features = ["attributes"] } 24 | tracing-subscriber = "0.3" 25 | rice-proto = { version = "0.2.1", path = "rice-proto" } 26 | rice-c = { version = "0.2.1", path = "rice-c" } 27 | rice-stun-types = { version = "0.2.1", path = "rice-stun-types", default-features = false } 28 | rice-ctypes = { version = "0.2.1", path = "rice-ctypes" } 29 | criterion = "0.6" 30 | 31 | [profile.bench] 32 | codegen-units = 16 33 | lto = "thin" 34 | -------------------------------------------------------------------------------- /rice-ctypes/README.md: -------------------------------------------------------------------------------- 1 | [![Chat](https://img.shields.io/matrix/librice-general:matrix.org?logo=matrix)](https://matrix.to/#/#librice-general:matrix.org) 2 | [![Build status](https://github.com/ystreet/librice/workflows/Build/badge.svg?branch=main)](https://github.com/ystreet/librice/actions) 3 | [![codecov](https://codecov.io/gh/ystreet/librice/branch/main/graph/badge.svg)](https://codecov.io/gh/ystreet/librice) 4 | [![Dependencies](https://deps.rs/repo/github/ystreet/librice/status.svg)](https://deps.rs/repo/github/ystreet/librice) 5 | [![crates.io](https://img.shields.io/crates/v/rice-stun-types.svg)](https://crates.io/crates/rice-stun-types) 6 | [![docs.rs](https://docs.rs/rice-stun-types/badge.svg)](https://docs.rs/rice-stun-types) 7 | 8 | # rice-stun-types 9 | 10 | Implementation of ICE-relevant STUN attributes based on [stun-types] as specified in [RFC8445], 11 | [RFC6544], and [RFC5245]. 12 | 13 | ## Relevant standards 14 | 15 | - [x] [RFC5245]: Interactive Connectivity Establishment (ICE): A Protocol for Network Address 16 | Translator (NAT) Traversal for Offer/Answer Protocols 17 | - [x] [RFC6544]: TCP Candidates with Interactive Connectivity Establishment (ICE) 18 | - [x] [RFC8445]: Interactive Connectivity Establishment (ICE): A Protocol 19 | for Network Address Translator (NAT) Traversal 20 | 21 | [RFC5245]: 22 | [RFC6544]: 23 | [RFC8445]: 24 | [stun-types]: https://docs.rs/stun-types 25 | -------------------------------------------------------------------------------- /rice-stun-types/README.md: -------------------------------------------------------------------------------- 1 | [![Chat](https://img.shields.io/matrix/librice-general:matrix.org?logo=matrix)](https://matrix.to/#/#librice-general:matrix.org) 2 | [![Build status](https://github.com/ystreet/librice/workflows/Build/badge.svg?branch=main)](https://github.com/ystreet/librice/actions) 3 | [![codecov](https://codecov.io/gh/ystreet/librice/branch/main/graph/badge.svg)](https://codecov.io/gh/ystreet/librice) 4 | [![Dependencies](https://deps.rs/repo/github/ystreet/librice/status.svg)](https://deps.rs/repo/github/ystreet/librice) 5 | [![crates.io](https://img.shields.io/crates/v/rice-stun-types.svg)](https://crates.io/crates/rice-stun-types) 6 | [![docs.rs](https://docs.rs/rice-stun-types/badge.svg)](https://docs.rs/rice-stun-types) 7 | 8 | # rice-stun-types 9 | 10 | Implementation of ICE-relevant STUN attributes based on [stun-types] as specified in [RFC8445], 11 | [RFC6544], and [RFC5245]. 12 | 13 | ## Relevant standards 14 | 15 | - [x] [RFC5245]: Interactive Connectivity Establishment (ICE): A Protocol for Network Address 16 | Translator (NAT) Traversal for Offer/Answer Protocols 17 | - [x] [RFC6544]: TCP Candidates with Interactive Connectivity Establishment (ICE) 18 | - [x] [RFC8445]: Interactive Connectivity Establishment (ICE): A Protocol 19 | for Network Address Translator (NAT) Traversal 20 | 21 | [RFC5245]: 22 | [RFC6544]: 23 | [RFC8445]: 24 | [stun-types]: https://docs.rs/stun-types 25 | -------------------------------------------------------------------------------- /rice-stun-types/src/attribute/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! # Attributes 10 | //! 11 | //! The list of STUN attributes relevant for ICE as specified in [RFC8445], [RFC6544], and [RFC5245]. 12 | //! 13 | //! [RFC5245]: 14 | //! [RFC6544]: 15 | //! [RFC8445]: 16 | 17 | mod priority; 18 | pub use priority::Priority; 19 | mod use_candidate; 20 | pub use use_candidate::UseCandidate; 21 | mod tie_breaker; 22 | pub use tie_breaker::{IceControlled, IceControlling}; 23 | 24 | pub(super) fn debug_init() { 25 | #[cfg(feature = "std")] 26 | { 27 | use stun_types::prelude::*; 28 | 29 | stun_types::attribute_display!(IceControlled); 30 | IceControlled::TYPE.add_name("IceControlled"); 31 | stun_types::attribute_display!(IceControlling); 32 | IceControlling::TYPE.add_name("IceControlling"); 33 | stun_types::attribute_display!(Priority); 34 | Priority::TYPE.add_name("Priority"); 35 | stun_types::attribute_display!(UseCandidate); 36 | UseCandidate::TYPE.add_name("UseCandidate"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | all-features = false 3 | no-default-features = false 4 | exclude-unpublished = true 5 | 6 | [advisories] 7 | db-urls = ["https://github.com/rustsec/advisory-db"] 8 | 9 | [licenses] 10 | allow = [ 11 | "MIT", 12 | "Apache-2.0", 13 | "Apache-2.0 WITH LLVM-exception", 14 | "CDLA-Permissive-2.0", 15 | "BSD-3-Clause", 16 | "Unicode-3.0", 17 | "ISC", 18 | "OpenSSL", 19 | "Zlib", 20 | ] 21 | confidence-threshold = 0.9 22 | 23 | [bans] 24 | multiple-versions = "deny" 25 | wildcards = "allow" 26 | highlight = "all" 27 | skip-tree = [ 28 | # FIXME: replace get_if_addrs 29 | { crate = "get_if_addrs", reason = "Outdated dependency that needs replacing" }, 30 | { crate = "bindgen", reason = "Build dependency only" }, 31 | { crate = "windows-sys@0.45", reason = "a foundational crate for many that bumps far too frequently to ever have a shared version" }, 32 | { crate = "windows-sys@0.52", reason = "a foundational crate for many that bumps far too frequently to ever have a shared version" }, 33 | { crate = "windows-sys@0.59", reason = "a foundational crate for many that bumps far too frequently to ever have a shared version" }, 34 | { crate = "windows-sys@0.60", reason = "a foundational crate for many that bumps far too frequently to ever have a shared version" }, 35 | { crate = "getrandom@0.2", reason = "flume depends on an old version of getrandom" }, 36 | { crate = "thiserror@1", reason = "jni depends on this version" }, 37 | { crate = "thiserror-impl@1", reason = "jni depends on this version" }, 38 | ] 39 | 40 | [sources] 41 | unknown-registry = "deny" 42 | unknown-git = "deny" 43 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 44 | -------------------------------------------------------------------------------- /rice-proto/check_meson_wraps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os, subprocess 4 | import pathlib, configparser 5 | 6 | def parse_cargo_tree_line(line): 7 | components = line.split() 8 | # FIXME doesn't support more than single digit depths 9 | depth = int(components[0][0]) 10 | name = components[0][1:] 11 | version = components[1][1:] 12 | features = [] 13 | if len(components) > 2 and components[2][0] != '(': 14 | features = components[2].split(',') 15 | print(depth, name, version, features) 16 | return (name, version, features) 17 | 18 | CRATES_URL_TEMPL = "https://crates.io/api/v1/crates/{name}/{version}/download" 19 | 20 | def main(): 21 | crates_features = subprocess.run(["cargo", "tree", "-f", "{p} {f}", "-p", "rice-proto", "-e", "normal", "--prefix", "depth"], capture_output=True, check=True, text=True).stdout 22 | crates = {} 23 | for line in crates_features.splitlines(): 24 | (name, version, features) = parse_cargo_tree_line(line) 25 | if name != 'rice-proto': 26 | if name in crates: 27 | assert(crates[name] == (version, features)) 28 | continue 29 | crates[name] = (version, features) 30 | wrap_file = pathlib.Path('..') / 'rice-proto' / 'subprojects' / (name + '.wrap') 31 | with wrap_file.open(mode="r") as f: 32 | wrap = configparser.ConfigParser() 33 | wrap.read_file(f) 34 | name_version = name + '-' + version 35 | assert(wrap["wrap-file"]["directory"] == name_version) 36 | assert(wrap["wrap-file"]["source_url"] == CRATES_URL_TEMPL.format(name=name, version=version)) 37 | assert(wrap["wrap-file"]["source_filename"] == name_version + '.tar.gz') 38 | assert(wrap["provide"]["dependency_names"] == name + '-rs') 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /rice-proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rice-proto" 3 | description = "ICE (RFC8445) implementation protocol" 4 | version.workspace = true 5 | authors = ["Matthew Waters "] 6 | license = "MIT OR Apache-2.0" 7 | keywords = ["STUN", "ICE"] 8 | categories = ["network-programming", ] 9 | documentation = "https://docs.rs/rice-proto" 10 | edition.workspace = true 11 | repository.workspace = true 12 | rust-version.workspace = true 13 | workspace = ".." 14 | 15 | [features] 16 | default = ["std", "rustls", "openssl"] 17 | capi = ["dep:libc", "dep:tracing-subscriber", "std", "dep:rice-ctypes"] 18 | std = ["rice-stun-types/std", "stun-proto/std", "turn-client-proto/std", "rand/thread_rng"] 19 | # allows TLS connections to TURN servers 20 | rustls = ["std", "turn-client-proto/rustls", "dep:rustls", "dep:rustls-platform-verifier"] 21 | # allows (D)TLS connections to TURN servers 22 | openssl = ["std", "turn-client-proto/openssl", "dep:openssl"] 23 | 24 | [dependencies] 25 | arbitrary = { workspace = true, optional = true } 26 | byteorder.workspace = true 27 | libc = { version = "0.2", optional = true } 28 | nom = { version = "8", default-features = false } 29 | openssl = { version = "0.10", optional = true } 30 | rand.workspace = true 31 | rice-stun-types.workspace = true 32 | rice-ctypes = { workspace = true, optional = true } 33 | rustls = { version = "0.23", optional = true } 34 | rustls-platform-verifier = { version = "0.6", optional = true } 35 | smallvec.workspace = true 36 | stun-proto.workspace = true 37 | tracing.workspace = true 38 | tracing-subscriber = { workspace = true, optional = true } 39 | turn-client-proto.workspace = true 40 | 41 | [dev-dependencies] 42 | tracing = { workspace = true, features = ["std"] } 43 | tracing-subscriber.workspace = true 44 | turn-server-proto = "0.4.0-alpha2" 45 | criterion.workspace = true 46 | 47 | [package.metadata.capi] 48 | min_version = "0.9.21" 49 | 50 | [package.metadata.capi.header] 51 | name = "rice-proto" 52 | subdirectory = "rice" 53 | 54 | [package.metadata.capi.library] 55 | name = "rice-proto" 56 | version_suffix_components = 1 57 | rustflags = "-Cpanic=abort" 58 | 59 | [package.metadata.capi.pkg_config] 60 | name = "rice-proto" 61 | filename = "rice-proto" 62 | 63 | [[bench]] 64 | name = "sendrecv" 65 | harness = false 66 | -------------------------------------------------------------------------------- /rice-c/src/ffi.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! FFI module for the raw `rice-proto` C API. 10 | 11 | #![allow(non_camel_case_types)] 12 | #![allow(non_upper_case_globals)] 13 | #![allow(unused)] 14 | #![allow(missing_debug_implementations)] 15 | #![allow(missing_docs)] 16 | 17 | use crate::mut_override; 18 | 19 | include!("bindings.rs"); 20 | 21 | impl Default for RiceStreamIncomingData { 22 | fn default() -> Self { 23 | Self { 24 | handled: false, 25 | have_more_data: false, 26 | data: RiceDataImpl { 27 | ptr: core::ptr::null_mut(), 28 | size: 0, 29 | }, 30 | } 31 | } 32 | } 33 | 34 | impl RiceDataImpl { 35 | pub(crate) fn to_c(slice: &[u8]) -> Self { 36 | Self { 37 | ptr: mut_override(slice.as_ptr()), 38 | size: slice.len(), 39 | } 40 | } 41 | } 42 | 43 | impl RiceData { 44 | pub(crate) fn to_c_owned(slice: &[u8]) -> Self { 45 | RiceData { 46 | tag: RICE_DATA_OWNED, 47 | field1: RiceData__bindgen_ty_1 { 48 | field2: core::mem::ManuallyDrop::new(RiceData__bindgen_ty_1__bindgen_ty_2 { 49 | owned: RiceDataImpl::to_c(slice), 50 | }), 51 | }, 52 | } 53 | } 54 | } 55 | 56 | impl RiceGatheredCandidate { 57 | pub(crate) unsafe fn zeroed() -> Self { 58 | unsafe { 59 | RiceGatheredCandidate { 60 | candidate: RiceCandidate::zeroed(), 61 | turn_agent: core::ptr::null_mut(), 62 | } 63 | } 64 | } 65 | } 66 | 67 | impl RiceCandidate { 68 | pub(crate) unsafe fn zeroed() -> Self { 69 | RiceCandidate { 70 | component_id: 1, 71 | candidate_type: RICE_CANDIDATE_TYPE_HOST, 72 | transport_type: RICE_TRANSPORT_TYPE_UDP, 73 | foundation: core::ptr::null_mut(), 74 | priority: 0, 75 | address: core::ptr::null(), 76 | base_address: core::ptr::null(), 77 | related_address: core::ptr::null(), 78 | tcp_type: RICE_TCP_TYPE_NONE, 79 | extensions: core::ptr::null_mut(), 80 | extensions_len: 0, 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /rice-stun-types/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #![deny(missing_debug_implementations)] 10 | #![deny(missing_docs)] 11 | 12 | //! # rice-stun-types 13 | //! 14 | //! Implementation of ICE-relevant STUN attributes based on [stun-types] as specified in [RFC8445], 15 | //! [RFC6544], and [RFC5245]. 16 | //! 17 | //! ## Relevant standards 18 | //! 19 | //! - [x] [RFC5245]: Interactive Connectivity Establishment (ICE): A Protocol for Network Address 20 | //! Translator (NAT) Traversal for Offer/Answer Protocols 21 | //! - [x] [RFC6544]: TCP Candidates with Interactive Connectivity Establishment (ICE) 22 | //! - [x] [RFC8445]: Interactive Connectivity Establishment (ICE): A Protocol 23 | //! for Network Address Translator (NAT) Traversal 24 | //! 25 | //! [RFC5245]: 26 | //! [RFC6544]: 27 | //! [RFC8445]: 28 | //! [stun-types]: https://docs.rs/stun-types 29 | 30 | #![no_std] 31 | 32 | #[cfg(any(feature = "std", test))] 33 | extern crate std; 34 | 35 | pub mod attribute; 36 | 37 | /// Initialize some debugging functionality of the library. 38 | /// 39 | /// It is not required to call this function, however doing so allows debug functionality of 40 | /// stun-types to print much more human readable descriptions of attributes and messages. 41 | pub fn debug_init() { 42 | attribute::debug_init(); 43 | } 44 | 45 | #[cfg(test)] 46 | pub(crate) mod tests { 47 | use tracing::subscriber::DefaultGuard; 48 | use tracing_subscriber::Layer; 49 | use tracing_subscriber::layer::SubscriberExt; 50 | 51 | use super::*; 52 | 53 | pub fn test_init_log() -> DefaultGuard { 54 | debug_init(); 55 | let level_filter = std::env::var("RICE_LOG") 56 | .or(std::env::var("RUST_LOG")) 57 | .ok() 58 | .and_then(|var| var.parse::().ok()) 59 | .unwrap_or( 60 | tracing_subscriber::filter::Targets::new().with_default(tracing::Level::TRACE), 61 | ); 62 | let registry = tracing_subscriber::registry().with( 63 | tracing_subscriber::fmt::layer() 64 | .with_file(true) 65 | .with_line_number(true) 66 | .with_level(true) 67 | .with_target(false) 68 | .with_test_writer() 69 | .with_filter(level_filter), 70 | ); 71 | tracing::subscriber::set_default(registry) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /librice/src/gathering.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Utilities for gathering potential sockets to send/receive data to/from. 10 | 11 | use std::net::{SocketAddr, UdpSocket}; 12 | use tracing::info; 13 | 14 | use std::net::IpAddr; 15 | use std::sync::Arc; 16 | 17 | use get_if_addrs::get_if_addrs; 18 | 19 | use crate::agent::AgentError; 20 | use crate::candidate::TransportType; 21 | use crate::runtime::{AsyncTcpListener, Runtime}; 22 | use crate::socket::UdpSocketChannel; 23 | 24 | /// A gathered socket 25 | #[derive(Debug, Clone)] 26 | pub enum GatherSocket { 27 | Udp(UdpSocketChannel), 28 | Tcp(Arc), 29 | } 30 | 31 | impl GatherSocket { 32 | /// The [`TransportType`] of this socket 33 | pub fn transport(&self) -> TransportType { 34 | match self { 35 | GatherSocket::Udp(_) => TransportType::Udp, 36 | GatherSocket::Tcp(_) => TransportType::Tcp, 37 | } 38 | } 39 | 40 | /// The address of the local end of this socket 41 | pub fn local_addr(&self) -> SocketAddr { 42 | match self { 43 | GatherSocket::Udp(s) => s.local_addr().unwrap(), 44 | GatherSocket::Tcp(s) => s.local_addr().unwrap(), 45 | } 46 | } 47 | } 48 | 49 | fn address_is_ignorable(ip: IpAddr) -> bool { 50 | // TODO: add is_benchmarking() and is_documentation() when they become stable 51 | if ip.is_loopback() || ip.is_unspecified() || ip.is_multicast() { 52 | return true; 53 | } 54 | match ip { 55 | IpAddr::V4(ipv4) => ipv4.is_broadcast() || ipv4.is_link_local(), 56 | IpAddr::V6(_ipv6) => false, 57 | } 58 | } 59 | 60 | /// Returns a stream of sockets corresponding to the available network interfaces 61 | pub async fn iface_sockets( 62 | runtime: Arc, 63 | ) -> Result>, AgentError> { 64 | let mut ifaces = get_if_addrs()?; 65 | // We only care about non-loopback interfaces for now 66 | // TODO: remove 'Deprecated IPv4-compatible IPv6 addresses [RFC4291]' 67 | // TODO: remove 'IPv6 site-local unicast addresses [RFC3879]' 68 | // TODO: remove 'IPv4-mapped IPv6 addresses unless ipv6 only' 69 | // TODO: location tracking Ipv6 address mismatches 70 | ifaces.retain(|e| !address_is_ignorable(e.ip())); 71 | 72 | for _f in ifaces.iter().inspect(|iface| { 73 | info!("Found interface {} address {:?}", iface.name, iface.ip()); 74 | }) {} 75 | 76 | let mut ret = vec![]; 77 | for iface in ifaces { 78 | ret.push( 79 | UdpSocket::bind(SocketAddr::new(iface.clone().ip(), 0)).and_then(|udp| { 80 | runtime 81 | .wrap_udp_socket(udp) 82 | .map(|udp| GatherSocket::Udp(UdpSocketChannel::new(udp))) 83 | }), 84 | ); 85 | ret.push( 86 | runtime 87 | .new_tcp_listener(SocketAddr::new(iface.clone().ip(), 0)) 88 | .await 89 | .map(GatherSocket::Tcp), 90 | ); 91 | } 92 | Ok(ret) 93 | } 94 | -------------------------------------------------------------------------------- /rice-proto/src/tcp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use alloc::vec::Vec; 10 | 11 | use byteorder::{BigEndian, ByteOrder}; 12 | 13 | use tracing::trace; 14 | 15 | /// A buffer object for handling STUN data received over a TCP connection that requires framing as 16 | /// specified in RFC 4571. This framing is required for ICE usage of TCP candidates. 17 | #[derive(Debug)] 18 | pub struct TcpBuffer { 19 | buf: Vec, 20 | } 21 | 22 | impl core::fmt::Display for TcpBuffer { 23 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 24 | f.debug_struct("TcpBuffer") 25 | .field("buf", &alloc::format!("{} bytes", self.buf.len())) 26 | .finish() 27 | } 28 | } 29 | 30 | impl TcpBuffer { 31 | /// Construct a new [`TcpBuffer`] 32 | pub fn new() -> Self { 33 | Vec::new().into() 34 | } 35 | 36 | /// Push a chunk of received data into the buffer. 37 | pub fn push_data(&mut self, data: &[u8]) { 38 | self.buf.extend(data); 39 | } 40 | 41 | /// Pull the next chunk of data from the buffer. If no buffer is available, then None is 42 | /// returned. 43 | pub fn pull_data(&mut self) -> Option> { 44 | if self.buf.len() < 2 { 45 | trace!( 46 | "running buffer is currently too small ({} bytes) to provide data", 47 | self.buf.len() 48 | ); 49 | return None; 50 | } 51 | 52 | let data_length = BigEndian::read_u16(&self.buf[..2]) as usize; 53 | if self.buf.len() < data_length { 54 | trace!( 55 | "not enough data, buf length {} data specifies length {}", 56 | self.buf.len(), 57 | data_length 58 | ); 59 | return None; 60 | } 61 | 62 | let bytes = self.take(data_length); 63 | trace!("return {} bytes", data_length); 64 | Some(bytes) 65 | } 66 | 67 | fn take(&mut self, data_length: usize) -> Vec { 68 | let offset = data_length + 2; 69 | if offset > self.buf.len() { 70 | return Vec::new(); 71 | } 72 | let mut data = self.buf.split_off(offset); 73 | core::mem::swap(&mut data, &mut self.buf); 74 | data[2..].to_vec() 75 | } 76 | } 77 | 78 | impl Default for TcpBuffer { 79 | fn default() -> Self { 80 | Self::new() 81 | } 82 | } 83 | 84 | impl From> for TcpBuffer { 85 | fn from(value: Vec) -> Self { 86 | Self { buf: value } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | #[test] 95 | fn tcp_buffer_split_recv() { 96 | let _log = crate::tests::test_init_log(); 97 | 98 | let mut tcp_buffer = TcpBuffer::default(); 99 | 100 | let mut len = [0; 2]; 101 | let data = [0, 1, 2, 4, 3]; 102 | BigEndian::write_u16(&mut len, data.len() as u16); 103 | 104 | tcp_buffer.push_data(&len); 105 | assert!(tcp_buffer.pull_data().is_none()); 106 | tcp_buffer.push_data(&data); 107 | assert_eq!(tcp_buffer.pull_data().unwrap(), &data); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /rice-c/README.md: -------------------------------------------------------------------------------- 1 | [![Chat](https://img.shields.io/matrix/librice-general:matrix.org?logo=matrix)](https://matrix.to/#/#librice-general:matrix.org) 2 | [![Build status](https://github.com/ystreet/librice/workflows/Build/badge.svg?branch=main)](https://github.com/ystreet/librice/actions) 3 | [![codecov](https://codecov.io/gh/ystreet/librice/branch/main/graph/badge.svg)](https://codecov.io/gh/ystreet/librice) 4 | [![Dependencies](https://deps.rs/repo/github/ystreet/librice/status.svg)](https://deps.rs/repo/github/ystreet/librice) 5 | [![crates.io](https://img.shields.io/crates/v/rice-c.svg)](https://crates.io/crates/rice-c) 6 | [![docs.rs](https://docs.rs/rice-c/badge.svg)](https://docs.rs/rice-c) 7 | 8 | # rice-c 9 | 10 | Repository containing Rust bindings to the C API version of `rice-proto`. This 11 | would be needed when using `rice-proto` from multiple independent 12 | libraries/application and shared access to the same `Agent` is required. 13 | 14 | ## Relevant standards 15 | 16 | - [x] [RFC5245](https://tools.ietf.org/html/rfc5245): 17 | Interactive Connectivity Establishment (ICE): A Protocol for Network Address 18 | Translator (NAT) Traversal for Offer/Answer Protocols 19 | - [x] [RFC5389](https://tools.ietf.org/html/rfc5389): 20 | Session Traversal Utilities for NAT (STUN) 21 | - [x] [RFC5766](https://tools.ietf.org/html/rfc5766): 22 | Traversal Using Relays around NAT (TURN): Relay Extensions to Session 23 | Traversal Utilities for NAT (STUN) 24 | - [x] [RFC5769](https://tools.ietf.org/html/rfc5769): 25 | Test Vectors for Session Traversal Utilities for NAT (STUN) 26 | - [ ] [RFC6062](https://tools.ietf.org/html/rfc6062): 27 | Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations 28 | - [x] [RFC6156](https://tools.ietf.org/html/rfc6156): 29 | Traversal Using Relays around NAT (TURN) Extension for IPv6 30 | - [x] [RFC6544](https://tools.ietf.org/html/rfc6544): 31 | TCP Candidates with Interactive Connectivity Establishment (ICE) 32 | - [ ] [RFC7675](https://tools.ietf.org/html/rfc7675): 33 | Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness 34 | - [x] [RFC8445](https://tools.ietf.org/html/rfc8445): 35 | Interactive Connectivity Establishment (ICE): A Protocol for Network Address 36 | Translator (NAT) Traversal 37 | - [x] [RFC8489](https://tools.ietf.org/html/rfc8489): 38 | Session Traversal Utilities for NAT (STUN) 39 | - [x] [RFC8656](https://tools.ietf.org/html/rfc8656): 40 | Traversal Using Relays around NAT (TURN): Relay Extensions to Session 41 | Traversal Utilities for NAT (STUN) 42 | - [x] [RFC8838](https://tools.ietf.org/html/rfc8838): 43 | Trickle ICE: Incremental Provisioning of Candidates for the Interactive 44 | Connectivity Establishment (ICE) Protocol 45 | 46 | ## TODO 47 | 48 | - RFC6062 49 | - RFC7675 50 | 51 | ## Building 52 | 53 | If building `rice-c` as part of this repository, then `cargo-c` is required 54 | and can be installed using: 55 | ```sh 56 | cargo install cargo-c 57 | ``` 58 | `rice-c` will then build a local copy of `rice-proto` for use. 59 | 60 | Otherwise, this crate requires a pre-existing installation of the C library 61 | `rice-proto` that can be found using `pkg-config` (through `system-deps`). 62 | Running the following command will indicate whether your environment contains 63 | `rice-proto`. 64 | ``` 65 | pkg-config --modversion rice-proto 66 | ``` 67 | 68 | If you need to build `rice-proto` with the C API, have a look at [rice-proto's 69 | README](https://github.com/ystreet/librice/tree/main/rice-proto). 70 | 71 | Once this prerequisite is handled, you can build `rice-c` using a 72 | regular `cargo build` invocation. 73 | -------------------------------------------------------------------------------- /rice-stun-types/src/attribute/use_candidate.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use stun_types::attribute::{ 10 | Attribute, AttributeFromRaw, AttributeStaticType, AttributeType, AttributeWrite, RawAttribute, 11 | }; 12 | use stun_types::message::StunParseError; 13 | use stun_types::prelude::*; 14 | 15 | /// The UseCandidate [`Attribute`] 16 | #[derive(Debug, Clone, PartialEq, Eq)] 17 | pub struct UseCandidate {} 18 | 19 | impl AttributeStaticType for UseCandidate { 20 | const TYPE: AttributeType = AttributeType::new(0x0025); 21 | } 22 | impl Attribute for UseCandidate { 23 | fn get_type(&self) -> AttributeType { 24 | Self::TYPE 25 | } 26 | 27 | fn length(&self) -> u16 { 28 | 0 29 | } 30 | } 31 | impl AttributeWrite for UseCandidate { 32 | fn to_raw(&self) -> RawAttribute<'_> { 33 | static BUF: [u8; 0] = [0; 0]; 34 | RawAttribute::new(UseCandidate::TYPE, &BUF) 35 | } 36 | fn write_into_unchecked(&self, dest: &mut [u8]) { 37 | self.write_header_unchecked(dest); 38 | } 39 | } 40 | 41 | impl AttributeFromRaw<'_> for UseCandidate { 42 | fn from_raw_ref(raw: &RawAttribute) -> Result 43 | where 44 | Self: Sized, 45 | { 46 | Self::try_from(raw) 47 | } 48 | } 49 | 50 | impl TryFrom<&RawAttribute<'_>> for UseCandidate { 51 | type Error = StunParseError; 52 | 53 | fn try_from(raw: &RawAttribute) -> Result { 54 | raw.check_type_and_len(Self::TYPE, 0..=0)?; 55 | Ok(Self {}) 56 | } 57 | } 58 | 59 | impl Default for UseCandidate { 60 | fn default() -> Self { 61 | UseCandidate::new() 62 | } 63 | } 64 | 65 | impl UseCandidate { 66 | /// Create a new UseCandidate [`Attribute`] 67 | /// 68 | /// # Examples 69 | /// 70 | /// ``` 71 | /// # use rice_stun_types::attribute::*; 72 | /// let _use_candidate = UseCandidate::new(); 73 | /// ``` 74 | pub fn new() -> Self { 75 | Self {} 76 | } 77 | } 78 | 79 | impl core::fmt::Display for UseCandidate { 80 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 81 | write!(f, "{}", Self::TYPE) 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | use tracing::trace; 89 | 90 | use std::vec::Vec; 91 | 92 | #[test] 93 | fn use_candidate() { 94 | let _log = crate::tests::test_init_log(); 95 | let use_candidate = UseCandidate::default(); 96 | trace!("{use_candidate}"); 97 | assert_eq!(use_candidate.length(), 0); 98 | let raw = RawAttribute::from(&use_candidate); 99 | trace!("{raw}"); 100 | assert_eq!(raw.get_type(), UseCandidate::TYPE); 101 | let mapped2 = UseCandidate::try_from(&raw).unwrap(); 102 | let mut data = [0; 4]; 103 | mapped2.write_into(&mut data).unwrap(); 104 | assert_eq!(data.as_ref(), &raw.to_bytes()); 105 | // provide incorrectly typed data 106 | let mut data: Vec<_> = raw.into(); 107 | data[1] = 0; 108 | assert!(matches!( 109 | UseCandidate::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()), 110 | Err(StunParseError::WrongAttributeImplementation) 111 | )); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /librice/README.md: -------------------------------------------------------------------------------- 1 | [![Chat](https://img.shields.io/matrix/librice-general:matrix.org?logo=matrix)](https://matrix.to/#/#librice-general:matrix.org) 2 | [![Build status](https://github.com/ystreet/librice/workflows/Build/badge.svg?branch=main)](https://github.com/ystreet/librice/actions) 3 | [![codecov](https://codecov.io/gh/ystreet/librice/branch/main/graph/badge.svg)](https://codecov.io/gh/ystreet/librice) 4 | [![Dependencies](https://deps.rs/repo/github/ystreet/librice/status.svg)](https://deps.rs/repo/github/ystreet/librice) 5 | [![crates.io](https://img.shields.io/crates/v/librice.svg)](https://crates.io/crates/librice) 6 | [![docs.rs](https://docs.rs/librice/badge.svg)](https://docs.rs/librice) 7 | 8 | # librice 9 | 10 | Repository containing an async implementation of the ICE (RFC8445) protocol 11 | written in the [Rust programming language](https://www.rust-lang.org/). 12 | This async implementation is based on the sans-IO crate `rice-proto` in 13 | the same repository. See the [rice-proto 14 | README](https://github.com/ystreet/librice/tree/main/rice-proto) for some 15 | details as to why use the sans-IO design. 16 | 17 | ## Current status 18 | 19 | The current status is that there is enough of the implementation to sucessfully 20 | communicate with STUN/TURN servers and/or a browser (Chrome or Firefox) in a WebRTC 21 | scenario. The STUN implementation is relatively mature. More work is needed on 22 | the ICE layer for efficiency and API experience. Initial TURN support has been 23 | implemented and some TURN-related RFCs are currently in progress. Supporting 24 | more scenarios is part of the near and long term future roadmap. 25 | 26 | ## Relevant standards 27 | 28 | - [x] [RFC5245](https://tools.ietf.org/html/rfc5245): 29 | Interactive Connectivity Establishment (ICE): A Protocol for Network Address 30 | Translator (NAT) Traversal for Offer/Answer Protocols 31 | - [x] [RFC5389](https://tools.ietf.org/html/rfc5389): 32 | Session Traversal Utilities for NAT (STUN) 33 | - [x] [RFC5766](https://tools.ietf.org/html/rfc5766): 34 | Traversal Using Relays around NAT (TURN): Relay Extensions to Session 35 | Traversal Utilities for NAT (STUN) 36 | - [x] [RFC5769](https://tools.ietf.org/html/rfc5769): 37 | Test Vectors for Session Traversal Utilities for NAT (STUN) 38 | - [ ] [RFC6062](https://tools.ietf.org/html/rfc6062): 39 | Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations 40 | - [x] [RFC6156](https://tools.ietf.org/html/rfc6156): 41 | Traversal Using Relays around NAT (TURN) Extension for IPv6 42 | - [x] [RFC6544](https://tools.ietf.org/html/rfc6544): 43 | TCP Candidates with Interactive Connectivity Establishment (ICE) 44 | - [ ] [RFC7675](https://tools.ietf.org/html/rfc7675): 45 | Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness 46 | - [x] [RFC8445](https://tools.ietf.org/html/rfc8445): 47 | Interactive Connectivity Establishment (ICE): A Protocol for Network Address 48 | Translator (NAT) Traversal 49 | - [x] [RFC8489](https://tools.ietf.org/html/rfc8489): 50 | Session Traversal Utilities for NAT (STUN) 51 | - [x] [RFC8656](https://tools.ietf.org/html/rfc8656): 52 | Traversal Using Relays around NAT (TURN): Relay Extensions to Session 53 | Traversal Utilities for NAT (STUN) 54 | - [x] [RFC8838](https://tools.ietf.org/html/rfc8838): 55 | Trickle ICE: Incremental Provisioning of Candidates for the Interactive 56 | Connectivity Establishment (ICE) Protocol 57 | 58 | ## TODO 59 | 60 | - RFC6062 61 | - RFC7675 62 | 63 | ## Building 64 | 65 | `librice` depends on `rice-c` and thus has the same build requirements as 66 | outlined in [its README](https://github.com/ystreet/librice/tree/main/rice-c). 67 | 68 | Specifically, that either `cargo-c` must be installed if building from source 69 | using the `librice` repository for development, or the the `rice-proto` C API 70 | must be available in the build environment through pkg-config. 71 | -------------------------------------------------------------------------------- /librice/tests/stund.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::net::UdpSocket; 10 | 11 | use futures::future::{AbortHandle, Abortable}; 12 | 13 | use librice::runtime::{AsyncTcpStreamReadExt, AsyncTcpStreamWriteExt, AsyncUdpSocketExt}; 14 | use stun_proto::types::message::*; 15 | 16 | #[macro_use] 17 | extern crate tracing; 18 | 19 | mod common; 20 | 21 | #[cfg(feature = "runtime-smol")] 22 | #[test] 23 | fn smol_udp_stund() { 24 | smol::block_on(udp_stund()) 25 | } 26 | 27 | #[cfg(feature = "runtime-tokio")] 28 | #[test] 29 | fn tokio_udp_stund() { 30 | crate::common::tokio_runtime().block_on(udp_stund()) 31 | } 32 | 33 | async fn udp_stund() { 34 | common::debug_init(); 35 | let runtime = librice::runtime::default_runtime().unwrap(); 36 | let stun_socket = UdpSocket::bind("127.0.0.1:0").unwrap(); 37 | let stun_socket = runtime.wrap_udp_socket(stun_socket).unwrap(); 38 | let stun_addr = stun_socket.local_addr().unwrap(); 39 | debug!("stun bound to {:?}", stun_addr); 40 | let (abort_handle, abort_registration) = AbortHandle::new_pair(); 41 | let stun_server = Abortable::new(common::stund_udp(stun_socket), abort_registration); 42 | runtime.spawn(Box::pin(async move { 43 | let _ = stun_server.await; 44 | })); 45 | 46 | let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); 47 | let socket = runtime.wrap_udp_socket(socket).unwrap(); 48 | let msg = Message::builder_request(BINDING, MessageWriteVec::new()); 49 | debug!("sent to {:?}, {:?}", stun_addr, msg); 50 | socket.send_to(&msg.finish(), stun_addr).await.unwrap(); 51 | 52 | let mut buf = [0; 1500]; 53 | let (size, _addr) = socket.recv_from(&mut buf).await.unwrap(); 54 | let msg = Message::from_bytes(&buf[..size]).unwrap(); 55 | debug!("received response {}", msg); 56 | abort_handle.abort(); 57 | debug!("stun socket closed"); 58 | } 59 | 60 | #[cfg(feature = "runtime-smol")] 61 | #[test] 62 | fn smol_tcp_stund() { 63 | smol::block_on(tcp_stund()) 64 | } 65 | 66 | #[cfg(feature = "runtime-tokio")] 67 | #[test] 68 | fn tokio_tcp_stund() { 69 | crate::common::tokio_runtime().block_on(tcp_stund()) 70 | } 71 | 72 | async fn tcp_stund() { 73 | common::debug_init(); 74 | let runtime = librice::runtime::default_runtime().unwrap(); 75 | let stun_socket = runtime 76 | .new_tcp_listener("127.0.0.1:0".parse().unwrap()) 77 | .await 78 | .unwrap(); 79 | let stun_addr = stun_socket.local_addr().unwrap(); 80 | debug!("stun bound to {:?}", stun_addr); 81 | let (abort_handle, abort_registration) = AbortHandle::new_pair(); 82 | let stun_server = Abortable::new( 83 | common::stund_tcp(runtime.clone(), stun_socket), 84 | abort_registration, 85 | ); 86 | runtime.spawn(Box::pin(async move { 87 | let _ = stun_server.await; 88 | })); 89 | 90 | let socket = runtime.tcp_connect(stun_addr).await.unwrap(); 91 | let (mut read, mut write) = socket.split(); 92 | let msg = Message::builder_request(BINDING, MessageWriteVec::new()); 93 | debug!("sent to {:?}, {:?}", stun_addr, msg); 94 | let msg_bytes = msg.finish(); 95 | write.write_all(&msg_bytes).await.unwrap(); 96 | 97 | let mut buf = [0; 1500]; 98 | let mut read_position = 0; 99 | loop { 100 | let read_amount = read.read(&mut buf[read_position..]).await.unwrap(); 101 | read_position += read_amount; 102 | debug!( 103 | "got {} bytes, buffer contains {} bytes", 104 | read_amount, read_position 105 | ); 106 | if read_position < 20 { 107 | continue; 108 | } 109 | match Message::from_bytes(&buf[..read_position]) { 110 | Ok(msg) => { 111 | debug!("received response {}", msg); 112 | break; 113 | } 114 | Err(_) => continue, 115 | } 116 | } 117 | 118 | abort_handle.abort(); 119 | //assert!(matches!(stun_server.await, Err(Aborted))); 120 | debug!("stun socket closed"); 121 | } 122 | -------------------------------------------------------------------------------- /librice/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #![deny(missing_debug_implementations)] 10 | #![deny(missing_docs)] 11 | 12 | //! # librice 13 | //! 14 | //! An async implementation based on [rice-proto] using the [rice-c] bindings. 15 | //! 16 | //! ## Relevant standards 17 | //! 18 | //! - [x] [RFC5245](https://tools.ietf.org/html/rfc5245): 19 | //! Interactive Connectivity Establishment (ICE): A Protocol for Network Address 20 | //! Translator (NAT) Traversal for Offer/Answer Protocols 21 | //! - [x] [RFC5389](https://tools.ietf.org/html/rfc5389): 22 | //! Session Traversal Utilities for NAT (STUN) 23 | //! - [x] [RFC5766](https://tools.ietf.org/html/rfc5766): 24 | //! Traversal Using Relays around NAT (TURN): Relay Extensions to Session 25 | //! Traversal Utilities for NAT (STUN) 26 | //! - [x] [RFC5769](https://tools.ietf.org/html/rfc5769): 27 | //! Test Vectors for Session Traversal Utilities for NAT (STUN) 28 | //! - [ ] [RFC6062](https://tools.ietf.org/html/rfc6062): 29 | //! Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations 30 | //! - [x] [RFC6156](https://tools.ietf.org/html/rfc6156): 31 | //! Traversal Using Relays around NAT (TURN) Extension for IPv6 32 | //! - [x] [RFC6544](https://tools.ietf.org/html/rfc6544): 33 | //! TCP Candidates with Interactive Connectivity Establishment (ICE) 34 | //! - [ ] [RFC7675](https://tools.ietf.org/html/rfc7675): 35 | //! Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness 36 | //! - [x] [RFC8445]: Interactive Connectivity Establishment (ICE): A Protocol 37 | //! for Network Address Translator (NAT) Traversal 38 | //! - [x] [RFC8489](https://tools.ietf.org/html/rfc8489): 39 | //! Session Traversal Utilities for NAT (STUN) 40 | //! - [x] [RFC8656](https://tools.ietf.org/html/rfc8656): 41 | //! Traversal Using Relays around NAT (TURN): Relay Extensions to Session 42 | //! Traversal Utilities for NAT (STUN) 43 | //! - [x] [RFC8838](https://tools.ietf.org/html/rfc8838): 44 | //! Trickle ICE: Incremental Provisioning of Candidates for the Interactive 45 | //! Connectivity Establishment (ICE) Protocol 46 | //! 47 | //! ## Building 48 | //! 49 | //! `librice` has the same build requirements as [rice-c] and the crate level documentation for 50 | //! [rice-c] provides guidelines on how to build [rice-c] and projects that depend on [rice-c]. 51 | //! 52 | //! [RFC8445]: 53 | //! [rice-proto]: 54 | //! [rice-c]: 55 | 56 | pub mod agent; 57 | pub mod component; 58 | mod gathering; 59 | pub mod runtime; 60 | pub mod socket; 61 | pub mod stream; 62 | mod utils; 63 | 64 | pub use rice_c::candidate; 65 | pub use rice_c::random_string; 66 | 67 | #[cfg(test)] 68 | pub(crate) mod tests { 69 | use std::sync::Once; 70 | use tracing_subscriber::Layer; 71 | use tracing_subscriber::layer::SubscriberExt; 72 | 73 | static TRACING: Once = Once::new(); 74 | 75 | pub fn test_init_log() { 76 | TRACING.call_once(|| { 77 | let level_filter = std::env::var("RICE_LOG") 78 | .or(std::env::var("RUST_LOG")) 79 | .ok() 80 | .and_then(|var| var.parse::().ok()) 81 | .unwrap_or( 82 | tracing_subscriber::filter::Targets::new().with_default(tracing::Level::ERROR), 83 | ); 84 | let registry = tracing_subscriber::registry().with( 85 | tracing_subscriber::fmt::layer() 86 | .with_file(true) 87 | .with_line_number(true) 88 | .with_level(true) 89 | .with_target(false) 90 | .with_test_writer() 91 | .with_filter(level_filter), 92 | ); 93 | tracing::subscriber::set_global_default(registry).unwrap(); 94 | }); 95 | } 96 | 97 | #[cfg(feature = "runtime-tokio")] 98 | pub fn tokio_runtime() -> tokio::runtime::Runtime { 99 | tokio::runtime::Builder::new_current_thread() 100 | .enable_all() 101 | .build() 102 | .unwrap() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /rice-ctypes/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #![deny(missing_debug_implementations)] 10 | #![deny(missing_docs)] 11 | #![allow(clippy::missing_safety_doc)] 12 | 13 | //! # rice-ctypes 14 | //! 15 | //! Helper crate for providing C types for the rice API. 16 | 17 | use core::ffi::CStr; 18 | use core::ffi::{c_char, c_int}; 19 | use core::net::SocketAddr; 20 | use core::str::FromStr; 21 | 22 | /// Errors when processing an operation. 23 | #[repr(i32)] 24 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 25 | pub enum RiceError { 26 | /// Not an error. The operation was completed successfully. 27 | Success = 0, 28 | /// The operation failed for an unspecified reason. 29 | Failed = -1, 30 | /// A required resource was not found. 31 | ResourceNotFound = -2, 32 | /// The operation is already in progress. 33 | AlreadyInProgress = -3, 34 | } 35 | 36 | /// A socket address. 37 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] 38 | pub struct RiceAddress(SocketAddr); 39 | 40 | impl RiceAddress { 41 | /// Create a new `RiceAddress`. 42 | pub fn new(addr: SocketAddr) -> Self { 43 | Self(addr) 44 | } 45 | 46 | /// Convert this `RiceAddress` into it's C API equivalent. 47 | /// 48 | /// The returned value should be converted back into the Rust equivalent using 49 | /// `RiceAddress::into_rust_full()` in order to free the resource. 50 | pub fn into_c_full(self) -> *const RiceAddress { 51 | const_override(Box::into_raw(Box::new(self))) 52 | } 53 | 54 | /// Consume a C representation of a `RiceAddress` into the Rust equivalent. 55 | pub unsafe fn into_rice_full(value: *const RiceAddress) -> Box { 56 | unsafe { Box::from_raw(mut_override(value)) } 57 | } 58 | 59 | /// Copy a C representation of a `RiceAddress` into the Rust equivalent. 60 | pub unsafe fn into_rice_none(value: *const RiceAddress) -> Self { 61 | unsafe { 62 | let boxed = Box::from_raw(mut_override(value)); 63 | let ret = *boxed; 64 | core::mem::forget(boxed); 65 | ret 66 | } 67 | } 68 | 69 | /// The inner representation of the [`RiceAddress`]. 70 | pub fn inner(self) -> SocketAddr { 71 | self.0 72 | } 73 | } 74 | 75 | impl core::ops::Deref for RiceAddress { 76 | type Target = SocketAddr; 77 | fn deref(&self) -> &Self::Target { 78 | &self.0 79 | } 80 | } 81 | 82 | /// Create a `RiceAddress` from a string representation of the socket address. 83 | pub unsafe fn rice_address_new_from_string(string: *const c_char) -> *mut RiceAddress { 84 | unsafe { 85 | let Ok(string) = CStr::from_ptr(string).to_str() else { 86 | return core::ptr::null_mut(); 87 | }; 88 | let Ok(saddr) = SocketAddr::from_str(string) else { 89 | return core::ptr::null_mut(); 90 | }; 91 | 92 | mut_override(RiceAddress::into_c_full(RiceAddress::new(saddr))) 93 | } 94 | } 95 | 96 | /// Compare whether two `RiceAddress`es are equal. 97 | pub unsafe fn rice_address_cmp(addr: *const RiceAddress, other: *const RiceAddress) -> c_int { 98 | unsafe { 99 | match (addr.is_null(), other.is_null()) { 100 | (true, true) => 0, 101 | (true, false) => -1, 102 | (false, true) => 1, 103 | (false, false) => { 104 | let addr = RiceAddress::into_rice_none(addr); 105 | let other = RiceAddress::into_rice_none(other); 106 | addr.cmp(&other) as c_int 107 | } 108 | } 109 | } 110 | } 111 | 112 | /// Free a `RiceAddress`. 113 | pub unsafe fn rice_address_free(addr: *mut RiceAddress) { 114 | unsafe { 115 | if !addr.is_null() { 116 | let _addr = Box::from_raw(addr); 117 | } 118 | } 119 | } 120 | 121 | /// The transport family 122 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 123 | #[repr(u32)] 124 | pub enum RiceTransportType { 125 | /// The UDP transport 126 | Udp, 127 | /// The TCP transport 128 | Tcp, 129 | } 130 | 131 | fn mut_override(val: *const T) -> *mut T { 132 | val as *mut T 133 | } 134 | 135 | fn const_override(val: *mut T) -> *const T { 136 | val as *const T 137 | } 138 | -------------------------------------------------------------------------------- /rice-stun-types/src/attribute/priority.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use byteorder::{BigEndian, ByteOrder}; 10 | 11 | use stun_types::attribute::{ 12 | Attribute, AttributeFromRaw, AttributeStaticType, AttributeType, AttributeWrite, RawAttribute, 13 | }; 14 | use stun_types::message::StunParseError; 15 | use stun_types::prelude::*; 16 | 17 | /// The Priority [`Attribute`] 18 | #[derive(Debug, Clone, PartialEq, Eq)] 19 | pub struct Priority { 20 | priority: u32, 21 | } 22 | 23 | impl AttributeStaticType for Priority { 24 | const TYPE: AttributeType = AttributeType::new(0x0024); 25 | } 26 | impl Attribute for Priority { 27 | fn get_type(&self) -> AttributeType { 28 | Self::TYPE 29 | } 30 | 31 | fn length(&self) -> u16 { 32 | 4 33 | } 34 | } 35 | impl AttributeWrite for Priority { 36 | fn to_raw(&self) -> RawAttribute<'_> { 37 | let mut buf = [0; 4]; 38 | BigEndian::write_u32(&mut buf[0..4], self.priority); 39 | RawAttribute::new(Priority::TYPE, &buf).into_owned() 40 | } 41 | fn write_into_unchecked(&self, dest: &mut [u8]) { 42 | self.write_header_unchecked(dest); 43 | BigEndian::write_u32(&mut dest[4..8], self.priority); 44 | } 45 | } 46 | 47 | impl AttributeFromRaw<'_> for Priority { 48 | fn from_raw_ref(raw: &RawAttribute) -> Result 49 | where 50 | Self: Sized, 51 | { 52 | Self::try_from(raw) 53 | } 54 | } 55 | 56 | impl TryFrom<&RawAttribute<'_>> for Priority { 57 | type Error = StunParseError; 58 | 59 | fn try_from(raw: &RawAttribute) -> Result { 60 | raw.check_type_and_len(Self::TYPE, 4..=4)?; 61 | Ok(Self { 62 | priority: BigEndian::read_u32(&raw.value[..4]), 63 | }) 64 | } 65 | } 66 | 67 | impl Priority { 68 | /// Create a new Priority [`Attribute`] 69 | /// 70 | /// # Examples 71 | /// 72 | /// ``` 73 | /// # use rice_stun_types::attribute::*; 74 | /// let priority = Priority::new(1234); 75 | /// assert_eq!(priority.priority(), 1234); 76 | /// ``` 77 | pub fn new(priority: u32) -> Self { 78 | Self { priority } 79 | } 80 | 81 | /// Retrieve the priority value 82 | /// 83 | /// # Examples 84 | /// 85 | /// ``` 86 | /// # use rice_stun_types::attribute::*; 87 | /// let priority = Priority::new(1234); 88 | /// assert_eq!(priority.priority(), 1234); 89 | /// ``` 90 | pub fn priority(&self) -> u32 { 91 | self.priority 92 | } 93 | } 94 | 95 | impl core::fmt::Display for Priority { 96 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 97 | write!(f, "{}: {}", Self::TYPE, self.priority) 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use super::*; 104 | use tracing::trace; 105 | 106 | use std::vec::Vec; 107 | 108 | #[test] 109 | fn priority() { 110 | let _log = crate::tests::test_init_log(); 111 | let val = 100; 112 | let priority = Priority::new(val); 113 | trace!("{priority}"); 114 | assert_eq!(priority.priority(), val); 115 | assert_eq!(priority.length(), 4); 116 | let raw = RawAttribute::from(&priority); 117 | trace!("{raw}"); 118 | assert_eq!(raw.get_type(), Priority::TYPE); 119 | let mapped2 = Priority::try_from(&raw).unwrap(); 120 | assert_eq!(mapped2.priority(), val); 121 | // truncate by one byte 122 | let mut data: Vec<_> = raw.clone().into(); 123 | let len = data.len(); 124 | BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1); 125 | assert!(matches!( 126 | Priority::try_from(&RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()), 127 | Err(StunParseError::Truncated { 128 | expected: 4, 129 | actual: 3 130 | }) 131 | )); 132 | // provide incorrectly typed data 133 | let mut data: Vec<_> = raw.into(); 134 | BigEndian::write_u16(&mut data[0..2], 0); 135 | assert!(matches!( 136 | Priority::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()), 137 | Err(StunParseError::WrongAttributeImplementation) 138 | )); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /rice-proto/README.md: -------------------------------------------------------------------------------- 1 | [![Chat](https://img.shields.io/matrix/librice-general:matrix.org?logo=matrix)](https://matrix.to/#/#librice-general:matrix.org) 2 | [![Build status](https://github.com/ystreet/librice/workflows/Build/badge.svg?branch=main)](https://github.com/ystreet/librice/actions) 3 | [![codecov](https://codecov.io/gh/ystreet/librice/branch/main/graph/badge.svg)](https://codecov.io/gh/ystreet/librice) 4 | [![Dependencies](https://deps.rs/repo/github/ystreet/librice/status.svg)](https://deps.rs/repo/github/ystreet/librice) 5 | [![crates.io](https://img.shields.io/crates/v/rice-proto.svg)](https://crates.io/crates/rice-proto) 6 | [![docs.rs](https://docs.rs/rice-proto/badge.svg)](https://docs.rs/rice-proto) 7 | 8 | # rice-proto 9 | 10 | Repository containing an (sans-IO) implementation of ICE (RFC8445) protocol written in 11 | the [Rust programming language](https://www.rust-lang.org/). 12 | 13 | ## Current status 14 | 15 | The current status is that there is enough of the implementation to sucessfully 16 | communicate with STUN/TURN servers and/or a browser (Chrome or Firefox) in a WebRTC 17 | scenario. The STUN implementation is relatively mature. More work is needed on 18 | the ICE layer for efficiency and API experience. Initial TURN support has been 19 | implemented and some TURN-related RFCs are currently in progress. Supporting 20 | more scenarios is part of the near and long term future roadmap. 21 | 22 | ## Why sans-io? 23 | 24 | A few reasons: reusability, testability, and composability. 25 | 26 | Without being bogged down in the details of how IO happens, the same sans-IO 27 | implementation can be used without prescribing the IO pattern that an application 28 | must follow. Instead, the application (or parent library) has much more freedom 29 | in how bytes are transferred between peers. It is possible to use a sans-IO 30 | library in either a synchronous environment or within an asynchronous runtime. 31 | 32 | A sans-IO design also allows easy testing of any specific state the sans-IO 33 | implementation might find itself in. Combined with a comprehensive test-suite, 34 | this provides assurance that the implementation behaves as expected under all 35 | circumstances. 36 | 37 | For other examples of sans-IO implementations, take a look at: 38 | - [stun-proto](https://github.com/ystreet/stun-proto): A sans-IO implementation 39 | of a STUN agent (client or server). 40 | - [turn-proto](https://github.com/ystreet/turn-proto): A sans-IO implementation 41 | of a TURN client or server. 42 | - [Quinn](https://github.com/quinn-rs/quinn/): A pure Rust async-compatible 43 | implementation of QUIC. 44 | - https://sans-io.readthedocs.io/ 45 | 46 | ## Relevant standards 47 | 48 | - [x] [RFC5245](https://tools.ietf.org/html/rfc5245): 49 | Interactive Connectivity Establishment (ICE): A Protocol for Network Address 50 | Translator (NAT) Traversal for Offer/Answer Protocols 51 | - [x] [RFC5389](https://tools.ietf.org/html/rfc5389): 52 | Session Traversal Utilities for NAT (STUN) 53 | - [x] [RFC5766](https://tools.ietf.org/html/rfc5766): 54 | Traversal Using Relays around NAT (TURN): Relay Extensions to Session 55 | Traversal Utilities for NAT (STUN) 56 | - [x] [RFC5769](https://tools.ietf.org/html/rfc5769): 57 | Test Vectors for Session Traversal Utilities for NAT (STUN) 58 | - [ ] [RFC6062](https://tools.ietf.org/html/rfc6062): 59 | Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations 60 | - [x] [RFC6156](https://tools.ietf.org/html/rfc6156): 61 | Traversal Using Relays around NAT (TURN) Extension for IPv6 62 | - [x] [RFC6544](https://tools.ietf.org/html/rfc6544): 63 | TCP Candidates with Interactive Connectivity Establishment (ICE) 64 | - [ ] [RFC7675](https://tools.ietf.org/html/rfc7675): 65 | Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness 66 | - [x] [RFC8445](https://tools.ietf.org/html/rfc8445): 67 | Interactive Connectivity Establishment (ICE): A Protocol for Network Address 68 | Translator (NAT) Traversal 69 | - [x] [RFC8489](https://tools.ietf.org/html/rfc8489): 70 | Session Traversal Utilities for NAT (STUN) 71 | - [x] [RFC8656](https://tools.ietf.org/html/rfc8656): 72 | Traversal Using Relays around NAT (TURN): Relay Extensions to Session 73 | Traversal Utilities for NAT (STUN) 74 | - [x] [RFC8838](https://tools.ietf.org/html/rfc8838): 75 | Trickle ICE: Incremental Provisioning of Candidates for the Interactive 76 | Connectivity Establishment (ICE) Protocol 77 | 78 | ## TODO 79 | 80 | - RFC6062 81 | - RFC7675 82 | 83 | ## Building 84 | 85 | In order to build a C API, we use [cargo-c](https://crates.io/crates/cargo-c) 86 | to build and install to a relevant installation prefix. 87 | 88 | Once `cargo-c` has been installed with: 89 | 90 | ```sh 91 | cargo install cargo-c 92 | ``` 93 | 94 | Installation can be performed using: 95 | 96 | ```sh 97 | cargo cinstall --prefix $PREFIX 98 | ``` 99 | 100 | And be used e.g. by `rice-c` for exposing a rust API of the C ABI. 101 | -------------------------------------------------------------------------------- /rice-c/build.rs: -------------------------------------------------------------------------------- 1 | // avoid linking/regenerating when running on docs.rs 2 | #[cfg(docsrs)] 3 | fn main() {} 4 | 5 | #[cfg(not(docsrs))] 6 | fn main() { 7 | use std::env; 8 | use std::path::{Path, PathBuf}; 9 | use std::process::Command; 10 | 11 | if env::var("DOCS_RS").is_ok() { 12 | return; 13 | } 14 | 15 | let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); 16 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 17 | let destdir = Path::new(&out_dir).join("rice-proto-cbuild"); 18 | let pkgconfigdir = destdir.clone().join("lib").join("pkgconfig"); 19 | let rice_proto_dir = manifest_dir.join("../rice-proto"); 20 | 21 | let rice_proto_exists = std::fs::File::open(rice_proto_dir.as_path()).is_ok(); 22 | if rice_proto_exists { 23 | println!( 24 | "cargo:rerun-if-changed={}", 25 | rice_proto_dir.to_str().unwrap() 26 | ); 27 | println!( 28 | "cargo:rerun-if-changed={}", 29 | rice_proto_dir.join("src").to_str().unwrap() 30 | ); 31 | println!( 32 | "cargo:rerun-if-changed={}", 33 | rice_proto_dir.join("src").join("capi").to_str().unwrap() 34 | ); 35 | } 36 | 37 | // Default to building the internal module if not already configured and the rice-proto 38 | // project exists. 39 | if env::var_os("SYSTEM_DEPS_RICE_PROTO_BUILD_INTERNAL").is_none() 40 | && env::var_os("SYSTEM_DEPS_BUILD_INTERNAL").is_none() 41 | && rice_proto_exists 42 | { 43 | // SAFETY: modifying the environment in a build.rs only occurs in single-threaded code. 44 | unsafe { 45 | env::set_var("SYSTEM_DEPS_RICE_PROTO_BUILD_INTERNAL", "auto"); 46 | // use static linking for `cargo tarpaulin` to be able to run doc tests correctly. 47 | env::set_var("SYSTEM_DEPS_RICE_PROTO_LINK", "static"); 48 | }; 49 | } 50 | let config = system_deps::Config::new() 51 | .add_build_internal("rice-proto", move |lib, version| { 52 | if rice_proto_exists { 53 | let target = env::var("TARGET").unwrap(); 54 | let mut cmd = Command::new("cargo"); 55 | cmd.stderr(std::process::Stdio::piped()) 56 | .args(["cinstall", "-p", "rice-proto", "--prefix"]) 57 | .arg(&destdir) 58 | .args(["--libdir", "lib"]) 59 | .args(["--target", &target]) 60 | .args([ 61 | "--target-dir", 62 | destdir.join("target").as_path().to_str().unwrap(), 63 | ]); 64 | let cmd_state = &mut cmd; 65 | if env::var("DEBUG").map(|v| v == "true").unwrap_or(false) { 66 | cmd_state.arg("--debug"); 67 | } 68 | let status = cmd 69 | .stderr(std::process::Stdio::piped()) 70 | .spawn() 71 | .expect("Failed to build internal copy of rice-proto"); 72 | let output = status.wait_with_output().unwrap(); 73 | let stderr = String::from_utf8(output.stderr).unwrap(); 74 | if !output.status.success() { 75 | eprintln!("stderr: {stderr}"); 76 | panic!("Could not build rice-proto"); 77 | } 78 | } 79 | system_deps::Library::from_internal_pkg_config(pkgconfigdir, lib, version) 80 | }) 81 | .probe() 82 | .unwrap(); 83 | 84 | let rice_proto = config.get_by_name("rice-proto").unwrap(); 85 | let mut rice_proto_h = None; 86 | for path in rice_proto.include_paths.iter() { 87 | let mut path = path.clone(); 88 | path.push("rice-proto.h"); 89 | if std::fs::metadata(&path).is_ok() { 90 | rice_proto_h = Some(path.clone()); 91 | break; 92 | } 93 | } 94 | let rice_proto_h = rice_proto_h.expect("Could not find rice-proto.h header"); 95 | 96 | let bindings = bindgen::Builder::default() 97 | .header(rice_proto_h.as_path().to_str().unwrap()) 98 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 99 | .allowlist_file(".*/rice/rice-.*\\.h") 100 | .prepend_enum_name(false) 101 | .default_enum_style(bindgen::EnumVariation::Consts) 102 | .disable_nested_struct_naming() 103 | .disable_name_namespacing() 104 | .no_copy( 105 | "Rice(Candidate|Transmit|AgentSocket|StreamIncomingData|DataImpl|GatheredCandidate)", 106 | ) 107 | .default_non_copy_union_style(bindgen::NonCopyUnionStyle::ManuallyDrop) 108 | .anon_fields_prefix("field") 109 | .use_core() 110 | .generate_cstr(true) 111 | .generate() 112 | .unwrap(); 113 | 114 | if rice_proto_exists { 115 | // only update the bindings if we are building from a local checkout. 116 | bindings 117 | .write_to_file(manifest_dir.join("src").join("bindings.rs")) 118 | .unwrap(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /rice-proto/src/turn.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! TURN module. 10 | 11 | use core::net::SocketAddr; 12 | use stun_proto::types::AddressFamily; 13 | 14 | use crate::candidate::TransportType; 15 | 16 | pub use turn_client_proto::types::TurnCredentials; 17 | 18 | /// Configuration for a particular TURN server connection. 19 | #[derive(Debug, Clone)] 20 | pub struct TurnConfig { 21 | client_transport: TransportType, 22 | turn_server: SocketAddr, 23 | credentials: TurnCredentials, 24 | families: smallvec::SmallVec<[AddressFamily; 2]>, 25 | tls_config: Option, 26 | } 27 | 28 | impl TurnConfig { 29 | /// Create a new [`TurnConfig`] from the provided details. 30 | /// 31 | /// # Examples 32 | /// ``` 33 | /// # use rice_proto::AddressFamily; 34 | /// # use rice_proto::turn::{TurnConfig, TurnCredentials}; 35 | /// # use rice_proto::candidate::TransportType; 36 | /// let credentials = TurnCredentials::new("user", "pass"); 37 | /// let server_addr = "127.0.0.1:3478".parse().unwrap(); 38 | /// let families = [AddressFamily::IPV4]; 39 | /// let config = TurnConfig::new( 40 | /// TransportType::Udp, 41 | /// server_addr, 42 | /// credentials.clone(), 43 | /// &families 44 | /// ); 45 | /// assert_eq!(config.client_transport(), TransportType::Udp); 46 | /// assert_eq!(config.addr(), server_addr); 47 | /// assert_eq!(config.credentials().username(), credentials.username()); 48 | /// assert_eq!(config.families(), families); 49 | /// ``` 50 | pub fn new( 51 | client_transport: TransportType, 52 | server_addr: SocketAddr, 53 | credentials: TurnCredentials, 54 | families: &[AddressFamily], 55 | ) -> Self { 56 | Self { 57 | client_transport, 58 | turn_server: server_addr, 59 | credentials, 60 | families: families.into(), 61 | tls_config: None, 62 | } 63 | } 64 | 65 | /// Connect to the TURN server over TLS. 66 | pub fn with_tls_config(mut self, config: TurnTlsConfig) -> Self { 67 | self.tls_config = Some(config); 68 | self 69 | } 70 | 71 | /// The TLS configuration to use for connecting to this TURN server. 72 | pub fn tls_config(&self) -> Option<&TurnTlsConfig> { 73 | self.tls_config.as_ref() 74 | } 75 | 76 | /// The TURN server address to connect to. 77 | pub fn addr(&self) -> SocketAddr { 78 | self.turn_server 79 | } 80 | 81 | /// The [`TransportType`] between the client and the TURN server. 82 | pub fn client_transport(&self) -> TransportType { 83 | self.client_transport 84 | } 85 | 86 | /// The credentials for accessing the TURN server. 87 | pub fn credentials(&self) -> &TurnCredentials { 88 | &self.credentials 89 | } 90 | 91 | /// The address family to allocate on the TURN server. 92 | pub fn families(&self) -> &[AddressFamily] { 93 | &self.families 94 | } 95 | } 96 | 97 | /// Configuration parameters for TURN use over (D)TLS. 98 | #[derive(Debug, Clone)] 99 | pub enum TurnTlsConfig { 100 | /// Rustls variant for TLS configuration. 101 | #[cfg(feature = "rustls")] 102 | Rustls(RustlsTurnConfig), 103 | /// Openssl variant for TLS configuration. 104 | #[cfg(feature = "openssl")] 105 | Openssl(OpensslTurnConfig), 106 | } 107 | 108 | #[cfg(feature = "rustls")] 109 | use alloc::sync::Arc; 110 | #[cfg(feature = "rustls")] 111 | use rustls::{ClientConfig, pki_types::ServerName}; 112 | 113 | /// Configuration parameters for TURN use over TLS with Rustls. 114 | #[cfg(feature = "rustls")] 115 | #[derive(Debug, Clone)] 116 | pub struct RustlsTurnConfig { 117 | config: Arc, 118 | server_name: ServerName<'static>, 119 | } 120 | 121 | #[cfg(feature = "rustls")] 122 | impl RustlsTurnConfig { 123 | /// Create a new [`RustlsTurnConfig`] for TURN over TLS with Rustls. 124 | pub fn new(config: Arc, server_name: ServerName<'static>) -> Self { 125 | Self { 126 | config, 127 | server_name, 128 | } 129 | } 130 | 131 | /// The Rustls `ClientConfig` for the TURN connection. 132 | pub fn client_config(&self) -> Arc { 133 | self.config.clone() 134 | } 135 | 136 | /// The server name to validate the TURN server with. 137 | pub fn server_name(&self) -> ServerName<'static> { 138 | self.server_name.clone() 139 | } 140 | } 141 | 142 | #[cfg(feature = "rustls")] 143 | impl From for TurnTlsConfig { 144 | fn from(value: RustlsTurnConfig) -> Self { 145 | Self::Rustls(value) 146 | } 147 | } 148 | 149 | /// Configuration parameters for TURN use over TLS with OpenSSL. 150 | #[cfg(feature = "openssl")] 151 | #[derive(Debug, Clone)] 152 | pub struct OpensslTurnConfig { 153 | ssl: openssl::ssl::SslContext, 154 | } 155 | 156 | #[cfg(feature = "openssl")] 157 | impl OpensslTurnConfig { 158 | /// Create a new [`RustlsTurnConfig`] for TURN over TLS with OpenSSL. 159 | pub fn new(ssl: openssl::ssl::SslContext) -> Self { 160 | Self { ssl } 161 | } 162 | 163 | /// The OpenSSL `SslContext` for the TURN connection. 164 | pub fn ssl_context(&self) -> &openssl::ssl::SslContext { 165 | &self.ssl 166 | } 167 | } 168 | 169 | #[cfg(feature = "openssl")] 170 | impl From for TurnTlsConfig { 171 | fn from(value: OpensslTurnConfig) -> Self { 172 | Self::Openssl(value) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /rice-proto/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #![deny(missing_debug_implementations)] 10 | #![deny(missing_docs)] 11 | 12 | //! # rice-proto 13 | //! 14 | //! A sans-IO implementation of the ICE protocol as specified in [RFC8445]. 15 | //! 16 | //! ## Why sans-io? 17 | //! 18 | //! A few reasons: reusability, testability, and composability. 19 | //! 20 | //! Without being bogged down in the details of how IO happens, the same sans-IO 21 | //! implementation can be used without prescribing the IO pattern that an application 22 | //! must follow. Instead, the application (or parent library) has much more freedom 23 | //! in how bytes are transferred between peers. It is possible to use a sans-IO 24 | //! library in either a synchronous environment or within an asynchronous runtime. 25 | //! 26 | //! A sans-IO design also allows easy testing of any specific state the sans-IO 27 | //! implementation might find itself in. Combined with a comprehensive test-suite, 28 | //! this provides assurance that the implementation behaves as expected under all 29 | //! circumstances. 30 | //! 31 | //! For other examples of sans-IO implementations, take a look at: 32 | //! - [stun-proto]: A sans-IO implementation of a STUN agent (client or server). 33 | //! - [turn-proto]: A sans-IO implementation of a TURN client or server. 34 | //! - 35 | //! 36 | //! ## Relevant standards 37 | //! 38 | //! - [x] [RFC5245](https://tools.ietf.org/html/rfc5245): 39 | //! Interactive Connectivity Establishment (ICE): A Protocol for Network Address 40 | //! Translator (NAT) Traversal for Offer/Answer Protocols 41 | //! - [x] [RFC5389](https://tools.ietf.org/html/rfc5389): 42 | //! Session Traversal Utilities for NAT (STUN) 43 | //! - [x] [RFC5766](https://tools.ietf.org/html/rfc5766): 44 | //! Traversal Using Relays around NAT (TURN): Relay Extensions to Session 45 | //! Traversal Utilities for NAT (STUN) 46 | //! - [x] [RFC5769](https://tools.ietf.org/html/rfc5769): 47 | //! Test Vectors for Session Traversal Utilities for NAT (STUN) 48 | //! - [ ] [RFC6062](https://tools.ietf.org/html/rfc6062): 49 | //! Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations 50 | //! - [x] [RFC6156](https://tools.ietf.org/html/rfc6156): 51 | //! Traversal Using Relays around NAT (TURN) Extension for IPv6 52 | //! - [x] [RFC6544](https://tools.ietf.org/html/rfc6544): 53 | //! TCP Candidates with Interactive Connectivity Establishment (ICE) 54 | //! - [ ] [RFC7675](https://tools.ietf.org/html/rfc7675): 55 | //! Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness 56 | //! - [x] [RFC8445]: Interactive Connectivity Establishment (ICE): A Protocol 57 | //! for Network Address Translator (NAT) Traversal 58 | //! - [x] [RFC8489](https://tools.ietf.org/html/rfc8489): 59 | //! Session Traversal Utilities for NAT (STUN) 60 | //! - [x] [RFC8656](https://tools.ietf.org/html/rfc8656): 61 | //! Traversal Using Relays around NAT (TURN): Relay Extensions to Session 62 | //! Traversal Utilities for NAT (STUN) 63 | //! - [x] [RFC8838](https://tools.ietf.org/html/rfc8838): 64 | //! Trickle ICE: Incremental Provisioning of Candidates for the Interactive 65 | //! Connectivity Establishment (ICE) Protocol 66 | //! 67 | //! ## Building a C library 68 | //! 69 | //! `rice-proto` uses [cargo-c] to build and install a compatible C library, headers and 70 | //! pkg-config file. 71 | //! 72 | //! Once [cargo-c] has been installed (e.g. with `cargo install cargo-c`), then installation of 73 | //! `rice-proto` can be achieved using: 74 | //! 75 | //! ```sh 76 | //! cargo install cinstall --prefix $PREFIX 77 | //! ``` 78 | //! 79 | //! and can be used by any build sytem that can retrieve compilation flags from `pkg-config` files. 80 | //! 81 | //! [cargo-c]: https://crates.io/crates/cargo-c 82 | //! [RFC8445]: 83 | //! [stun-proto]: https://docs.rs/stun-proto 84 | //! [turn-proto]: https://docs.rs/turn-proto 85 | 86 | #![no_std] 87 | 88 | extern crate alloc; 89 | 90 | #[cfg(any(feature = "std", test))] 91 | extern crate std; 92 | 93 | pub mod agent; 94 | pub mod candidate; 95 | pub mod component; 96 | mod conncheck; 97 | mod gathering; 98 | mod rand; 99 | pub mod stream; 100 | mod tcp; 101 | pub mod turn; 102 | 103 | #[cfg(feature = "capi")] 104 | pub mod capi; 105 | 106 | pub use stun_proto::types::AddressFamily; 107 | 108 | use crate::rand::generate_random_ice_string; 109 | 110 | /// Allowed characters within username fragment and password values. 111 | static ALPHABET: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"; 112 | 113 | /// Generate a random sequence of characters suitable for username fragments and passwords. 114 | pub fn random_string(len: usize) -> alloc::string::String { 115 | generate_random_ice_string(ALPHABET.as_bytes(), len) 116 | } 117 | 118 | #[cfg(test)] 119 | pub(crate) mod tests { 120 | use tracing::subscriber::DefaultGuard; 121 | use tracing_subscriber::Layer; 122 | use tracing_subscriber::layer::SubscriberExt; 123 | 124 | use super::*; 125 | 126 | pub fn test_init_log() -> DefaultGuard { 127 | let level_filter = std::env::var("RICE_LOG") 128 | .or(std::env::var("RUST_LOG")) 129 | .ok() 130 | .and_then(|var| var.parse::().ok()) 131 | .unwrap_or( 132 | tracing_subscriber::filter::Targets::new().with_default(tracing::Level::TRACE), 133 | ); 134 | let registry = tracing_subscriber::registry().with( 135 | tracing_subscriber::fmt::layer() 136 | .with_file(true) 137 | .with_line_number(true) 138 | .with_level(true) 139 | .with_target(false) 140 | .with_test_writer() 141 | .with_filter(level_filter), 142 | ); 143 | tracing::subscriber::set_default(registry) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Chat](https://img.shields.io/matrix/librice-general:matrix.org?logo=matrix)](https://matrix.to/#/#librice-general:matrix.org) 2 | [![Build status](https://github.com/ystreet/librice/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/ystreet/librice/actions) 3 | [![codecov](https://codecov.io/gh/ystreet/librice/branch/main/graph/badge.svg)](https://codecov.io/gh/ystreet/librice) 4 | [![Dependencies](https://deps.rs/repo/github/ystreet/librice/status.svg)](https://deps.rs/repo/github/ystreet/librice) 5 | [![crates.io](https://img.shields.io/crates/v/librice.svg)](https://crates.io/crates/librice) 6 | [![docs.rs](https://docs.rs/librice/badge.svg)](https://docs.rs/librice) 7 | 8 | # librice 9 | 10 | Repository containing an (sans-IO) implementation of ICE (RFC8445) protocol written in 11 | the [Rust programming language](https://www.rust-lang.org/). A C API interface is 12 | currently also provided for `rice-proto` and `rice-io`. The C interface can 13 | also be accessed from Rust using `rice-c`. 14 | 15 | ## Current status 16 | 17 | The current status is that there is enough of the implementation to sucessfully 18 | communicate with STUN/TURN servers and/or a browser (Chrome or Firefox) in a WebRTC 19 | scenario. The STUN implementation is relatively mature. More work is needed on 20 | the ICE layer for efficiency and API experience. Initial TURN support has been 21 | implemented and some TURN-related RFCs are currently in progress. Supporting 22 | more scenarios is part of the near and long term future roadmap. 23 | 24 | ## Why sans-io? 25 | 26 | A few reasons: reusability, testability, and composability. 27 | 28 | Without being bogged down in the details of how IO happens, the same sans-IO 29 | implementation can be used without prescribing the IO pattern that an application 30 | must follow. Instead, the application (or parent library) has much more freedom 31 | in how bytes are transferred between peers. It is possible to use a sans-IO 32 | library in either a synchronous environment or within an asynchronous runtime. 33 | 34 | A sans-IO design also allows easy testing of any specific state the sans-IO 35 | implementation might find itself in. Combined with a comprehensive test-suite, 36 | this provides assurance that the implementation behaves as expected under all 37 | circumstances. 38 | 39 | For other examples of sans-IO implementations, take a look at: 40 | - [stun-proto](https://github.com/ystreet/stun-proto): A sans-IO implementation 41 | of a STUN agent (client or server). 42 | - [turn-proto](https://github.com/ystreet/turn-proto): A sans-IO implementation 43 | of a TURN client or server. 44 | - [Quinn](https://github.com/quinn-rs/quinn/): A pure Rust async-compatible 45 | implementation of QUIC. 46 | - https://sans-io.readthedocs.io/ 47 | 48 | ## Relevant standards 49 | 50 | - [x] [RFC5245](https://tools.ietf.org/html/rfc5245): 51 | Interactive Connectivity Establishment (ICE): A Protocol for Network Address 52 | Translator (NAT) Traversal for Offer/Answer Protocols 53 | - [x] [RFC5389](https://tools.ietf.org/html/rfc5389): 54 | Session Traversal Utilities for NAT (STUN) 55 | - [x] [RFC5766](https://tools.ietf.org/html/rfc5766): 56 | Traversal Using Relays around NAT (TURN): Relay Extensions to Session 57 | Traversal Utilities for NAT (STUN) 58 | - [x] [RFC5769](https://tools.ietf.org/html/rfc5769): 59 | Test Vectors for Session Traversal Utilities for NAT (STUN) 60 | - [ ] [RFC6062](https://tools.ietf.org/html/rfc6062): 61 | Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations 62 | - [x] [RFC6156](https://tools.ietf.org/html/rfc6156): 63 | Traversal Using Relays around NAT (TURN) Extension for IPv6 64 | - [x] [RFC6544](https://tools.ietf.org/html/rfc6544): 65 | TCP Candidates with Interactive Connectivity Establishment (ICE) 66 | - [ ] [RFC7675](https://tools.ietf.org/html/rfc7675): 67 | Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness 68 | - [x] [RFC8445](https://tools.ietf.org/html/rfc8445): 69 | Interactive Connectivity Establishment (ICE): A Protocol for Network Address 70 | Translator (NAT) Traversal 71 | - [x] [RFC8489](https://tools.ietf.org/html/rfc8489): 72 | Session Traversal Utilities for NAT (STUN) 73 | - [x] [RFC8656](https://tools.ietf.org/html/rfc8656): 74 | Traversal Using Relays around NAT (TURN): Relay Extensions to Session 75 | Traversal Utilities for NAT (STUN) 76 | - [x] [RFC8838](https://tools.ietf.org/html/rfc8838): 77 | Trickle ICE: Incremental Provisioning of Candidates for the Interactive 78 | Connectivity Establishment (ICE) Protocol 79 | 80 | ## Structure 81 | 82 | ### [rice-stun-types](https://github.com/ystreet/librice/tree/main/rice-stun-types) 83 | 84 | Implementation of STUN attributes relevant for ICE (RFC8445). 85 | 86 | ### [rice-proto](https://github.com/ystreet/librice/tree/main/rice-proto) 87 | 88 | The sans-IO implementation of the ICE (RFC8445) protocol. Contains no IO code 89 | whatsover. 90 | 91 | ### [rice-c](https://github.com/ystreet/librice/tree/main/rice-c) 92 | 93 | A library for accessing `rice-proto` using a C API interface. 94 | Typically useful when exposing the ICE agent across library/application 95 | boundaries for accessing the same ICE agent. If your application does not have 96 | such a requirement (e.g. entirely in Rust), then `rice-c` is not needed. 97 | 98 | ### [librice](https://github.com/ystreet/librice/tree/main/librice) 99 | 100 | An async implementation of ICE (RFC8445) built using `rice-proto` using the C 101 | API through the `rice-c` crate. The async runtime used can be provided by the 102 | application for `librice` to use or the provided `tokio` and `smol` 103 | implementations can be used. 104 | 105 | ### [rice-io](https://github.com/ystreet/librice/tree/main/rice-io) 106 | 107 | An optional library exposing a C interface for handling IO using Rust's network 108 | primitives `UdpSocket`, and `TcpStream`. Uses a single dedicated thread for 109 | handling IO wakeups. It is not required for implementation. 110 | 111 | ## TODO 112 | 113 | - RFC6062 114 | - RFC7675 115 | 116 | ## Building 117 | 118 | All crates in the workspace can be built using a standard `cargo build` 119 | invocation. However in order to successfully build the `rice-c` crate (and any 120 | dependant crates, like `librice`), `cargo-c` must be installed and in the 121 | environment. The [rice-c README](https://github.com/ystreet/librice/tree/main/rice-c#building) 122 | contains more details. 123 | -------------------------------------------------------------------------------- /librice/src/runtime/tokio.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::future::Future; 10 | use std::pin::Pin; 11 | use std::sync::Arc; 12 | use std::task::{Poll, ready}; 13 | 14 | use tokio::time::sleep_until; 15 | 16 | use crate::runtime::{AsyncTcpStream, AsyncTcpStreamRead, AsyncTcpStreamWrite, Runtime}; 17 | 18 | /// An async implementation for use with Tokio. 19 | #[derive(Debug)] 20 | pub struct TokioRuntime; 21 | 22 | impl Runtime for TokioRuntime { 23 | fn spawn(&self, future: Pin + Send>>) { 24 | tokio::spawn(future); 25 | } 26 | fn new_timer(&self, i: std::time::Instant) -> std::pin::Pin> { 27 | Box::pin(sleep_until(i.into())) 28 | } 29 | fn wrap_udp_socket( 30 | &self, 31 | socket: std::net::UdpSocket, 32 | ) -> std::io::Result> { 33 | socket.set_nonblocking(true)?; 34 | Ok(Arc::new(TokioUdpSocket { 35 | io: tokio::net::UdpSocket::from_std(socket)?, 36 | })) 37 | } 38 | fn tcp_connect( 39 | &self, 40 | peer: std::net::SocketAddr, 41 | ) -> Pin>> + Send>> { 42 | Box::pin(async move { 43 | tokio::net::TcpStream::connect(peer) 44 | .await 45 | .map(|s| Box::new(s) as _) 46 | }) 47 | } 48 | fn new_tcp_listener( 49 | &self, 50 | addr: std::net::SocketAddr, 51 | ) -> Pin>> + Send>> 52 | { 53 | Box::pin(async move { 54 | tokio::net::TcpListener::bind(addr) 55 | .await 56 | .map(|s| Arc::new(s) as _) 57 | }) 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | struct TokioUdpSocket { 63 | io: tokio::net::UdpSocket, 64 | } 65 | 66 | impl super::AsyncUdpSocket for TokioUdpSocket { 67 | fn local_addr(&self) -> std::io::Result { 68 | self.io.local_addr() 69 | } 70 | fn poll_recv( 71 | &self, 72 | cx: &mut std::task::Context, 73 | dest: &mut [u8], 74 | ) -> std::task::Poll> { 75 | loop { 76 | ready!(self.io.poll_recv_ready(cx))?; 77 | match self.io.try_recv_from(dest) { 78 | Ok(ret) => return Poll::Ready(Ok(ret)), 79 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => (), 80 | Err(e) => return Poll::Ready(Err(e)), 81 | } 82 | } 83 | } 84 | fn poll_send( 85 | &self, 86 | cx: &mut std::task::Context, 87 | src: &[u8], 88 | to: std::net::SocketAddr, 89 | ) -> Poll> { 90 | loop { 91 | ready!(self.io.poll_send_ready(cx))?; 92 | match self.io.try_send_to(src, to) { 93 | Ok(ret) => return Poll::Ready(Ok(ret)), 94 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => (), 95 | Err(e) => return Poll::Ready(Err(e)), 96 | } 97 | } 98 | } 99 | } 100 | 101 | impl super::AsyncTcpStream for tokio::net::TcpStream { 102 | fn local_addr(&self) -> std::io::Result { 103 | tokio::net::TcpStream::local_addr(self) 104 | } 105 | fn remote_addr(&self) -> std::io::Result { 106 | tokio::net::TcpStream::peer_addr(self) 107 | } 108 | fn split(self: Box) -> (Box, Box) { 109 | let (read, write) = tokio::net::TcpStream::into_split(*self); 110 | (Box::new(read), Box::new(write)) 111 | } 112 | } 113 | 114 | impl super::AsyncTcpStreamRead for tokio::net::tcp::OwnedReadHalf { 115 | fn local_addr(&self) -> std::io::Result { 116 | tokio::net::tcp::OwnedReadHalf::local_addr(self) 117 | } 118 | fn remote_addr(&self) -> std::io::Result { 119 | tokio::net::tcp::OwnedReadHalf::peer_addr(self) 120 | } 121 | fn poll_read( 122 | &mut self, 123 | cx: &mut std::task::Context, 124 | buf: &mut [u8], 125 | ) -> Poll> { 126 | let mut buf = tokio::io::ReadBuf::new(buf); 127 | tokio::io::AsyncRead::poll_read(Pin::new(&mut *self), cx, &mut buf) 128 | .map_ok(|_| buf.filled().len()) 129 | } 130 | } 131 | 132 | impl super::AsyncTcpStreamWrite for tokio::net::tcp::OwnedWriteHalf { 133 | fn local_addr(&self) -> std::io::Result { 134 | tokio::net::tcp::OwnedWriteHalf::local_addr(self) 135 | } 136 | fn remote_addr(&self) -> std::io::Result { 137 | tokio::net::tcp::OwnedWriteHalf::peer_addr(self) 138 | } 139 | fn poll_write( 140 | &mut self, 141 | cx: &mut std::task::Context, 142 | buf: &[u8], 143 | ) -> Poll> { 144 | tokio::io::AsyncWrite::poll_write(Pin::new(&mut *self), cx, buf) 145 | } 146 | fn poll_flush(&mut self, cx: &mut std::task::Context) -> Poll> { 147 | tokio::io::AsyncWrite::poll_flush(Pin::new(&mut *self), cx) 148 | } 149 | fn poll_shutdown( 150 | &mut self, 151 | cx: &mut std::task::Context, 152 | _how: std::net::Shutdown, 153 | ) -> Poll> { 154 | tokio::io::AsyncWrite::poll_shutdown(Pin::new(&mut *self), cx) 155 | } 156 | } 157 | 158 | impl super::AsyncTcpListener for tokio::net::TcpListener { 159 | fn local_addr(&self) -> std::io::Result { 160 | tokio::net::TcpListener::local_addr(self) 161 | } 162 | fn poll_next( 163 | &self, 164 | cx: &mut std::task::Context, 165 | ) -> Poll>> { 166 | tokio::net::TcpListener::poll_accept(self, cx).map_ok(|(s, _addr)| Box::new(s) as _) 167 | } 168 | } 169 | 170 | impl super::AsyncTimer for tokio::time::Sleep { 171 | fn reset(self: std::pin::Pin<&mut Self>, i: std::time::Instant) { 172 | Self::reset(self, i.into()) 173 | } 174 | fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context) -> std::task::Poll<()> { 175 | Future::poll(self, cx) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /librice/src/runtime/smol.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use std::future::Future; 10 | use std::net::{SocketAddr, TcpListener, TcpStream}; 11 | use std::pin::Pin; 12 | use std::sync::Arc; 13 | use std::task::{Poll, ready}; 14 | 15 | use futures::FutureExt; 16 | use smol::{Async, Timer}; 17 | 18 | use crate::runtime::{ 19 | AsyncTcpStream, AsyncTcpStreamRead, AsyncTcpStreamWrite, AsyncTimer, Runtime, 20 | }; 21 | 22 | /// An async implemtnation for use with `smol`. 23 | #[derive(Debug)] 24 | pub struct SmolRuntime; 25 | 26 | impl Runtime for SmolRuntime { 27 | fn spawn(&self, future: Pin + Send>>) { 28 | smol::spawn(future).detach() 29 | } 30 | fn new_timer(&self, i: std::time::Instant) -> Pin> { 31 | Box::pin(smol::Timer::at(i)) 32 | } 33 | fn wrap_udp_socket( 34 | &self, 35 | socket: std::net::UdpSocket, 36 | ) -> std::io::Result> { 37 | Ok(Arc::new(SmolUdpSocket { 38 | io: Async::new_nonblocking(socket)?, 39 | })) 40 | } 41 | fn new_tcp_listener( 42 | &self, 43 | addr: SocketAddr, 44 | ) -> Pin>> + Send>> 45 | { 46 | Box::pin(async move { SmolTcpListener::bind(addr).map(|s| Arc::new(s) as _) }) 47 | } 48 | fn tcp_connect( 49 | &self, 50 | peer: SocketAddr, 51 | ) -> Pin>> + Send>> { 52 | Box::pin(async move { 53 | Async::::connect(peer) 54 | .await 55 | .map(|s| Box::new(smol::net::TcpStream::from(s)) as _) 56 | }) 57 | } 58 | } 59 | 60 | #[derive(Debug)] 61 | struct SmolUdpSocket { 62 | io: Async, 63 | } 64 | 65 | impl super::AsyncUdpSocket for SmolUdpSocket { 66 | fn local_addr(&self) -> std::io::Result { 67 | self.io.get_ref().local_addr() 68 | } 69 | fn poll_recv( 70 | &self, 71 | cx: &mut std::task::Context, 72 | dest: &mut [u8], 73 | ) -> Poll> { 74 | loop { 75 | ready!(self.io.poll_readable(cx))?; 76 | if let Ok(res) = self.io.get_ref().recv_from(dest) { 77 | return Poll::Ready(Ok(res)); 78 | } 79 | } 80 | } 81 | fn poll_send( 82 | &self, 83 | cx: &mut std::task::Context, 84 | src: &[u8], 85 | to: std::net::SocketAddr, 86 | ) -> Poll> { 87 | loop { 88 | ready!(self.io.poll_writable(cx))?; 89 | match self.io.get_ref().send_to(src, to) { 90 | Ok(bytes) => return Poll::Ready(Ok(bytes)), 91 | Err(e) => { 92 | if e.kind() == std::io::ErrorKind::WouldBlock { 93 | continue; 94 | } else { 95 | return Poll::Ready(Err(e)); 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | #[derive(Debug)] 104 | pub struct SmolTcpListener { 105 | io: Async, 106 | } 107 | 108 | impl super::AsyncTcpListener for SmolTcpListener { 109 | fn local_addr(&self) -> std::io::Result { 110 | self.io.get_ref().local_addr() 111 | } 112 | fn poll_next( 113 | &self, 114 | cx: &mut std::task::Context, 115 | ) -> Poll>> { 116 | ready!(self.io.poll_readable(cx))?; 117 | let fut = self.io.accept(); 118 | let mut fut = core::pin::pin!(fut); 119 | fut.poll_unpin(cx) 120 | .map_ok(|(stream, _remote_addr)| Box::new(smol::net::TcpStream::from(stream)) as _) 121 | } 122 | } 123 | 124 | impl SmolTcpListener { 125 | fn bind(addr: SocketAddr) -> std::io::Result { 126 | Ok(Self { 127 | io: Async::::bind(addr)?, 128 | }) 129 | } 130 | } 131 | 132 | impl super::AsyncTcpStream for smol::net::TcpStream { 133 | fn local_addr(&self) -> std::io::Result { 134 | smol::net::TcpStream::local_addr(self) 135 | } 136 | fn remote_addr(&self) -> std::io::Result { 137 | smol::net::TcpStream::peer_addr(self) 138 | } 139 | fn split(self: Box) -> (Box, Box) { 140 | (self.clone(), self) 141 | } 142 | } 143 | 144 | impl super::AsyncTcpStreamRead for smol::net::TcpStream { 145 | fn local_addr(&self) -> std::io::Result { 146 | smol::net::TcpStream::local_addr(self) 147 | } 148 | fn remote_addr(&self) -> std::io::Result { 149 | smol::net::TcpStream::peer_addr(self) 150 | } 151 | fn poll_read( 152 | &mut self, 153 | cx: &mut std::task::Context, 154 | buf: &mut [u8], 155 | ) -> Poll> { 156 | smol::io::AsyncRead::poll_read(Pin::new(&mut *self), cx, buf) 157 | } 158 | } 159 | 160 | impl super::AsyncTcpStreamWrite for smol::net::TcpStream { 161 | fn local_addr(&self) -> std::io::Result { 162 | smol::net::TcpStream::local_addr(self) 163 | } 164 | fn remote_addr(&self) -> std::io::Result { 165 | smol::net::TcpStream::peer_addr(self) 166 | } 167 | fn poll_write( 168 | &mut self, 169 | cx: &mut std::task::Context, 170 | buf: &[u8], 171 | ) -> Poll> { 172 | smol::io::AsyncWrite::poll_write(Pin::new(&mut *self), cx, buf) 173 | } 174 | fn poll_flush(&mut self, cx: &mut std::task::Context) -> Poll> { 175 | smol::io::AsyncWrite::poll_flush(Pin::new(&mut *self), cx) 176 | } 177 | fn poll_shutdown( 178 | &mut self, 179 | cx: &mut std::task::Context, 180 | how: std::net::Shutdown, 181 | ) -> Poll> { 182 | self.shutdown(how)?; 183 | smol::io::AsyncWrite::poll_close(Pin::new(&mut *self), cx) 184 | } 185 | } 186 | 187 | impl AsyncTimer for Timer { 188 | fn reset(mut self: Pin<&mut Self>, i: std::time::Instant) { 189 | self.set_at(i) 190 | } 191 | fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll<()> { 192 | Future::poll(self, cx).map(|_| ()) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /rice-proto/benches/sendrecv.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; 10 | use rice_proto::agent::Agent; 11 | use rice_proto::candidate::{Candidate, CandidatePair}; 12 | use stun_proto::Instant; 13 | use stun_proto::agent::Transmit; 14 | use stun_proto::types::TransportType; 15 | 16 | fn bench_sendrecv_udp(c: &mut Criterion) { 17 | let local_addr = "192.168.1.1:1000".parse().unwrap(); 18 | let local_candidate = Candidate::builder( 19 | 1, 20 | rice_proto::candidate::CandidateType::Host, 21 | stun_proto::types::TransportType::Udp, 22 | "1", 23 | local_addr, 24 | ) 25 | .base_address(local_addr) 26 | .priority(1000) 27 | .build(); 28 | let remote_addr = "192.168.1.2:2000".parse().unwrap(); 29 | let remote_candidate = Candidate::builder( 30 | 1, 31 | rice_proto::candidate::CandidateType::Host, 32 | stun_proto::types::TransportType::Udp, 33 | "1", 34 | remote_addr, 35 | ) 36 | .base_address(local_addr) 37 | .priority(1000) 38 | .build(); 39 | 40 | let mut agent = Agent::builder().trickle_ice(true).controlling(true).build(); 41 | let stream_id = agent.add_stream(); 42 | 43 | let mut stream = agent.mut_stream(stream_id).unwrap(); 44 | let component_id = stream.add_component().unwrap(); 45 | stream.add_local_candidate(local_candidate.clone()); 46 | stream.end_of_local_candidates(); 47 | stream.add_remote_candidate(remote_candidate.clone()); 48 | 49 | let pair = CandidatePair::new(local_candidate, remote_candidate); 50 | let mut component = stream.mut_component(component_id).unwrap(); 51 | component.set_selected_pair(pair).unwrap(); 52 | 53 | let mut group = c.benchmark_group("Component/Udp"); 54 | for size in [32, 1024, 16000] { 55 | let mut component = stream.mut_component(component_id).unwrap(); 56 | group.throughput(criterion::Throughput::Bytes(size as u64)); 57 | let data = vec![1; size]; 58 | let now = Instant::ZERO; 59 | let transmit = component.send(data.clone(), now).unwrap(); 60 | assert_eq!(transmit.data.as_ref(), data.as_slice()); 61 | group.bench_function(BenchmarkId::new("Send", size), |b| { 62 | b.iter_batched( 63 | || data.clone(), 64 | |data| { 65 | let _transmit = component.send(data, now).unwrap(); 66 | }, 67 | criterion::BatchSize::SmallInput, 68 | ) 69 | }); 70 | let transmit = Transmit::new(&data, TransportType::Udp, remote_addr, local_addr); 71 | let reply = stream.handle_incoming_data(1, transmit, now); 72 | assert_eq!(reply.data.unwrap().as_ref(), &data); 73 | group.bench_function(BenchmarkId::new("Recv", size), |b| { 74 | b.iter(|| { 75 | let transmit = Transmit::new(&data, TransportType::Udp, remote_addr, local_addr); 76 | let _reply = stream.handle_incoming_data(1, transmit, now); 77 | }) 78 | }); 79 | } 80 | group.finish(); 81 | } 82 | 83 | fn bench_sendrecv_tcp(c: &mut Criterion) { 84 | let local_addr = "192.168.1.1:1000".parse().unwrap(); 85 | let local_candidate = Candidate::builder( 86 | 1, 87 | rice_proto::candidate::CandidateType::Host, 88 | stun_proto::types::TransportType::Tcp, 89 | "1", 90 | local_addr, 91 | ) 92 | .base_address(local_addr) 93 | .priority(1000) 94 | .tcp_type(rice_proto::candidate::TcpType::Active) 95 | .build(); 96 | let remote_addr = "192.168.1.2:2000".parse().unwrap(); 97 | let remote_candidate = Candidate::builder( 98 | 1, 99 | rice_proto::candidate::CandidateType::Host, 100 | stun_proto::types::TransportType::Tcp, 101 | "1", 102 | remote_addr, 103 | ) 104 | .base_address(local_addr) 105 | .priority(1000) 106 | .tcp_type(rice_proto::candidate::TcpType::Passive) 107 | .build(); 108 | 109 | let mut agent = Agent::builder().trickle_ice(true).controlling(true).build(); 110 | let stream_id = agent.add_stream(); 111 | 112 | let mut stream = agent.mut_stream(stream_id).unwrap(); 113 | let component_id = stream.add_component().unwrap(); 114 | stream.add_local_candidate(local_candidate.clone()); 115 | stream.end_of_local_candidates(); 116 | stream.add_remote_candidate(remote_candidate.clone()); 117 | 118 | let pair = CandidatePair::new(local_candidate, remote_candidate); 119 | let mut component = stream.mut_component(component_id).unwrap(); 120 | component.set_selected_pair(pair).unwrap(); 121 | 122 | let mut group = c.benchmark_group("Component/Tcp"); 123 | for size in [32, 1024, 16000] { 124 | let mut component = stream.mut_component(component_id).unwrap(); 125 | group.throughput(criterion::Throughput::Bytes(size as u64)); 126 | let data = vec![1; size]; 127 | let now = Instant::ZERO; 128 | let transmit = component.send(data.clone(), now).unwrap(); 129 | assert_eq!(&transmit.data.as_ref()[2..], data.as_slice()); 130 | group.bench_function(BenchmarkId::new("Send", size), |b| { 131 | b.iter_batched( 132 | || data.clone(), 133 | |data| { 134 | let _transmit = component.send(data, now).unwrap(); 135 | }, 136 | criterion::BatchSize::SmallInput, 137 | ) 138 | }); 139 | let mut framed = vec![0; data.len() + 2]; 140 | framed[0] = ((size & 0xff00) >> 8) as u8; 141 | framed[1] = (size & 0xff) as u8; 142 | framed[2..].copy_from_slice(&data); 143 | let transmit = Transmit::new(&framed, TransportType::Tcp, remote_addr, local_addr); 144 | let reply = stream.handle_incoming_data(1, transmit, now); 145 | assert!(reply.data.is_none()); 146 | let reply = stream.poll_recv().unwrap(); 147 | assert_eq!(&reply.data, &framed[2..]); 148 | group.bench_function(BenchmarkId::new("Recv", size), |b| { 149 | b.iter(|| { 150 | let transmit = Transmit::new(&framed, TransportType::Tcp, remote_addr, local_addr); 151 | let _reply = stream.handle_incoming_data(1, transmit, now); 152 | let _reply = stream.poll_recv().unwrap(); 153 | }) 154 | }); 155 | } 156 | group.finish(); 157 | } 158 | 159 | fn bench_sendrecv(c: &mut Criterion) { 160 | bench_sendrecv_udp(c); 161 | bench_sendrecv_tcp(c); 162 | } 163 | 164 | criterion_group!(send, bench_sendrecv); 165 | criterion_main!(send); 166 | -------------------------------------------------------------------------------- /librice/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use librice::runtime::{ 10 | AsyncTcpListener, AsyncTcpListenerExt, AsyncTcpStreamReadExt, AsyncTcpStreamWriteExt, 11 | AsyncUdpSocket, AsyncUdpSocketExt, Runtime, 12 | }; 13 | use rice_c::Instant; 14 | use stun_proto::agent::HandleStunReply; 15 | use stun_proto::agent::StunAgent; 16 | use stun_proto::types::attribute::*; 17 | use stun_proto::types::message::*; 18 | 19 | use std::fmt::Display; 20 | use std::net::SocketAddr; 21 | use std::sync::Arc; 22 | use std::sync::Once; 23 | 24 | use librice::agent::*; 25 | 26 | pub fn debug_init() { 27 | use tracing_subscriber::Layer; 28 | use tracing_subscriber::layer::SubscriberExt; 29 | 30 | static TRACING: Once = Once::new(); 31 | TRACING.call_once(|| { 32 | let level_filter = std::env::var("RICE_LOG") 33 | .or(std::env::var("RUST_LOG")) 34 | .ok() 35 | .and_then(|var| var.parse::().ok()) 36 | .unwrap_or( 37 | tracing_subscriber::filter::Targets::new().with_default(tracing::Level::ERROR), 38 | ); 39 | let registry = tracing_subscriber::registry().with( 40 | tracing_subscriber::fmt::layer() 41 | .with_file(true) 42 | .with_line_number(true) 43 | .with_level(true) 44 | .with_target(false) 45 | .with_test_writer() 46 | .with_filter(level_filter), 47 | ); 48 | tracing::subscriber::set_global_default(registry).unwrap(); 49 | 50 | turn_server_proto::types::debug_init(); 51 | }); 52 | } 53 | 54 | fn warn_on_err(res: Result, default: T) -> T 55 | where 56 | E: Display, 57 | { 58 | match res { 59 | Ok(v) => v, 60 | Err(e) => { 61 | warn!("{}", e); 62 | default 63 | } 64 | } 65 | } 66 | 67 | fn handle_binding_request(msg: &Message, from: SocketAddr) -> Result { 68 | if let Some(error_msg) = 69 | Message::check_attribute_types(msg, &[Fingerprint::TYPE], &[], MessageWriteVec::new()) 70 | { 71 | return Ok(error_msg); 72 | } 73 | 74 | let mut response = Message::builder_success(msg, MessageWriteVec::new()); 75 | let xor_addr = XorMappedAddress::new(from, msg.transaction_id()); 76 | response.add_attribute(&xor_addr).unwrap(); 77 | response.add_fingerprint().unwrap(); 78 | Ok(response) 79 | } 80 | 81 | fn handle_incoming_data( 82 | data: &[u8], 83 | from: SocketAddr, 84 | stun_agent: &mut StunAgent, 85 | ) -> Option<(MessageWriteVec, SocketAddr)> { 86 | let msg = Message::from_bytes(data).ok()?; 87 | match stun_agent.handle_stun(msg, from) { 88 | HandleStunReply::Drop(_) | HandleStunReply::UnvalidatedStunResponse(_) => None, 89 | // we don't send any stun request so should never receive any responses 90 | HandleStunReply::ValidatedStunResponse(_response) => { 91 | error!("Received STUN response from {from}!"); 92 | None 93 | } 94 | HandleStunReply::IncomingStun(msg) => { 95 | info!("received from {from}: {}", msg); 96 | if msg.has_class(MessageClass::Request) && msg.has_method(BINDING) { 97 | match handle_binding_request(&msg, from) { 98 | Ok(response) => { 99 | info!("sending response to {from}: {:?}", response); 100 | return Some((response, from)); 101 | } 102 | Err(err) => warn!("error: {}", err), 103 | } 104 | } else { 105 | let mut response = Message::builder_error(&msg, MessageWriteVec::new()); 106 | let error = ErrorCode::new(400, "Bad Request").unwrap(); 107 | response.add_attribute(&error).unwrap(); 108 | return Some((response, from)); 109 | } 110 | None 111 | } 112 | } 113 | } 114 | 115 | #[allow(dead_code)] 116 | pub async fn stund_udp(udp_socket: Arc) -> std::io::Result<()> { 117 | let local_addr = udp_socket.local_addr()?; 118 | let mut udp_stun_agent = 119 | StunAgent::builder(stun_proto::types::TransportType::Udp, local_addr).build(); 120 | 121 | loop { 122 | let mut data = vec![0; 1500]; 123 | let (len, from) = warn_on_err(udp_socket.recv_from(&mut data).await, (0, local_addr)); 124 | if let Some((response, to)) = handle_incoming_data(&data[..len], from, &mut udp_stun_agent) 125 | { 126 | warn_on_err(udp_socket.send_to(&response.finish(), to).await, 0); 127 | } 128 | } 129 | } 130 | 131 | #[allow(dead_code)] 132 | pub async fn stund_tcp( 133 | runtime: Arc, 134 | listener: Arc, 135 | ) -> std::io::Result<()> { 136 | let local_addr = listener.local_addr()?; 137 | let base_instant = std::time::Instant::now(); 138 | while let Ok(stream) = listener.accept().await { 139 | debug!("stund incoming tcp connection"); 140 | runtime.spawn(Box::pin(async move { 141 | let remote_addr = stream.remote_addr().unwrap(); 142 | let mut tcp_stun_agent = 143 | StunAgent::builder(stun_proto::types::TransportType::Tcp, local_addr) 144 | .remote_addr(remote_addr) 145 | .build(); 146 | let mut data = vec![0; 1500]; 147 | let (mut read, mut write) = stream.split(); 148 | let size = warn_on_err(read.read(&mut data).await, 0); 149 | if size == 0 { 150 | debug!("TCP connection with {remote_addr} closed"); 151 | return; 152 | } 153 | debug!("stund tcp received {size} bytes"); 154 | if let Some((response, to)) = 155 | handle_incoming_data(&data[..size], remote_addr, &mut tcp_stun_agent) 156 | { 157 | if let Ok(transmit) = 158 | tcp_stun_agent.send(response.finish(), to, Instant::from_std(base_instant)) 159 | { 160 | warn_on_err(write.write_all(&transmit.data).await, ()); 161 | } 162 | } 163 | // XXX: Assumes that the stun packet arrives in a single packet 164 | write.shutdown(std::net::Shutdown::Read).await.unwrap(); 165 | })) 166 | } 167 | Ok(()) 168 | } 169 | 170 | #[cfg(feature = "runtime-tokio")] 171 | pub fn tokio_runtime() -> tokio::runtime::Runtime { 172 | tokio::runtime::Builder::new_current_thread() 173 | .enable_all() 174 | .build() 175 | .unwrap() 176 | } 177 | -------------------------------------------------------------------------------- /rice-c/src/component.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! A [`Component`] in an ICE [`Stream`](crate::stream::Stream) 10 | 11 | use sans_io_time::Instant; 12 | 13 | use crate::agent::{AgentError, AgentTransmit}; 14 | use crate::candidate::{CandidatePair, TransportType}; 15 | use crate::turn::TurnConfig; 16 | use crate::{Address, const_override, mut_override}; 17 | 18 | /// A [`Component`] in an ICE [`Stream`](crate::stream::Stream) 19 | #[derive(Debug)] 20 | pub struct Component { 21 | ffi: *mut crate::ffi::RiceComponent, 22 | stream_id: usize, 23 | } 24 | 25 | unsafe impl Send for Component {} 26 | unsafe impl Sync for Component {} 27 | 28 | impl Clone for Component { 29 | fn clone(&self) -> Self { 30 | Self { 31 | ffi: unsafe { crate::ffi::rice_component_ref(self.ffi) }, 32 | stream_id: self.stream_id, 33 | } 34 | } 35 | } 36 | 37 | impl Drop for Component { 38 | fn drop(&mut self) { 39 | unsafe { crate::ffi::rice_component_unref(self.ffi) } 40 | } 41 | } 42 | 43 | impl Component { 44 | pub(crate) fn from_c_full(component: *mut crate::ffi::RiceComponent, stream_id: usize) -> Self { 45 | Self { 46 | ffi: component, 47 | stream_id, 48 | } 49 | } 50 | 51 | /// The component identifier within a particular ICE [`Stream`](crate::stream::Stream) 52 | pub fn id(&self) -> usize { 53 | unsafe { crate::ffi::rice_component_get_id(self.ffi) } 54 | } 55 | 56 | /// Retrieve the [`Stream`](crate::stream::Stream) for this component. 57 | pub fn stream(&self) -> crate::stream::Stream { 58 | unsafe { 59 | crate::stream::Stream::from_c_full(crate::ffi::rice_component_get_stream(self.ffi)) 60 | } 61 | } 62 | 63 | /// Retrieve the current state of a `Component` 64 | pub fn state(&self) -> ComponentConnectionState { 65 | unsafe { crate::ffi::rice_component_get_state(self.ffi).into() } 66 | } 67 | 68 | /// The [`CandidatePair`] this component has selected to send/receive data with. This will not 69 | /// be valid until the [`Component`] has reached [`ComponentConnectionState::Connected`] 70 | pub fn selected_pair(&self) -> Option { 71 | unsafe { 72 | let mut local = crate::ffi::RiceCandidate::zeroed(); 73 | let mut remote = crate::ffi::RiceCandidate::zeroed(); 74 | crate::ffi::rice_component_selected_pair(self.ffi, &mut local, &mut remote); 75 | if local.address.is_null() || remote.address.is_null() { 76 | None 77 | } else { 78 | Some(crate::candidate::CandidatePair::new( 79 | crate::candidate::Candidate::from_c_full(local).to_owned(), 80 | crate::candidate::Candidate::from_c_full(remote).to_owned(), 81 | )) 82 | } 83 | } 84 | } 85 | 86 | /// Start gathering candidates for this component. The parent 87 | /// [`Agent::poll`](crate::agent::Agent::poll) is used to progress 88 | /// the gathering. 89 | pub fn gather_candidates<'a, 'b>( 90 | &self, 91 | sockets: impl IntoIterator, 92 | turn_servers: impl IntoIterator, 93 | ) -> Result<(), AgentError> { 94 | unsafe { 95 | let mut transports = vec![]; 96 | let mut socket_addr = vec![]; 97 | let mut socket_addresses = vec![]; 98 | for (ttype, addr) in sockets.into_iter() { 99 | transports.push(ttype.into()); 100 | socket_addresses.push(const_override(addr.ffi)); 101 | socket_addr.push(addr); 102 | } 103 | let mut turn_sockets = vec![]; 104 | let mut turn_configs = vec![]; 105 | for (turn_addr, config) in turn_servers.into_iter() { 106 | turn_sockets.push(const_override(turn_addr.ffi)); 107 | turn_configs.push(config.into_c_full()); 108 | } 109 | AgentError::from_c(crate::ffi::rice_component_gather_candidates( 110 | self.ffi, 111 | transports.len(), 112 | socket_addresses.as_ptr(), 113 | transports.as_ptr(), 114 | turn_sockets.len(), 115 | turn_sockets.as_ptr(), 116 | turn_configs.as_ptr(), 117 | )) 118 | } 119 | } 120 | 121 | /// Set the pair that will be used to send/receive data. This will override the ICE 122 | /// negotiation chosen value. 123 | pub fn set_selected_pair(&self, pair: CandidatePair) -> Result<(), AgentError> { 124 | unsafe { 125 | AgentError::from_c(crate::ffi::rice_component_set_selected_pair( 126 | self.ffi, 127 | pair.local.as_c(), 128 | pair.remote.as_c(), 129 | )) 130 | } 131 | } 132 | 133 | /// Send data to the peer using the selected pair. This will not succeed until the 134 | /// [`Component`] has reached [`ComponentConnectionState::Connected`] 135 | pub fn send(&self, data: &[u8], now: Instant) -> Result { 136 | unsafe { 137 | let mut transmit = crate::ffi::RiceTransmit { 138 | stream_id: self.stream_id, 139 | transport: TransportType::Udp.into(), 140 | from: core::ptr::null(), 141 | to: core::ptr::null(), 142 | data: crate::ffi::RiceDataImpl { 143 | ptr: core::ptr::null_mut(), 144 | size: 0, 145 | }, 146 | }; 147 | AgentError::from_c(crate::ffi::rice_component_send( 148 | self.ffi, 149 | mut_override(data.as_ptr()), 150 | data.len(), 151 | now.as_nanos(), 152 | &mut transmit, 153 | ))?; 154 | Ok(AgentTransmit::from_c_full(transmit)) 155 | } 156 | } 157 | } 158 | 159 | /// The state of a component 160 | #[repr(u32)] 161 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 162 | pub enum ComponentConnectionState { 163 | /// Component is in initial state and no connectivity checks are in progress. 164 | New = crate::ffi::RICE_COMPONENT_CONNECTION_STATE_NEW, 165 | /// Connectivity checks are in progress for this candidate 166 | Connecting = crate::ffi::RICE_COMPONENT_CONNECTION_STATE_CONNECTING, 167 | /// A [`CandidatePair`](crate::candidate::CandidatePair`) has been selected for this component 168 | Connected = crate::ffi::RICE_COMPONENT_CONNECTION_STATE_CONNECTED, 169 | /// No connection could be found for this Component 170 | Failed = crate::ffi::RICE_COMPONENT_CONNECTION_STATE_FAILED, 171 | } 172 | 173 | impl ComponentConnectionState { 174 | pub(crate) fn from_c(ffi: crate::ffi::RiceComponentConnectionState) -> Self { 175 | match ffi { 176 | crate::ffi::RICE_COMPONENT_CONNECTION_STATE_NEW => Self::New, 177 | crate::ffi::RICE_COMPONENT_CONNECTION_STATE_CONNECTING => Self::Connecting, 178 | crate::ffi::RICE_COMPONENT_CONNECTION_STATE_CONNECTED => Self::Connected, 179 | crate::ffi::RICE_COMPONENT_CONNECTION_STATE_FAILED => Self::Failed, 180 | _ => panic!("Unknown RiceComponentConnectionState value {ffi:x?}"), 181 | } 182 | } 183 | } 184 | 185 | impl From for ComponentConnectionState { 186 | fn from(value: crate::ffi::RiceComponentConnectionState) -> Self { 187 | match value { 188 | crate::ffi::RICE_COMPONENT_CONNECTION_STATE_NEW => Self::New, 189 | crate::ffi::RICE_COMPONENT_CONNECTION_STATE_CONNECTING => Self::Connecting, 190 | crate::ffi::RICE_COMPONENT_CONNECTION_STATE_CONNECTED => Self::Connected, 191 | crate::ffi::RICE_COMPONENT_CONNECTION_STATE_FAILED => Self::Failed, 192 | val => panic!("Unknown component connection state value {val:x?}"), 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /rice-c/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #![deny(missing_debug_implementations)] 10 | #![deny(missing_docs)] 11 | 12 | //! # rice-c 13 | //! 14 | //! Bindings for the [rice-proto] C API. 15 | //! 16 | //! ## When to use 17 | //! 18 | //! The `rice-c` crate is useful when you have two separate components written in different 19 | //! languages that need to access and modify the same ICE resources. If your application stack is 20 | //! entirely in rust, then using only [rice-proto] may be sufficient and `rice-c` may not be needed. 21 | //! 22 | //! ## Building 23 | //! 24 | //! `rice-c` requires a pre-existing installation of the [rice-proto] C API that can be found using 25 | //! `pkg-config`. This detection is performed using [system-deps] and there are some environment 26 | //! variables that [system-deps] can use to influence the detection of a [rice-proto] 27 | //! installation. 28 | //! 29 | //! You can check if [rice-proto] is available in your build environment with: 30 | //! 31 | //! ```sh 32 | //! pkg-config --modversion rice-proto 33 | //! ``` 34 | //! 35 | //! ## Interface 36 | //! 37 | //! `rice-c` provides a very similar interface as [rice-proto] in order to ease switching between 38 | //! the two implementations (`rice-c` and [rice-proto]) as may be required. 39 | //! 40 | //! [rice-proto]: https://docs.rs/rice-proto 41 | //! [system-deps]: https://docs.rs/system-deps 42 | 43 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 44 | 45 | pub mod ffi; 46 | 47 | pub mod agent; 48 | pub mod candidate; 49 | pub mod component; 50 | pub mod stream; 51 | pub mod turn; 52 | 53 | pub use sans_io_time::Instant; 54 | 55 | /// Prelude module. 56 | pub mod prelude { 57 | pub use crate::candidate::CandidateApi; 58 | } 59 | 60 | /// A network address. 61 | pub struct Address { 62 | ffi: *mut crate::ffi::RiceAddress, 63 | } 64 | 65 | unsafe impl Send for Address {} 66 | unsafe impl Sync for Address {} 67 | 68 | impl core::fmt::Debug for Address { 69 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 70 | if self.ffi.is_null() { 71 | f.debug_struct("Address").field("ffi", &self.ffi).finish() 72 | } else { 73 | f.debug_struct("Address") 74 | .field("ffi", &self.ffi) 75 | .field("value", &self.as_socket()) 76 | .finish() 77 | } 78 | } 79 | } 80 | 81 | impl Clone for Address { 82 | fn clone(&self) -> Self { 83 | Self { 84 | ffi: unsafe { crate::ffi::rice_address_copy(self.ffi) }, 85 | } 86 | } 87 | } 88 | 89 | impl Drop for Address { 90 | fn drop(&mut self) { 91 | unsafe { crate::ffi::rice_address_free(self.ffi) } 92 | } 93 | } 94 | 95 | impl Address { 96 | pub(crate) fn as_c(&self) -> *mut crate::ffi::RiceAddress { 97 | self.ffi 98 | } 99 | 100 | pub(crate) fn into_c_full(self) -> *mut crate::ffi::RiceAddress { 101 | let ret = self.ffi; 102 | core::mem::forget(self); 103 | ret 104 | } 105 | 106 | pub(crate) fn from_c_none(ffi: *const crate::ffi::RiceAddress) -> Self { 107 | Self { 108 | ffi: unsafe { crate::ffi::rice_address_copy(ffi) }, 109 | } 110 | } 111 | 112 | pub(crate) fn from_c_full(ffi: *mut crate::ffi::RiceAddress) -> Self { 113 | Self { ffi } 114 | } 115 | 116 | /// Convert this [`Address`] into a `SocketAddr`. 117 | pub fn as_socket(&self) -> SocketAddr { 118 | self.into() 119 | } 120 | } 121 | 122 | impl From for Address { 123 | fn from(addr: SocketAddr) -> Self { 124 | match addr.ip() { 125 | IpAddr::V4(v4) => Self { 126 | ffi: unsafe { 127 | crate::ffi::rice_address_new_from_bytes( 128 | crate::ffi::RICE_ADDRESS_FAMILY_IPV4, 129 | v4.octets().as_ptr(), 130 | addr.port(), 131 | ) 132 | }, 133 | }, 134 | IpAddr::V6(v6) => Self { 135 | ffi: unsafe { 136 | crate::ffi::rice_address_new_from_bytes( 137 | crate::ffi::RICE_ADDRESS_FAMILY_IPV6, 138 | v6.octets().as_ptr(), 139 | addr.port(), 140 | ) 141 | }, 142 | }, 143 | } 144 | } 145 | } 146 | 147 | impl From<&Address> for SocketAddr { 148 | fn from(value: &Address) -> Self { 149 | unsafe { 150 | let port = crate::ffi::rice_address_get_port(value.ffi); 151 | let ip = match crate::ffi::rice_address_get_family(value.ffi) { 152 | crate::ffi::RICE_ADDRESS_FAMILY_IPV4 => { 153 | let mut octets = [0; 4]; 154 | crate::ffi::rice_address_get_address_bytes(value.ffi, octets.as_mut_ptr()); 155 | IpAddr::V4(Ipv4Addr::from(octets)) 156 | } 157 | crate::ffi::RICE_ADDRESS_FAMILY_IPV6 => { 158 | let mut octets = [0; 16]; 159 | crate::ffi::rice_address_get_address_bytes(value.ffi, octets.as_mut_ptr()); 160 | IpAddr::V6(Ipv6Addr::from(octets)) 161 | } 162 | val => panic!("Unknown address family value {val:x?}"), 163 | }; 164 | SocketAddr::new(ip, port) 165 | } 166 | } 167 | } 168 | 169 | impl std::str::FromStr for Address { 170 | type Err = std::net::AddrParseError; 171 | 172 | fn from_str(s: &str) -> Result { 173 | let addr: SocketAddr = s.parse()?; 174 | Ok(Self::from(addr)) 175 | } 176 | } 177 | 178 | impl PartialEq
for Address { 179 | fn eq(&self, other: &Address) -> bool { 180 | unsafe { crate::ffi::rice_address_cmp(self.ffi, other.ffi) == 0 } 181 | } 182 | } 183 | 184 | /// The family of an address. 185 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 186 | #[repr(u32)] 187 | pub enum AddressFamily { 188 | /// Version 4 of the Internet Protocol. 189 | IPV4 = crate::ffi::RICE_ADDRESS_FAMILY_IPV4, 190 | /// Version 6 of the Internet Protocol. 191 | IPV6 = crate::ffi::RICE_ADDRESS_FAMILY_IPV6, 192 | } 193 | 194 | fn mut_override(val: *const T) -> *mut T { 195 | val as *mut T 196 | } 197 | 198 | fn const_override(val: *mut T) -> *const T { 199 | val as *const T 200 | } 201 | 202 | /// Generate a random sequence of characters suitable for username fragments and passwords. 203 | pub fn random_string(len: usize) -> String { 204 | if len == 0 { 205 | return String::new(); 206 | } 207 | unsafe { 208 | let ptr = crate::ffi::rice_random_string(len); 209 | let s = core::ffi::CStr::from_ptr(ptr).to_str().unwrap(); 210 | let ret = s.to_string(); 211 | crate::ffi::rice_string_free(ptr); 212 | ret 213 | } 214 | } 215 | 216 | #[cfg(test)] 217 | pub(crate) mod tests { 218 | use tracing::subscriber::DefaultGuard; 219 | use tracing_subscriber::Layer; 220 | use tracing_subscriber::layer::SubscriberExt; 221 | 222 | pub fn test_init_log() -> DefaultGuard { 223 | let level_filter = std::env::var("RICE_LOG") 224 | .or(std::env::var("RUST_LOG")) 225 | .ok() 226 | .and_then(|var| var.parse::().ok()) 227 | .unwrap_or( 228 | tracing_subscriber::filter::Targets::new().with_default(tracing::Level::TRACE), 229 | ); 230 | let registry = tracing_subscriber::registry().with( 231 | tracing_subscriber::fmt::layer() 232 | .with_file(true) 233 | .with_line_number(true) 234 | .with_level(true) 235 | .with_target(false) 236 | .with_test_writer() 237 | .with_filter(level_filter), 238 | ); 239 | tracing::subscriber::set_default(registry) 240 | } 241 | 242 | #[test] 243 | fn random_string() { 244 | assert!(crate::random_string(0).is_empty()); 245 | assert_eq!(crate::random_string(4).len(), 4); 246 | println!("{}", crate::random_string(128)); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /rice-stun-types/src/attribute/tie_breaker.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use byteorder::{BigEndian, ByteOrder}; 10 | 11 | use stun_types::attribute::{ 12 | Attribute, AttributeFromRaw, AttributeStaticType, AttributeType, AttributeWrite, RawAttribute, 13 | }; 14 | use stun_types::message::StunParseError; 15 | use stun_types::prelude::*; 16 | 17 | /// The IceControlled [`Attribute`] 18 | #[derive(Debug, Clone, PartialEq, Eq)] 19 | pub struct IceControlled { 20 | tie_breaker: u64, 21 | } 22 | 23 | impl AttributeStaticType for IceControlled { 24 | const TYPE: AttributeType = AttributeType::new(0x8029); 25 | } 26 | impl Attribute for IceControlled { 27 | fn get_type(&self) -> AttributeType { 28 | Self::TYPE 29 | } 30 | 31 | fn length(&self) -> u16 { 32 | 8 33 | } 34 | } 35 | impl AttributeWrite for IceControlled { 36 | fn to_raw(&self) -> RawAttribute<'_> { 37 | let mut buf = [0; 8]; 38 | BigEndian::write_u64(&mut buf[..8], self.tie_breaker); 39 | RawAttribute::new(IceControlled::TYPE, &buf).into_owned() 40 | } 41 | fn write_into_unchecked(&self, dest: &mut [u8]) { 42 | self.write_header_unchecked(dest); 43 | BigEndian::write_u64(&mut dest[4..12], self.tie_breaker); 44 | } 45 | } 46 | 47 | impl AttributeFromRaw<'_> for IceControlled { 48 | fn from_raw_ref(raw: &RawAttribute) -> Result 49 | where 50 | Self: Sized, 51 | { 52 | Self::try_from(raw) 53 | } 54 | } 55 | 56 | impl TryFrom<&RawAttribute<'_>> for IceControlled { 57 | type Error = StunParseError; 58 | 59 | fn try_from(raw: &RawAttribute) -> Result { 60 | raw.check_type_and_len(Self::TYPE, 8..=8)?; 61 | Ok(Self { 62 | tie_breaker: BigEndian::read_u64(&raw.value), 63 | }) 64 | } 65 | } 66 | 67 | impl IceControlled { 68 | /// Create a new IceControlled [`Attribute`] 69 | /// 70 | /// # Examples 71 | /// 72 | /// ``` 73 | /// # use rice_stun_types::attribute::*; 74 | /// let ice_controlled = IceControlled::new(1234); 75 | /// assert_eq!(ice_controlled.tie_breaker(), 1234); 76 | /// ``` 77 | pub fn new(tie_breaker: u64) -> Self { 78 | Self { tie_breaker } 79 | } 80 | 81 | /// Retrieve the tie breaker value 82 | /// 83 | /// # Examples 84 | /// 85 | /// ``` 86 | /// # use rice_stun_types::attribute::*; 87 | /// let ice_controlled = IceControlled::new(1234); 88 | /// assert_eq!(ice_controlled.tie_breaker(), 1234); 89 | /// ``` 90 | pub fn tie_breaker(&self) -> u64 { 91 | self.tie_breaker 92 | } 93 | } 94 | 95 | impl core::fmt::Display for IceControlled { 96 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 97 | write!(f, "{}", Self::TYPE) 98 | } 99 | } 100 | 101 | /// The IceControlling [`Attribute`] 102 | #[derive(Debug, Clone, PartialEq, Eq)] 103 | pub struct IceControlling { 104 | tie_breaker: u64, 105 | } 106 | 107 | impl AttributeStaticType for IceControlling { 108 | const TYPE: AttributeType = AttributeType::new(0x802A); 109 | } 110 | 111 | impl Attribute for IceControlling { 112 | fn get_type(&self) -> AttributeType { 113 | Self::TYPE 114 | } 115 | 116 | fn length(&self) -> u16 { 117 | 8 118 | } 119 | } 120 | 121 | impl AttributeWrite for IceControlling { 122 | fn to_raw(&self) -> RawAttribute<'_> { 123 | let mut buf = [0; 8]; 124 | BigEndian::write_u64(&mut buf[..8], self.tie_breaker); 125 | RawAttribute::new(IceControlling::TYPE, &buf).into_owned() 126 | } 127 | fn write_into_unchecked(&self, dest: &mut [u8]) { 128 | self.write_header_unchecked(dest); 129 | BigEndian::write_u64(&mut dest[4..12], self.tie_breaker); 130 | } 131 | } 132 | 133 | impl AttributeFromRaw<'_> for IceControlling { 134 | fn from_raw_ref(raw: &RawAttribute) -> Result 135 | where 136 | Self: Sized, 137 | { 138 | Self::try_from(raw) 139 | } 140 | } 141 | 142 | impl TryFrom<&RawAttribute<'_>> for IceControlling { 143 | type Error = StunParseError; 144 | 145 | fn try_from(raw: &RawAttribute) -> Result { 146 | raw.check_type_and_len(Self::TYPE, 8..=8)?; 147 | Ok(Self { 148 | tie_breaker: BigEndian::read_u64(&raw.value), 149 | }) 150 | } 151 | } 152 | 153 | impl IceControlling { 154 | /// Create a new IceControlling [`Attribute`] 155 | /// 156 | /// # Examples 157 | /// 158 | /// ``` 159 | /// # use rice_stun_types::attribute::*; 160 | /// let ice_controlling = IceControlling::new(1234); 161 | /// assert_eq!(ice_controlling.tie_breaker(), 1234); 162 | /// ``` 163 | pub fn new(tie_breaker: u64) -> Self { 164 | Self { tie_breaker } 165 | } 166 | 167 | /// Create a new IceControlling [`Attribute`] 168 | /// 169 | /// # Examples 170 | /// 171 | /// ``` 172 | /// # use rice_stun_types::attribute::*; 173 | /// let ice_controlling = IceControlling::new(1234); 174 | /// assert_eq!(ice_controlling.tie_breaker(), 1234); 175 | /// ``` 176 | pub fn tie_breaker(&self) -> u64 { 177 | self.tie_breaker 178 | } 179 | } 180 | 181 | impl core::fmt::Display for IceControlling { 182 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 183 | write!(f, "{}", Self::TYPE) 184 | } 185 | } 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use super::*; 190 | use tracing::trace; 191 | 192 | use std::vec::Vec; 193 | 194 | #[test] 195 | fn ice_controlling() { 196 | let _log = crate::tests::test_init_log(); 197 | let tb = 100; 198 | let attr = IceControlling::new(tb); 199 | trace!("{attr}"); 200 | assert_eq!(attr.get_type(), IceControlling::TYPE); 201 | assert_eq!(attr.tie_breaker(), tb); 202 | assert_eq!(attr.length(), 8); 203 | let raw = RawAttribute::from(&attr); 204 | trace!("{raw}"); 205 | assert_eq!(raw.get_type(), IceControlling::TYPE); 206 | let mapped2 = IceControlling::try_from(&raw).unwrap(); 207 | assert_eq!(mapped2.tie_breaker(), tb); 208 | let mut data = [0; 12]; 209 | mapped2.write_into(&mut data).unwrap(); 210 | assert_eq!(data.as_ref(), &raw.to_bytes()); 211 | // truncate by one byte 212 | let mut data: Vec<_> = raw.clone().into(); 213 | let len = data.len(); 214 | BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1); 215 | assert!(matches!( 216 | IceControlling::try_from(&RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()), 217 | Err(StunParseError::Truncated { 218 | expected: 8, 219 | actual: 7 220 | }) 221 | )); 222 | // provide incorrectly typed data 223 | let mut data: Vec<_> = raw.into(); 224 | BigEndian::write_u16(&mut data[0..2], 0); 225 | assert!(matches!( 226 | IceControlling::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()), 227 | Err(StunParseError::WrongAttributeImplementation) 228 | )); 229 | } 230 | 231 | #[test] 232 | fn ice_controlled() { 233 | let _log = crate::tests::test_init_log(); 234 | let tb = 100; 235 | let attr = IceControlled::new(tb); 236 | trace!("{attr}"); 237 | assert_eq!(attr.tie_breaker(), tb); 238 | assert_eq!(attr.length(), 8); 239 | let raw = RawAttribute::from(&attr); 240 | trace!("{raw}"); 241 | assert_eq!(raw.get_type(), IceControlled::TYPE); 242 | let mapped2 = IceControlled::try_from(&raw).unwrap(); 243 | assert_eq!(mapped2.tie_breaker(), tb); 244 | // truncate by one byte 245 | let mut data: Vec<_> = raw.clone().into(); 246 | let len = data.len(); 247 | BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1); 248 | assert!(matches!( 249 | IceControlled::try_from(&RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()), 250 | Err(StunParseError::Truncated { 251 | expected: 8, 252 | actual: 7 253 | }) 254 | )); 255 | // provide incorrectly typed data 256 | let mut data: Vec<_> = raw.into(); 257 | BigEndian::write_u16(&mut data[0..2], 0); 258 | assert!(matches!( 259 | IceControlled::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()), 260 | Err(StunParseError::WrongAttributeImplementation) 261 | )); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /librice/src/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Async runtime abstraction 10 | 11 | use core::future::Future; 12 | 13 | use std::fmt::Debug; 14 | use std::net::SocketAddr; 15 | use std::pin::Pin; 16 | use std::sync::Arc; 17 | use std::task::{Context, Poll}; 18 | use std::time::Instant; 19 | 20 | /// Abstracts I/O and timer operations for runtime independence. 21 | pub trait Runtime: Send + Sync + core::fmt::Debug + 'static { 22 | /// Drive a `Future` to completion in the background. 23 | #[track_caller] 24 | fn spawn(&self, future: Pin + Send>>); 25 | /// Construct a timer that will expire at `i`. 26 | fn new_timer(&self, i: Instant) -> Pin>; 27 | /// Convert socket into the socket type used by this runtime. 28 | fn wrap_udp_socket( 29 | &self, 30 | socket: std::net::UdpSocket, 31 | ) -> std::io::Result>; 32 | /// Construct a new TCP listener. 33 | #[allow(clippy::type_complexity)] 34 | fn new_tcp_listener( 35 | &self, 36 | addr: SocketAddr, 37 | ) -> Pin>> + Send>>; 38 | /// Connect to a TCP server. 39 | #[allow(clippy::type_complexity)] 40 | fn tcp_connect( 41 | &self, 42 | peer: SocketAddr, 43 | ) -> Pin>> + Send>>; 44 | } 45 | 46 | /// Abstract implementation of an async timer for runtime independence. 47 | pub trait AsyncTimer: Send + Debug + 'static { 48 | /// Update the timer to expire at `i`. 49 | fn reset(self: Pin<&mut Self>, i: Instant); 50 | /// Check whether the timer has expired, or register to be woken up at the configured instant. 51 | fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()>; 52 | } 53 | 54 | /// Abstract implementation of an async UDP socket for runtime independence. 55 | pub trait AsyncUdpSocket: Send + Sync + Debug + 'static { 56 | /// Return the local bound address of a socket. 57 | fn local_addr(&self) -> std::io::Result; 58 | /// Receive a UDP datagram, or register to be woken up. 59 | fn poll_recv( 60 | &self, 61 | cx: &mut Context, 62 | dest: &mut [u8], 63 | ) -> Poll>; 64 | /// Send a UDP datagram, or register to be woken up. 65 | fn poll_send( 66 | &self, 67 | cx: &mut Context, 68 | src: &[u8], 69 | to: SocketAddr, 70 | ) -> Poll>; 71 | } 72 | 73 | /// Helper trait implementing for `AsyncUdpSocket`. 74 | pub trait AsyncUdpSocketExt: AsyncUdpSocket { 75 | /// Send a datagram to a particular peer. 76 | fn send_to( 77 | &self, 78 | data: &[u8], 79 | to: SocketAddr, 80 | ) -> impl Future> + Send; 81 | /// Receive a datagram received on a socket. 82 | fn recv_from( 83 | &self, 84 | buf: &mut [u8], 85 | ) -> impl Future> + Send; 86 | } 87 | 88 | impl AsyncUdpSocketExt for T { 89 | async fn send_to(&self, data: &[u8], to: SocketAddr) -> std::io::Result { 90 | core::future::poll_fn(|cx| self.poll_send(cx, data, to)).await 91 | } 92 | async fn recv_from(&self, buf: &mut [u8]) -> std::io::Result<(usize, SocketAddr)> { 93 | core::future::poll_fn(|cx| self.poll_recv(cx, buf)).await 94 | } 95 | } 96 | 97 | /// Abstract implementation of an async UDP socket for runtime independence. 98 | pub trait AsyncTcpListener: Send + Sync + Debug + 'static { 99 | /// Return the local bound address of a socket. 100 | fn local_addr(&self) -> std::io::Result; 101 | /// Receive a UDP datagram, or register to be woken up. 102 | fn poll_next(&self, cx: &mut Context) -> Poll>>; 103 | } 104 | 105 | /// Extension trait for listening for TCP connections. 106 | pub trait AsyncTcpListenerExt { 107 | /// Accept an incoming TCP connection. 108 | fn accept(&self) -> impl Future>> + Send; 109 | } 110 | 111 | impl AsyncTcpListenerExt for T { 112 | async fn accept(&self) -> std::io::Result> { 113 | core::future::poll_fn(|cx| self.poll_next(cx)).await 114 | } 115 | } 116 | 117 | /// Abstract implementation of an async TCP listener for runtime independence. 118 | pub trait AsyncTcpStream: Send + Sync + Debug { 119 | /// Return the local bound address of a socket. 120 | fn local_addr(&self) -> std::io::Result; 121 | /// Return the local bound address of a socket. 122 | fn remote_addr(&self) -> std::io::Result; 123 | /// Split into read and write halves. 124 | fn split(self: Box) -> (Box, Box); 125 | } 126 | 127 | /// Trait for reading from a Tcp connection. 128 | pub trait AsyncTcpStreamRead: Send + Sync + Debug { 129 | /// Return the local bound address of a socket. 130 | fn local_addr(&self) -> std::io::Result; 131 | /// Return the local bound address of a socket. 132 | fn remote_addr(&self) -> std::io::Result; 133 | /// Poll for the progress of reading from a Tcp connection. 134 | fn poll_read(&mut self, cx: &mut Context, buf: &mut [u8]) -> Poll>; 135 | } 136 | 137 | /// Extension trait for reading from a TCP stream. 138 | pub trait AsyncTcpStreamReadExt: AsyncTcpStreamRead { 139 | /// Read from a TCP stream. 140 | fn read(&mut self, dest: &mut [u8]) -> impl Future> + Send; 141 | } 142 | 143 | impl AsyncTcpStreamReadExt for T { 144 | async fn read(&mut self, dest: &mut [u8]) -> std::io::Result { 145 | core::future::poll_fn(|cx| self.poll_read(cx, dest)).await 146 | } 147 | } 148 | 149 | /// Trait for writing to a Tcp connection. 150 | pub trait AsyncTcpStreamWrite: Send + Sync + Debug { 151 | /// Return the local bound address of a socket. 152 | fn local_addr(&self) -> std::io::Result; 153 | /// Return the local bound address of a socket. 154 | fn remote_addr(&self) -> std::io::Result; 155 | /// Poll for writing data to the Tcp connection. 156 | fn poll_write(&mut self, cx: &mut Context, buf: &[u8]) -> Poll>; 157 | /// Poll for flush completion. 158 | fn poll_flush(&mut self, cx: &mut Context) -> Poll>; 159 | /// Poll for shutdown completion. 160 | fn poll_shutdown( 161 | &mut self, 162 | cx: &mut Context, 163 | how: std::net::Shutdown, 164 | ) -> Poll>; 165 | } 166 | 167 | /// Automatically implemented extension trait for writing to a Tcp connection. 168 | pub trait AsyncTcpStreamWriteExt: AsyncTcpStreamWrite { 169 | /// Write the buffer into the Tcp connection. Returns the number of bytes written. 170 | fn write(&mut self, buf: &[u8]) -> impl Future> + Send; 171 | /// Write all the bytes to the Tcp connection or produce an error. 172 | fn write_all(&mut self, buf: &[u8]) -> impl Future> { 173 | async move { 174 | let mut idx = 0; 175 | loop { 176 | if idx >= buf.len() { 177 | return Ok(()); 178 | } 179 | match self.write(&buf[idx..]).await { 180 | Ok(len) => idx += len, 181 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => (), 182 | Err(e) => return Err(e), 183 | } 184 | } 185 | } 186 | } 187 | /// Flush the Tcp connection. 188 | fn flush(&mut self) -> impl Future>; 189 | /// Close the Tcp connection 190 | fn shutdown(&mut self, how: std::net::Shutdown) -> impl Future>; 191 | } 192 | 193 | impl AsyncTcpStreamWriteExt for T { 194 | async fn flush(&mut self) -> std::io::Result<()> { 195 | core::future::poll_fn(|cx| self.poll_flush(cx)).await 196 | } 197 | async fn shutdown(&mut self, how: std::net::Shutdown) -> std::io::Result<()> { 198 | core::future::poll_fn(|cx| self.poll_shutdown(cx, how)).await 199 | } 200 | async fn write(&mut self, buf: &[u8]) -> std::io::Result { 201 | core::future::poll_fn(|cx| self.poll_write(cx, buf)).await 202 | } 203 | } 204 | 205 | /// Automatically select the appropriate runtime from those enabled at compile time. 206 | #[allow(clippy::needless_return)] 207 | pub fn default_runtime() -> Option> { 208 | #[cfg(feature = "runtime-tokio")] 209 | if ::tokio::runtime::Handle::try_current().is_ok() { 210 | return Some(Arc::new(TokioRuntime)); 211 | } 212 | 213 | #[cfg(feature = "runtime-smol")] 214 | { 215 | return Some(Arc::new(SmolRuntime)); 216 | } 217 | 218 | #[cfg(not(feature = "runtime-smol"))] 219 | None 220 | } 221 | 222 | #[cfg(feature = "runtime-smol")] 223 | mod smol; 224 | #[cfg(feature = "runtime-smol")] 225 | pub use smol::SmolRuntime; 226 | #[cfg(feature = "runtime-tokio")] 227 | mod tokio; 228 | #[cfg(feature = "runtime-tokio")] 229 | pub use tokio::TokioRuntime; 230 | -------------------------------------------------------------------------------- /librice/examples/icegather.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use clap::Parser; 10 | 11 | use librice::agent::{Agent, AgentMessage, TurnConfig, TurnCredentials}; 12 | use rice_c::{AddressFamily, turn::TurnTlsConfig}; 13 | 14 | use std::{io, net::SocketAddr, str::FromStr}; 15 | 16 | use futures::prelude::*; 17 | 18 | use librice::candidate::TransportType; 19 | 20 | fn init_logs() { 21 | use tracing_subscriber::Layer; 22 | use tracing_subscriber::layer::SubscriberExt; 23 | 24 | let level_filter = std::env::var("RICE_LOG") 25 | .ok() 26 | .and_then(|var| var.parse::().ok()) 27 | .unwrap_or(tracing_subscriber::filter::Targets::new().with_default(tracing::Level::ERROR)); 28 | let registry = tracing_subscriber::registry().with( 29 | tracing_subscriber::fmt::layer() 30 | .with_file(true) 31 | .with_line_number(true) 32 | .with_level(true) 33 | .with_target(false) 34 | .with_test_writer() 35 | .with_filter(level_filter), 36 | ); 37 | tracing::subscriber::set_global_default(registry).unwrap(); 38 | } 39 | 40 | #[derive(Clone, Debug)] 41 | struct TurnServerConfig { 42 | addr: SocketAddr, 43 | client_transport: TransportType, 44 | user: String, 45 | pass: String, 46 | tls: Option, 47 | } 48 | 49 | #[derive(Clone, Debug)] 50 | enum TlsConfig { 51 | Rustls(Option), 52 | Openssl, 53 | } 54 | 55 | impl clap::builder::ValueParserFactory for TurnServerConfig { 56 | type Parser = TurnServerConfigParser; 57 | fn value_parser() -> Self::Parser { 58 | TurnServerConfigParser 59 | } 60 | } 61 | 62 | #[derive(Debug, Clone)] 63 | struct TurnServerConfigParser; 64 | impl clap::builder::TypedValueParser for TurnServerConfigParser { 65 | type Value = TurnServerConfig; 66 | fn parse_ref( 67 | &self, 68 | cmd: &clap::Command, 69 | _arg: Option<&clap::Arg>, 70 | value: &std::ffi::OsStr, 71 | ) -> Result { 72 | let split = value.to_str().ok_or_else(|| { 73 | clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd) 74 | })?; 75 | let mut split = split.splitn(6, ","); 76 | let Some(addr) = split.next() else { 77 | eprintln!("No TURN address"); 78 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 79 | }; 80 | let Ok(addr) = SocketAddr::from_str(addr) else { 81 | eprintln!("Failed to parse TURN address"); 82 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 83 | }; 84 | let Some(client_transport) = split.next() else { 85 | eprintln!("No TURN client transport"); 86 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 87 | }; 88 | let Ok(client_transport) = TransportType::from_str(client_transport) else { 89 | eprintln!("Failed to parse TURN client transport"); 90 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 91 | }; 92 | let Some(user) = split.next() else { 93 | eprintln!("No TURN user name"); 94 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 95 | }; 96 | let Some(pass) = split.next() else { 97 | eprintln!("No TURN password"); 98 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 99 | }; 100 | let tls = if let Some(tls) = split.next() { 101 | match tls { 102 | "rustls" => Some(TlsConfig::Rustls(split.next().map(|s| s.to_string()))), 103 | "openssl" => Some(TlsConfig::Openssl), 104 | tls_name => { 105 | eprintln!("Unknown TLS implementation: {tls_name}"); 106 | return Err( 107 | clap::Error::new(clap::error::ErrorKind::InvalidValue).with_cmd(cmd) 108 | ); 109 | } 110 | } 111 | } else { 112 | None 113 | }; 114 | if split.next().is_some() { 115 | eprintln!("trailing unhandled options"); 116 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 117 | } 118 | 119 | Ok(TurnServerConfig { 120 | addr, 121 | client_transport, 122 | user: user.to_string(), 123 | pass: pass.to_string(), 124 | tls, 125 | }) 126 | } 127 | } 128 | 129 | #[derive(Debug, Clone, Parser)] 130 | struct StunServer { 131 | transport: TransportType, 132 | server: SocketAddr, 133 | } 134 | 135 | impl clap::builder::ValueParserFactory for StunServer { 136 | type Parser = StunServerParser; 137 | fn value_parser() -> Self::Parser { 138 | StunServerParser 139 | } 140 | } 141 | #[derive(Debug, Clone)] 142 | struct StunServerParser; 143 | impl clap::builder::TypedValueParser for StunServerParser { 144 | type Value = StunServer; 145 | fn parse_ref( 146 | &self, 147 | cmd: &clap::Command, 148 | _arg: Option<&clap::Arg>, 149 | value: &std::ffi::OsStr, 150 | ) -> Result { 151 | let split = value.to_str().ok_or_else(|| { 152 | clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd) 153 | })?; 154 | let mut split = split.splitn(2, ","); 155 | let Some(server) = split.next() else { 156 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 157 | }; 158 | let Ok(server) = SocketAddr::from_str(server) else { 159 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 160 | }; 161 | let transport = split 162 | .next() 163 | .and_then(|s| TransportType::from_str(s).ok()) 164 | .unwrap_or(TransportType::Udp); 165 | if split.next().is_some() { 166 | return Err(clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd)); 167 | } 168 | 169 | Ok(StunServer { transport, server }) 170 | } 171 | } 172 | 173 | #[derive(Debug, Parser)] 174 | #[command(version, about)] 175 | struct Cli { 176 | #[arg(long, value_name = "ADDRESS[,udp|tcp]", action = clap::ArgAction::Append)] 177 | stun_server: Vec, 178 | #[arg( 179 | long, 180 | value_name = "ADDRESS,udp|tcp,USERNAME,PASSWORD[,openssl|rustls[,SERVER_IDENTITY]]", 181 | action = clap::ArgAction::Append, 182 | )] 183 | turn_server: Vec, 184 | } 185 | 186 | #[cfg(feature = "runtime-tokio")] 187 | fn tokio_runtime() -> tokio::runtime::Runtime { 188 | tokio::runtime::Builder::new_current_thread() 189 | .enable_all() 190 | .build() 191 | .unwrap() 192 | } 193 | 194 | async fn run() -> io::Result<()> { 195 | init_logs(); 196 | let cli = Cli::parse(); 197 | eprintln!("command parameters: {cli:?}"); 198 | let agent = Agent::builder().build(); 199 | for server in cli.stun_server { 200 | agent.add_stun_server(server.transport, server.server); 201 | } 202 | for ts in cli.turn_server { 203 | let credentials = TurnCredentials::new(&ts.user, &ts.pass); 204 | let tls_config = ts.tls.and_then(|tls| match tls { 205 | TlsConfig::Openssl => Some(TurnTlsConfig::new_openssl(ts.client_transport)), 206 | TlsConfig::Rustls(server_name) => { 207 | if ts.client_transport != TransportType::Tcp { 208 | eprintln!( 209 | "rustls only supports TCP connections, {} was requested", 210 | ts.client_transport 211 | ); 212 | None 213 | } else if let Some(server_name) = server_name { 214 | Some(TurnTlsConfig::new_rustls_with_dns(&server_name)) 215 | } else { 216 | Some(TurnTlsConfig::new_rustls_with_ip(&ts.addr.into())) 217 | } 218 | } 219 | }); 220 | let turn_cfg = TurnConfig::new( 221 | ts.client_transport, 222 | ts.addr.into(), 223 | credentials.clone(), 224 | &[AddressFamily::IPV4, AddressFamily::IPV6], 225 | tls_config, 226 | ); 227 | agent.add_turn_server(turn_cfg); 228 | } 229 | let stream = agent.add_stream(); 230 | let _comp = stream.add_component(); 231 | 232 | stream.gather_candidates().await.unwrap(); 233 | let mut messages = agent.messages(); 234 | while let Some(msg) = messages.next().await { 235 | match msg { 236 | AgentMessage::GatheredCandidate(_stream, candidate) => { 237 | println! {"{}", candidate.candidate().to_sdp_string()} 238 | } 239 | AgentMessage::GatheringComplete(_component) => break, 240 | _ => (), 241 | } 242 | } 243 | Ok(()) 244 | } 245 | 246 | #[allow(unreachable_code)] 247 | fn main() -> io::Result<()> { 248 | #[cfg(feature = "runtime-smol")] 249 | return smol::block_on(run()); 250 | 251 | #[cfg(feature = "runtime-tokio")] 252 | return tokio_runtime().block_on(run()); 253 | 254 | Err(std::io::Error::new( 255 | std::io::ErrorKind::NotFound, 256 | "No async runtime available", 257 | )) 258 | } 259 | -------------------------------------------------------------------------------- /rice-c/src/turn.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! TURN module. 10 | 11 | use crate::candidate::TransportType; 12 | use crate::{AddressFamily, const_override}; 13 | 14 | pub use crate::stream::Credentials as TurnCredentials; 15 | 16 | /// Configuration for a particular TURN server connection. 17 | #[derive(Debug)] 18 | pub struct TurnConfig { 19 | ffi: *mut crate::ffi::RiceTurnConfig, 20 | } 21 | 22 | unsafe impl Send for TurnConfig {} 23 | 24 | impl TurnConfig { 25 | /// Create a new [`TurnConfig`] from the provided details. 26 | /// 27 | /// # Examples 28 | /// ``` 29 | /// # use rice_c::AddressFamily; 30 | /// # use rice_c::turn::{TurnConfig, TurnCredentials}; 31 | /// # use rice_c::candidate::TransportType; 32 | /// # use core::net::SocketAddr; 33 | /// let credentials = TurnCredentials::new("user", "pass"); 34 | /// let server_addr = rice_c::Address::from("127.0.0.1:3478".parse::().unwrap()); 35 | /// let config = TurnConfig::new( 36 | /// TransportType::Udp, 37 | /// server_addr.clone(), 38 | /// credentials.clone(), 39 | /// &[AddressFamily::IPV4], 40 | /// None, 41 | /// ); 42 | /// assert_eq!(config.client_transport(), TransportType::Udp); 43 | /// assert_eq!(config.addr(), server_addr); 44 | /// // FIXME 45 | /// //assert_eq!(config.credentials().username(), credentials.username()); 46 | /// ``` 47 | pub fn new( 48 | client_transport: TransportType, 49 | turn_server: crate::Address, 50 | credentials: TurnCredentials, 51 | families: &[AddressFamily], 52 | tls_config: Option, 53 | ) -> Self { 54 | unsafe { 55 | let tls_config = if let Some(tls_config) = tls_config { 56 | tls_config.into_c_full() 57 | } else { 58 | core::ptr::null_mut() 59 | }; 60 | let families = families 61 | .iter() 62 | .map(|&family| family as u32) 63 | .collect::>(); 64 | let ffi = crate::ffi::rice_turn_config_new( 65 | client_transport.into(), 66 | const_override(turn_server.as_c()), 67 | credentials.into_c_none(), 68 | families.len(), 69 | families.as_ptr(), 70 | tls_config, 71 | ); 72 | Self { ffi } 73 | } 74 | } 75 | 76 | /// The TLS configuration to use for connecting to this TURN server. 77 | pub fn tls_config(&self) -> Option { 78 | unsafe { 79 | let ret = crate::ffi::rice_turn_config_get_tls_config(self.ffi); 80 | if ret.is_null() { 81 | None 82 | } else { 83 | match crate::ffi::rice_tls_config_variant(ret) { 84 | #[cfg(feature = "openssl")] 85 | crate::ffi::RICE_TLS_VARIANT_OPENSSL => Some(TurnTlsConfig::Openssl(ret)), 86 | #[cfg(feature = "rustls")] 87 | crate::ffi::RICE_TLS_VARIANT_RUSTLS => Some(TurnTlsConfig::Rustls(ret)), 88 | _ => None, 89 | } 90 | } 91 | } 92 | } 93 | 94 | /// The TURN server address to connect to. 95 | pub fn addr(&self) -> crate::Address { 96 | unsafe { crate::Address::from_c_full(crate::ffi::rice_turn_config_get_addr(self.ffi)) } 97 | } 98 | 99 | /// The [`TransportType`] between the client and the TURN server. 100 | pub fn client_transport(&self) -> TransportType { 101 | unsafe { crate::ffi::rice_turn_config_get_client_transport(self.ffi).into() } 102 | } 103 | 104 | /// The credentials for accessing the TURN server. 105 | pub fn credentials(&self) -> TurnCredentials { 106 | unsafe { 107 | TurnCredentials::from_c_full(crate::ffi::rice_turn_config_get_credentials(self.ffi)) 108 | } 109 | } 110 | 111 | pub(crate) fn into_c_full(self) -> *mut crate::ffi::RiceTurnConfig { 112 | let ret = self.ffi; 113 | core::mem::forget(self); 114 | ret 115 | } 116 | } 117 | 118 | impl Clone for TurnConfig { 119 | fn clone(&self) -> Self { 120 | unsafe { 121 | Self { 122 | ffi: crate::ffi::rice_turn_config_ref(self.ffi), 123 | } 124 | } 125 | } 126 | } 127 | 128 | impl Drop for TurnConfig { 129 | fn drop(&mut self) { 130 | unsafe { 131 | crate::ffi::rice_turn_config_unref(self.ffi); 132 | } 133 | } 134 | } 135 | 136 | /// Configuration parameters for TURN use over (D)TLS. 137 | #[derive(Debug)] 138 | pub enum TurnTlsConfig { 139 | /// Rustls variant for TLS configuration. 140 | #[cfg(feature = "rustls")] 141 | Rustls(*mut crate::ffi::RiceTlsConfig), 142 | /// Openssl variant for TLS configuration. 143 | #[cfg(feature = "openssl")] 144 | Openssl(*mut crate::ffi::RiceTlsConfig), 145 | } 146 | 147 | impl Clone for TurnTlsConfig { 148 | fn clone(&self) -> Self { 149 | match self { 150 | #[cfg(feature = "rustls")] 151 | Self::Rustls(cfg) => unsafe { Self::Rustls(crate::ffi::rice_tls_config_ref(*cfg)) }, 152 | #[cfg(feature = "openssl")] 153 | Self::Openssl(cfg) => unsafe { Self::Openssl(crate::ffi::rice_tls_config_ref(*cfg)) }, 154 | } 155 | } 156 | } 157 | 158 | impl Drop for TurnTlsConfig { 159 | fn drop(&mut self) { 160 | match self { 161 | #[cfg(feature = "rustls")] 162 | Self::Rustls(cfg) => unsafe { crate::ffi::rice_tls_config_unref(*cfg) }, 163 | #[cfg(feature = "openssl")] 164 | Self::Openssl(cfg) => unsafe { crate::ffi::rice_tls_config_unref(*cfg) }, 165 | } 166 | } 167 | } 168 | 169 | impl TurnTlsConfig { 170 | /// Construct a new client Rustls TLS configuration with the specified server name. 171 | #[cfg(feature = "rustls")] 172 | pub fn new_rustls_with_dns(server_name: &str) -> Self { 173 | let server_str = std::ffi::CString::new(server_name).unwrap(); 174 | unsafe { 175 | Self::Rustls(crate::ffi::rice_tls_config_new_rustls_with_dns( 176 | server_str.as_ptr(), 177 | )) 178 | } 179 | } 180 | 181 | /// Construct a new client Rustls TLS configuration with the specified ip. 182 | #[cfg(feature = "rustls")] 183 | pub fn new_rustls_with_ip(addr: &crate::Address) -> Self { 184 | unsafe { Self::Rustls(crate::ffi::rice_tls_config_new_rustls_with_ip(addr.as_c())) } 185 | } 186 | 187 | /// Construct a new client OpenSSL TLS configuration with the specified transport. 188 | #[cfg(feature = "openssl")] 189 | pub fn new_openssl(transport: TransportType) -> Self { 190 | unsafe { Self::Openssl(crate::ffi::rice_tls_config_new_openssl(transport.into())) } 191 | } 192 | 193 | pub(crate) fn into_c_full(self) -> *mut crate::ffi::RiceTlsConfig { 194 | #[allow(unreachable_patterns)] 195 | let ret = match self { 196 | #[cfg(feature = "rustls")] 197 | Self::Rustls(cfg) => cfg, 198 | #[cfg(feature = "openssl")] 199 | Self::Openssl(cfg) => cfg, 200 | _ => core::ptr::null_mut(), 201 | }; 202 | core::mem::forget(self); 203 | ret 204 | } 205 | } 206 | 207 | #[cfg(test)] 208 | mod tests { 209 | use super::*; 210 | 211 | use core::net::SocketAddr; 212 | 213 | fn turn_server_address() -> crate::Address { 214 | "127.0.0.1:3478".parse::().unwrap().into() 215 | } 216 | 217 | fn turn_credentials() -> TurnCredentials { 218 | TurnCredentials::new("tuser", "tpass") 219 | } 220 | 221 | #[test] 222 | fn test_config_getter() { 223 | let cfg = TurnConfig::new( 224 | TransportType::Udp, 225 | turn_server_address(), 226 | turn_credentials(), 227 | &[AddressFamily::IPV4], 228 | None, 229 | ); 230 | assert_eq!(cfg.addr(), turn_server_address()); 231 | assert_eq!(cfg.client_transport(), TransportType::Udp); 232 | // TODO credentials 233 | //assert_eq!(cfg.credentials().username(), turn_credentials().username()); 234 | assert!(cfg.tls_config().is_none()); 235 | } 236 | 237 | #[cfg(feature = "rustls")] 238 | mod rustls { 239 | use super::*; 240 | #[test] 241 | fn test_rustls_roundtrip() { 242 | let dns = "turn.example.com"; 243 | let cfg = TurnTlsConfig::new_rustls_with_dns(dns); 244 | drop(cfg); 245 | let addr = "127.0.0.1:3478".parse::().unwrap(); 246 | let _cfg = TurnTlsConfig::new_rustls_with_ip(&addr.into()); 247 | } 248 | 249 | #[test] 250 | fn test_rustls_getter() { 251 | let dns = "turn.example.com"; 252 | let tls = TurnTlsConfig::new_rustls_with_dns(dns); 253 | let cfg = TurnConfig::new( 254 | TransportType::Udp, 255 | turn_server_address(), 256 | turn_credentials(), 257 | &[AddressFamily::IPV4], 258 | Some(tls.clone()), 259 | ); 260 | let retrieved = cfg.tls_config().unwrap(); 261 | assert!(matches!(retrieved, TurnTlsConfig::Rustls(_))); 262 | } 263 | } 264 | 265 | #[cfg(feature = "openssl")] 266 | mod openssl { 267 | use super::*; 268 | #[test] 269 | fn test_openssl_roundtrip() { 270 | let _cfg = TurnTlsConfig::new_openssl(TransportType::Udp); 271 | } 272 | 273 | #[test] 274 | fn test_openssl_getter() { 275 | let tls = TurnTlsConfig::new_openssl(TransportType::Udp); 276 | let cfg = TurnConfig::new( 277 | TransportType::Udp, 278 | turn_server_address(), 279 | turn_credentials(), 280 | &[AddressFamily::IPV4], 281 | Some(tls), 282 | ); 283 | let retrieved = cfg.tls_config().unwrap(); 284 | assert!(matches!(retrieved, TurnTlsConfig::Openssl(_))); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /librice/src/component.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! A [`Component`] in an ICE [`Stream`](crate::stream::Stream) 10 | 11 | use std::collections::VecDeque; 12 | use std::sync::{Arc, Mutex, Weak}; 13 | 14 | use std::task::{Poll, Waker}; 15 | 16 | use rice_c::Instant; 17 | use rice_c::candidate::CandidatePair; 18 | use rice_c::prelude::*; 19 | 20 | pub use rice_c::component::ComponentConnectionState; 21 | use rice_c::stream::RecvData as CRecvData; 22 | use tracing::trace; 23 | 24 | use crate::agent::AgentError; 25 | use crate::socket::StunChannel; 26 | 27 | use futures::prelude::*; 28 | 29 | /// The component id for RTP streaming (and general data). 30 | pub const RTP: usize = 1; 31 | /// The component id for RTCP streaming (if rtcp-mux is not in use). 32 | pub const RTCP: usize = 2; 33 | 34 | /// A [`Component`] within an ICE [`Stream`](crate::stream::Stream`) 35 | #[derive(Debug, Clone)] 36 | pub struct Component { 37 | base_instant: std::time::Instant, 38 | proto: rice_c::component::Component, 39 | #[allow(dead_code)] 40 | stream_id: usize, 41 | pub(crate) id: usize, 42 | pub(crate) inner: Arc>, 43 | weak_stream: Weak, 44 | } 45 | 46 | impl Component { 47 | pub(crate) fn new( 48 | stream_id: usize, 49 | proto: rice_c::component::Component, 50 | base_instant: std::time::Instant, 51 | weak_stream: Weak, 52 | ) -> Self { 53 | Self { 54 | stream_id, 55 | id: proto.id(), 56 | proto, 57 | inner: Arc::new(Mutex::new(ComponentInner::new())), 58 | base_instant, 59 | weak_stream, 60 | } 61 | } 62 | 63 | /// The component identifier within a particular ICE [`Stream`] 64 | pub fn id(&self) -> usize { 65 | self.id 66 | } 67 | 68 | /// Retrieve the [`Stream`](crate::stream::Stream) for this component. 69 | pub fn stream(&self) -> crate::stream::Stream { 70 | crate::stream::Stream::from_state(self.weak_stream.upgrade().unwrap()) 71 | } 72 | 73 | /// Retrieve the current state of a `Component` 74 | /// 75 | /// # Examples 76 | /// 77 | /// The initial state is `ComponentState::New` 78 | /// 79 | /// ``` 80 | /// # use librice::component::{Component, ComponentConnectionState}; 81 | /// # use librice::agent::Agent; 82 | /// # use librice::stream::Stream; 83 | /// # #[cfg(feature = "runtime-tokio")] 84 | /// # let runtime = tokio::runtime::Builder::new_current_thread() 85 | /// # .enable_all() 86 | /// # .build() 87 | /// # .unwrap(); 88 | /// # #[cfg(feature = "runtime-tokio")] 89 | /// # let _runtime = runtime.enter(); 90 | /// let agent = Agent::default(); 91 | /// let stream = agent.add_stream(); 92 | /// let component = stream.add_component().unwrap(); 93 | /// assert_eq!(component.state(), ComponentConnectionState::New); 94 | /// ``` 95 | pub fn state(&self) -> ComponentConnectionState { 96 | self.proto.state() 97 | } 98 | 99 | /// Send data to the peer using the established communication channel. This will not succeed 100 | /// until the component is in the [`Connected`](ComponentConnectionState::Connected) state. 101 | pub async fn send(&self, data: &[u8]) -> Result<(), AgentError> { 102 | let transmit; 103 | let (mut channel, to) = { 104 | let inner = self.inner.lock().unwrap(); 105 | let selected_pair = inner.selected_pair.as_ref().ok_or(std::io::Error::new( 106 | std::io::ErrorKind::NotFound, 107 | "No selected pair", 108 | ))?; 109 | let to = selected_pair.pair.remote.address(); 110 | (selected_pair.socket.clone(), to) 111 | }; 112 | 113 | transmit = self 114 | .proto 115 | .send(data, Instant::from_std(self.base_instant))?; 116 | 117 | trace!("sending {} bytes to {:?}", data.len(), to); 118 | channel 119 | .send_to(transmit.data, transmit.to.as_socket()) 120 | .await?; 121 | 122 | Ok(()) 123 | } 124 | 125 | /// A stream that provides the data that has been sent from the peer to this component. 126 | pub fn recv(&self) -> impl Stream + '_ { 127 | ComponentRecv { 128 | inner: self.inner.clone(), 129 | } 130 | } 131 | 132 | pub(crate) fn set_selected_pair(&self, selected: SelectedPair) -> Result<(), AgentError> { 133 | let mut inner = self.inner.lock().unwrap(); 134 | tracing::info!("set selected pair {selected:?}"); 135 | self.proto.set_selected_pair(selected.pair.clone())?; 136 | inner.selected_pair = Some(selected); 137 | 138 | Ok(()) 139 | } 140 | 141 | /// The pair that has been selected for communication. Will not provide a useful value until 142 | /// ICE negotiation has completed successfully. 143 | pub fn selected_pair(&self) -> Option { 144 | self.inner 145 | .lock() 146 | .unwrap() 147 | .selected_pair 148 | .clone() 149 | .map(|selected| selected.pair.clone()) 150 | } 151 | } 152 | 153 | #[derive(Debug)] 154 | pub(crate) struct ComponentInner { 155 | selected_pair: Option, 156 | received_data: VecDeque, 157 | recv_waker: Option, 158 | } 159 | 160 | impl ComponentInner { 161 | fn new() -> Self { 162 | Self { 163 | selected_pair: None, 164 | received_data: VecDeque::default(), 165 | recv_waker: None, 166 | } 167 | } 168 | 169 | #[tracing::instrument( 170 | name = "component_incoming_data" 171 | skip(self, data) 172 | fields( 173 | data.len = data.len() 174 | ) 175 | )] 176 | pub(crate) fn handle_incoming_data(&mut self, data: RecvData) { 177 | self.received_data.push_back(data); 178 | if let Some(waker) = self.recv_waker.take() { 179 | waker.wake(); 180 | } 181 | } 182 | } 183 | 184 | /// Data that has been received from a peer. 185 | #[derive(Debug)] 186 | pub enum RecvData { 187 | /// Rust allocated Vec. 188 | Vec(Vec), 189 | /// C allocated data. 190 | Proto(CRecvData), 191 | } 192 | 193 | impl From> for RecvData { 194 | fn from(value: Vec) -> Self { 195 | Self::Vec(value) 196 | } 197 | } 198 | 199 | impl From for RecvData { 200 | fn from(value: CRecvData) -> Self { 201 | Self::Proto(value) 202 | } 203 | } 204 | 205 | impl core::ops::Deref for RecvData { 206 | type Target = [u8]; 207 | 208 | fn deref(&self) -> &Self::Target { 209 | match self { 210 | Self::Vec(vec) => vec, 211 | Self::Proto(proto) => proto.deref(), 212 | } 213 | } 214 | } 215 | 216 | #[derive(Debug)] 217 | #[doc(hidden)] 218 | pub struct ComponentRecv { 219 | inner: Arc>, 220 | } 221 | 222 | impl futures::Stream for ComponentRecv { 223 | type Item = RecvData; 224 | 225 | fn poll_next( 226 | self: std::pin::Pin<&mut Self>, 227 | cx: &mut std::task::Context<'_>, 228 | ) -> std::task::Poll> { 229 | let mut inner = self.inner.lock().unwrap(); 230 | if let Some(data) = inner.received_data.pop_front() { 231 | return Poll::Ready(Some(data)); 232 | } 233 | inner.recv_waker = Some(cx.waker().clone()); 234 | std::task::Poll::Pending 235 | } 236 | } 237 | 238 | #[derive(Debug, Clone)] 239 | pub(crate) struct SelectedPair { 240 | pair: rice_c::candidate::CandidatePair, 241 | socket: StunChannel, 242 | } 243 | 244 | impl SelectedPair { 245 | pub(crate) fn new(pair: rice_c::candidate::CandidatePair, socket: StunChannel) -> Self { 246 | Self { pair, socket } 247 | } 248 | } 249 | 250 | #[cfg(test)] 251 | mod tests { 252 | use rice_c::candidate::{Candidate, CandidateType, TransportType}; 253 | 254 | use super::*; 255 | use crate::{agent::Agent, socket::UdpSocketChannel}; 256 | 257 | fn init() { 258 | crate::tests::test_init_log(); 259 | } 260 | 261 | #[test] 262 | fn initial_state_new() { 263 | #[cfg(feature = "runtime-tokio")] 264 | let _runtime = crate::tests::tokio_runtime().enter(); 265 | init(); 266 | let agent = Agent::builder().build(); 267 | let s = agent.add_stream(); 268 | let c = s.add_component().unwrap(); 269 | assert_eq!(c.state(), ComponentConnectionState::New); 270 | } 271 | 272 | #[cfg(feature = "runtime-smol")] 273 | #[test] 274 | fn smol_send_recv() { 275 | smol::block_on(send_recv()); 276 | } 277 | 278 | #[cfg(feature = "runtime-tokio")] 279 | #[test] 280 | fn tokio_send_recv() { 281 | crate::tests::tokio_runtime().block_on(send_recv()); 282 | } 283 | 284 | async fn send_recv() { 285 | init(); 286 | let runtime = crate::runtime::default_runtime().unwrap(); 287 | let agent = Agent::builder().controlling(false).build(); 288 | let stream = agent.add_stream(); 289 | let send = stream.add_component().unwrap(); 290 | let local_socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); 291 | let remote_socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); 292 | let local_addr = local_socket.local_addr().unwrap(); 293 | let remote_addr = remote_socket.local_addr().unwrap(); 294 | let local_channel = StunChannel::Udp(UdpSocketChannel::new( 295 | runtime.wrap_udp_socket(local_socket).unwrap(), 296 | )); 297 | 298 | let local_cand = Candidate::builder( 299 | 1, 300 | CandidateType::Host, 301 | TransportType::Udp, 302 | "0", 303 | local_addr.into(), 304 | ) 305 | .build(); 306 | let remote_cand = Candidate::builder( 307 | 1, 308 | CandidateType::Host, 309 | TransportType::Udp, 310 | "0", 311 | remote_addr.into(), 312 | ) 313 | .build(); 314 | let candidate_pair = CandidatePair::new(local_cand.to_owned(), remote_cand.to_owned()); 315 | let selected_pair = SelectedPair::new(candidate_pair, local_channel); 316 | 317 | send.set_selected_pair(selected_pair.clone()).unwrap(); 318 | assert_eq!(selected_pair.pair, send.selected_pair().unwrap()); 319 | 320 | let data = vec![3; 4]; 321 | send.send(&data).await.unwrap(); 322 | let mut recved = vec![0; 16]; 323 | let (len, from) = remote_socket.recv_from(&mut recved).unwrap(); 324 | let recved = &recved[..len]; 325 | assert_eq!(from, local_addr); 326 | assert_eq!(recved, data); 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /librice/src/socket.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Matthew Waters 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Socket helpers for handling UDP and TCP transports 10 | 11 | use std::net::SocketAddr; 12 | use std::sync::{Arc, Mutex}; 13 | 14 | use futures::prelude::*; 15 | 16 | use tracing::{debug_span, info, trace, warn}; 17 | use tracing_futures::Instrument; 18 | 19 | use crate::runtime::{ 20 | AsyncTcpStream, AsyncTcpStreamRead, AsyncTcpStreamReadExt, AsyncTcpStreamWriteExt, 21 | AsyncUdpSocket, AsyncUdpSocketExt, Runtime, 22 | }; 23 | use crate::utils::DebugWrapper; 24 | 25 | use rice_c::candidate::TransportType; 26 | 27 | pub(crate) struct Transmit + std::fmt::Debug> { 28 | pub transport: TransportType, 29 | pub from: SocketAddr, 30 | pub to: SocketAddr, 31 | pub data: T, 32 | } 33 | 34 | impl + std::fmt::Debug> Transmit { 35 | pub fn new(data: T, transport: TransportType, from: SocketAddr, to: SocketAddr) -> Self { 36 | Self { 37 | data, 38 | transport, 39 | from, 40 | to, 41 | } 42 | } 43 | } 44 | 45 | const MAX_STUN_MESSAGE_SIZE: usize = 1500 * 2; 46 | 47 | /// A combined socket interface for both UDP and TCP 48 | #[derive(Debug, Clone)] 49 | pub enum StunChannel { 50 | /// A UDP socket. 51 | Udp(UdpSocketChannel), 52 | /// A TCP socket. 53 | Tcp(TcpChannel), 54 | } 55 | 56 | /// Data and address 57 | #[derive(Debug, Clone)] 58 | struct DataAddress { 59 | /// The data 60 | pub data: Vec, 61 | /// An address 62 | pub address: SocketAddr, 63 | } 64 | 65 | impl DataAddress { 66 | fn new(data: Vec, address: SocketAddr) -> Self { 67 | Self { data, address } 68 | } 69 | } 70 | 71 | impl StunChannel { 72 | /// Close the socket 73 | pub async fn close(&mut self) -> Result<(), std::io::Error> { 74 | match self { 75 | StunChannel::Udp(c) => c.close(), 76 | StunChannel::Tcp(c) => c.close().await, 77 | } 78 | } 79 | 80 | /// The transport of this socket 81 | pub fn transport(&self) -> TransportType { 82 | match self { 83 | StunChannel::Udp(_) => TransportType::Udp, 84 | StunChannel::Tcp(_) => TransportType::Tcp, 85 | } 86 | } 87 | 88 | /// Send data to a specified address 89 | pub async fn send_to(&mut self, data: &[u8], to: SocketAddr) -> Result<(), std::io::Error> { 90 | match self { 91 | StunChannel::Udp(udp) => udp.send_to(data, to).await, 92 | StunChannel::Tcp(tcp) => tcp.send_to(data, to).await, 93 | } 94 | } 95 | 96 | /// Return a stream of received data. 97 | /// 98 | /// WARNING: Any data received will only be dispatched to a single returned stream 99 | pub fn recv(&mut self) -> impl Stream, SocketAddr)> + '_ { 100 | match self { 101 | StunChannel::Udp(udp) => udp.recv().left_stream(), 102 | StunChannel::Tcp(tcp) => tcp.recv().right_stream(), 103 | } 104 | } 105 | 106 | /// The local address of the socket (where available) 107 | pub fn local_addr(&self) -> Result { 108 | match self { 109 | StunChannel::Udp(c) => c.local_addr(), 110 | StunChannel::Tcp(c) => c.local_addr(), 111 | } 112 | } 113 | 114 | /// The remote address of the socket (where available) 115 | pub fn remote_addr(&self) -> Result { 116 | match self { 117 | StunChannel::Udp(_) => Err(std::io::Error::new( 118 | std::io::ErrorKind::NotFound, 119 | "connection-less udp doesn't have a remote addr", 120 | )), 121 | StunChannel::Tcp(c) => c.remote_addr(), 122 | } 123 | } 124 | } 125 | 126 | /// A UDP socket 127 | #[derive(Debug, Clone)] 128 | pub struct UdpSocketChannel { 129 | socket: Arc, 130 | inner: DebugWrapper>>, 131 | } 132 | 133 | #[derive(Debug)] 134 | struct UdpSocketChannelInner { 135 | closed: bool, 136 | } 137 | 138 | impl UdpSocketChannel { 139 | /// Create a new UDP socket 140 | pub fn new(socket: Arc) -> Self { 141 | Self { 142 | socket, 143 | inner: DebugWrapper::wrap( 144 | Arc::new(Mutex::new(UdpSocketChannelInner { closed: false })), 145 | "...", 146 | ), 147 | } 148 | } 149 | 150 | /// Send a piece of data to a particular address 151 | pub async fn send_to(&self, data: &[u8], to: SocketAddr) -> std::io::Result<()> { 152 | { 153 | let inner = self.inner.lock().unwrap(); 154 | if inner.closed { 155 | return Err(std::io::Error::other("Connection closed")); 156 | } 157 | } 158 | trace!( 159 | "udp socket send_to {:?} bytes from {:?} to {:?}", 160 | data.len(), 161 | self.local_addr(), 162 | to 163 | ); 164 | self.socket.send_to(data, to).await?; 165 | Ok(()) 166 | } 167 | 168 | /// Close the socket for any further processing. 169 | pub fn close(&self) -> Result<(), std::io::Error> { 170 | { 171 | let mut inner = self.inner.lock().unwrap(); 172 | inner.closed = true; 173 | }; 174 | Ok(()) 175 | } 176 | 177 | /// A channel for receiving data sent to this socket. 178 | /// 179 | /// WARNING: If multiple streams are retrieved, it is undefined which returned stream will 180 | /// received a piece of data. 181 | pub fn recv(&self) -> impl Stream, SocketAddr)> + '_ { 182 | stream::unfold(self.clone(), |this| async move { 183 | let mut buf = vec![0; 1024]; 184 | let (size, from) = this.socket.recv_from(&mut buf).await.unwrap(); 185 | let ret = buf.split_at(size).0.to_vec(); 186 | Some(((ret, from), this)) 187 | }) 188 | } 189 | 190 | /// The local address of the socket 191 | pub fn local_addr(&self) -> Result { 192 | self.socket.local_addr() 193 | } 194 | } 195 | 196 | /// A TCP socket 197 | #[derive(Debug, Clone)] 198 | pub struct TcpChannel { 199 | read_channel: Arc>>>, 200 | local_addr: SocketAddr, 201 | remote_addr: SocketAddr, 202 | sender_channel: futures::channel::mpsc::Sender, 203 | } 204 | 205 | #[derive(Debug)] 206 | enum TcpData { 207 | Data(Vec), 208 | Shutdown, 209 | } 210 | 211 | impl TcpChannel { 212 | /// Create a TCP socket from an existing TcpStream 213 | pub fn new(runtime: Arc, stream: Box) -> Self { 214 | let local_addr = stream.local_addr().unwrap(); 215 | let remote_addr = stream.remote_addr().unwrap(); 216 | let (send_tx, send_rx) = futures::channel::mpsc::channel::(1); 217 | let (mut read, mut write) = stream.split(); 218 | runtime.spawn(Box::pin({ 219 | async move { 220 | let mut send_rx = core::pin::pin!(send_rx); 221 | //let mut write = core::pin::pin!(write); 222 | while let Some(data) = send_rx.next().await { 223 | match data { 224 | TcpData::Data(data) => { 225 | if let Err(e) = write.write_all(&data).await { 226 | warn!("tcp write produced error {e:?}"); 227 | break; 228 | } 229 | } 230 | TcpData::Shutdown => { 231 | if let Err(e) = write.shutdown(std::net::Shutdown::Both).await { 232 | warn!("tcp shutdown produced error {e:?}"); 233 | } 234 | break; 235 | } 236 | } 237 | } 238 | } 239 | })); 240 | let (mut recv_tx, recv_rx) = futures::channel::mpsc::channel::(1); 241 | runtime.spawn(Box::pin(async move { 242 | while let Ok(data_addr) = Self::inner_recv(&mut read).await { 243 | if recv_tx.send(data_addr).await.is_err() { 244 | break; 245 | } 246 | } 247 | })); 248 | Self { 249 | local_addr, 250 | remote_addr, 251 | read_channel: Arc::new(Mutex::new(Some(recv_rx))), 252 | sender_channel: send_tx, 253 | } 254 | } 255 | 256 | #[tracing::instrument( 257 | name = "tcp_single_recv", 258 | skip(stream), 259 | fields( 260 | remote.addr = ?stream.remote_addr() 261 | ) 262 | )] 263 | async fn inner_recv( 264 | stream: &mut Box, 265 | ) -> Result { 266 | let from = stream.remote_addr()?; 267 | 268 | let mut data = vec![0; MAX_STUN_MESSAGE_SIZE]; 269 | 270 | match stream.read(&mut data).await { 271 | Ok(size) => { 272 | trace!("recved {} bytes", size); 273 | if size == 0 { 274 | info!("connection closed"); 275 | return Err(std::io::Error::new( 276 | std::io::ErrorKind::WriteZero, 277 | "TCP connection closed", 278 | )); 279 | } 280 | trace!("return {} bytes", size); 281 | return Ok(DataAddress::new(data[..size].to_vec(), from)); 282 | } 283 | Err(e) => return Err(e), 284 | } 285 | } 286 | 287 | /// Close the socket 288 | pub async fn close(&mut self) -> Result<(), std::io::Error> { 289 | self.sender_channel 290 | .send(TcpData::Shutdown) 291 | .await 292 | .map_err(|e| { 293 | if e.is_disconnected() { 294 | std::io::Error::new(std::io::ErrorKind::BrokenPipe, "Disconnected") 295 | } else { 296 | unreachable!(); 297 | } 298 | }) 299 | } 300 | 301 | /// Send data to the specified address 302 | pub async fn send_to(&mut self, data: &[u8], to: SocketAddr) -> Result<(), std::io::Error> { 303 | if to != self.remote_addr()? { 304 | return Err(std::io::Error::new( 305 | std::io::ErrorKind::InvalidInput, 306 | "Address to send to is different from connected address", 307 | )); 308 | } 309 | 310 | if data.len() > u16::MAX as usize { 311 | return Err(std::io::Error::new( 312 | std::io::ErrorKind::InvalidInput, 313 | "data length too large for transport", 314 | )); 315 | } 316 | 317 | self.sender_channel 318 | .send(TcpData::Data(data.to_vec())) 319 | .await 320 | .map_err(|_| std::io::Error::from(std::io::ErrorKind::ConnectionAborted)) 321 | } 322 | 323 | /// Return a stream of received data blocks 324 | pub fn recv(&mut self) -> impl Stream, SocketAddr)> + '_ { 325 | let span = debug_span!("tcp_recv"); 326 | let chan = self 327 | .read_channel 328 | .lock() 329 | .unwrap() 330 | .take() 331 | .expect("Receiver already taken!"); 332 | chan.map(|v| (v.data, v.address)) 333 | .instrument(span.or_current()) 334 | // TODO: replace self.running_buffer when done? drop handler? 335 | /*stream::unfold(&mut self.read_stream, |mut stream| async move { 336 | TcpChannel::inner_recv(&mut stream) 337 | .await 338 | .ok() 339 | .map(|v| ((v.data, v.address), stream)) 340 | }) 341 | .instrument(span.or_current())*/ 342 | } 343 | 344 | /// The local address of the socket 345 | pub fn local_addr(&self) -> Result { 346 | Ok(self.local_addr) 347 | } 348 | 349 | /// The remoted address of the connected socket 350 | pub fn remote_addr(&self) -> Result { 351 | Ok(self.remote_addr) 352 | } 353 | } 354 | --------------------------------------------------------------------------------