Compare commits

...

16 Commits

Author SHA1 Message Date
benedettadavico db64706a1f testing quorum 2025-11-13 17:23:10 +01:00
Drazen Urch aac983d922 Remove debug feature from http-macro spec in gateway probe (#6195) 2025-11-13 14:18:29 +01:00
mfahampshire 577675bab3 Remove old conceptsoverview page + move index to proper place in sidebar (#6196) 2025-11-13 11:38:54 +00:00
mfahampshire ec015618cd update gw probe to point @ monorepo (#6194)
* update gw probe to point @ monorepo

* add funded nyx account info
2025-11-13 11:02:45 +00:00
mfahampshire fa40acbeca fixed broken link (#6193) 2025-11-12 15:12:38 +00:00
import this 386e1790dd [DOCs/operators]: Release notes for v2025.20 leerdammer (#6191)
* release notes

* bump up nym-node docs version

* add dev tools

* scrape stats and clean
2025-11-12 12:32:13 +00:00
mfahampshire d07f9c8fad Max/docs new structure (#6188)
* rework of sdk docs

* update integration docs + bit of overall restructure

* remove debug logger from tool
2025-11-12 11:03:28 +00:00
Tommy Verrall 0dc071daeb Merge pull request #6179 from nymtech/dns-debug
DNS relibility and troubleshooting
2025-11-12 11:01:20 +01:00
jmwample 10951d4cd3 clippy, fmt, minor fix 2025-11-11 10:40:25 -07:00
mfahampshire 872c25bfcc Use hardcoded devrel gw for examples to get around CSP (#6187)
* Use hardcoded devrel gw for examples to get around CSP

* remove comment
2025-11-11 16:22:41 +00:00
jmwample 5acce42c64 add some staic hosts and switch server strategy 2025-11-11 09:14:26 -07:00
mfahampshire 4848d081d0 Max/tweak ts sdk actions (#6185)
* add taskset to wasm release build commands

* bump taskset cores
2025-11-11 10:19:55 +00:00
mfahampshire b3452ede77 add wss to prod csp (#6183)
* add wss to csp
2025-11-10 20:48:02 +00:00
jmwample 2f752a6c42 fix things related to interface changes 2025-11-06 18:37:50 -07:00
Jack Wampler 806f807f02 Implement Static DNS fallback (#6178) 2025-11-06 16:46:39 -07:00
Jack Wampler 1400db6156 DNS Reliability Fixes (#6175) 2025-11-06 12:37:27 -07:00
73 changed files with 988 additions and 456 deletions
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup yarn
run: npm install -g yarn
@@ -54,6 +54,6 @@ jobs:
- name: Lint
run: yarn lint
- name: Typecheck with tsc
run: yarn tsc
Generated
+2 -1
View File
@@ -5443,7 +5443,7 @@ dependencies = [
[[package]]
name = "nym-credential-proxy"
version = "0.3.0"
version = "0.3.0-test"
dependencies = [
"anyhow",
"axum",
@@ -6058,6 +6058,7 @@ dependencies = [
"thiserror 2.0.12",
"tokio",
"tracing",
"tracing-subscriber",
"url",
"wasmtimer",
]
+1 -1
View File
@@ -45,7 +45,7 @@ pub enum ClientCoreError {
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
#[error("no gateways on network")]
NoGatewaysOnNetwork,
@@ -30,7 +30,6 @@ pub(crate) async fn connect_async(
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
@@ -39,7 +39,6 @@ pub(crate) async fn connect_async(
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
@@ -54,7 +54,7 @@ pub enum GatewayClientError {
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
#[error("No shared key was provided or obtained")]
NoSharedKeyAvailable,
+100 -1
View File
@@ -27,6 +27,8 @@ pub struct QuorumStateChecker {
cancellation_token: CancellationToken,
check_interval: Duration,
quorum_state: QuorumState,
max_retries: u32,
retry_initial_delay: Duration,
}
impl QuorumStateChecker {
@@ -42,6 +44,8 @@ impl QuorumStateChecker {
quorum_state: QuorumState {
available: Arc::new(Default::default()),
},
max_retries: 3,
retry_initial_delay: Duration::from_secs(2),
};
// first check MUST succeed, otherwise we shouldn't start
@@ -56,7 +60,102 @@ impl QuorumStateChecker {
self.quorum_state.clone()
}
fn is_retryable_error(&self, err: &CredentialProxyError) -> bool {
let err_str = err.to_string().to_lowercase();
// Check for DNS-related errors
if err_str.contains("dns")
|| err_str.contains("lookup")
|| err_str.contains("name resolution")
|| err_str.contains("temporary failure")
|| err_str.contains("failed to lookup address")
{
return true;
}
// Check if it's a Tendermint RPC error (which could be DNS/timeout related)
if let CredentialProxyError::NyxdFailure { source: nyxd_err } = err {
let nyxd_err_str = nyxd_err.to_string().to_lowercase();
if nyxd_err_str.contains("tendermint rpc request failed") {
return true;
}
if nyxd_err.is_tendermint_response_timeout() {
return true;
}
}
false
}
async fn check_quorum_state(&self) -> Result<bool, CredentialProxyError> {
self.check_quorum_state_with_retry().await
}
async fn check_quorum_state_with_retry(&self) -> Result<bool, CredentialProxyError> {
let mut last_error_msg = None;
let delay = self.retry_initial_delay;
for attempt in 0..=self.max_retries {
match self.check_quorum_state_once().await {
Ok(result) => {
if attempt > 0 {
info!("quorum check succeeded after {} retry attempt(s)", attempt);
}
return Ok(result);
}
Err(err) => {
let err_msg = err.to_string();
// Check if this error is retryable
if !self.is_retryable_error(&err) {
return Err(err);
}
last_error_msg = Some(err_msg.clone());
if attempt >= self.max_retries {
break;
}
// Log the retry attempt
warn!(
"quorum check failed (attempt {}/{}): {}. Retrying in {:?}...",
attempt + 1,
self.max_retries + 1,
err_msg,
delay
);
// Wait before retrying with exponential backoff
tokio::time::sleep(delay).await;
}
}
}
// try one final time to get the actual error
match self.check_quorum_state_once().await {
Ok(result) => {
warn!(
"quorum check succeeded on final attempt after {} retries",
self.max_retries
);
Ok(result)
}
Err(err) => {
if let Some(error_msg) = last_error_msg {
error!(
"quorum check failed after {} retry attempts. Last error: {}",
self.max_retries + 1,
error_msg
);
}
Err(err)
}
}
}
async fn check_quorum_state_once(&self) -> Result<bool, CredentialProxyError> {
let client_guard = self.client.query_chain().await;
// split the operation as we only need to hold the reference to chain client for the first part
@@ -93,7 +192,7 @@ impl QuorumStateChecker {
break
}
_ = tokio::time::sleep(self.check_interval) => {
match self.check_quorum_state().await {
match self.check_quorum_state_with_retry().await {
Ok(available) => self.quorum_state.available.store(available, Ordering::SeqCst),
Err(err) => error!("failed to check current quorum state: {err}"),
}
+2 -3
View File
@@ -32,7 +32,7 @@ thiserror = { workspace = true }
tracing = { workspace = true }
itertools = { workspace = true }
inventory = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros", "time"] }
# used for decoding text responses (they were already implicitly included)
bytes = { workspace = true }
encoding_rs = { workspace = true }
@@ -52,5 +52,4 @@ workspace = true
features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
tracing-subscriber.workspace = true
+308 -88
View File
@@ -30,19 +30,26 @@
use crate::ClientBuilder;
use std::{
net::SocketAddr,
collections::HashMap,
net::{IpAddr, SocketAddr},
str::FromStr,
sync::{Arc, LazyLock},
time::Duration,
};
use hickory_resolver::{
ResolveError, TokioResolver,
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ServerOrderingStrategy},
lookup_ip::{LookupIp, LookupIpIntoIter},
TokioResolver,
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig},
lookup_ip::LookupIpIntoIter,
name_server::TokioConnectionProvider,
};
use once_cell::sync::OnceCell;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::warn;
use tracing::*;
mod constants;
mod static_resolver;
pub use static_resolver::*;
impl ClientBuilder {
/// Override the DNS resolver implementation used by the underlying http client.
@@ -59,10 +66,6 @@ impl ClientBuilder {
}
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
// this is fine, as the OS can deallocate the terminated program faster than we can free memory
// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
@@ -72,11 +75,17 @@ static SHARED_RESOLVER: LazyLock<HickoryDnsResolver> = LazyLock::new(|| {
});
#[derive(Debug, thiserror::Error)]
#[error("hickory-dns resolver error: {hickory_error}")]
#[allow(missing_docs)]
/// Error occurring while resolving a hostname into an IP address.
pub struct HickoryDnsError {
#[from]
hickory_error: ResolveError,
pub enum ResolveError {
#[error("invalid name: {0}")]
InvalidNameError(String),
#[error("hickory-dns resolver error: {0}")]
ResolveError(#[from] hickory_resolver::ResolveError),
#[error("high level lookup timed out")]
Timeout,
#[error("hostname not found in static lookup table")]
StaticLookupMiss,
}
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
@@ -87,69 +96,116 @@ pub struct HickoryDnsError {
/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver
/// is required use `thread_resolver()` to build a resolver with an independently instantiated
/// internal `AsyncResolver`.
#[derive(Debug, Default, Clone)]
#[derive(Debug, Clone)]
pub struct HickoryDnsResolver {
// Since we might not have been called in the context of a
// Tokio Runtime in initialization, so we must delay the actual
// construction of the resolver.
state: Arc<OnceCell<TokioResolver>>,
fallback: Option<Arc<OnceCell<TokioResolver>>>,
static_base: Option<Arc<OnceCell<StaticResolver>>>,
dont_use_shared: bool,
/// Overall timeout for dns lookup associated with any individual host resolution. For example,
/// use of retries, server_ordering_strategy, etc. ends absolutely if this timeout is reached.
overall_dns_timeout: Duration,
}
impl Default for HickoryDnsResolver {
fn default() -> Self {
Self {
state: Default::default(),
fallback: Default::default(),
static_base: Default::default(),
dont_use_shared: Default::default(),
overall_dns_timeout: Duration::from_secs(10),
}
}
}
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.state.clone();
let maybe_fallback = self.fallback.clone();
let maybe_static = self.static_base.clone();
let independent = self.dont_use_shared;
let overall_dns_timeout = self.overall_dns_timeout;
Box::pin(async move {
let resolver = resolver.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if independent {
new_resolver()
} else {
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
}
})?;
resolve(
name,
resolver,
maybe_fallback,
maybe_static,
independent,
overall_dns_timeout,
)
.await
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
})
}
}
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name.as_str()).await {
Ok(res) => res,
Err(e) => {
if let Some(ref fallback) = maybe_fallback {
// on failure use the fall back system configured DNS resolver
if !e.is_no_records_found() {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
let resolver = fallback.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if independent {
new_resolver_system()
} else {
Ok(SHARED_RESOLVER
.fallback
.as_ref()
.ok_or(e)? // if the shared resolver has no fallback return the original error
.get_or_try_init(new_resolver_system)?
.clone())
}
})?;
resolver.lookup_ip(name.as_str()).await?
} else {
return Err(e.into());
}
}
};
async fn resolve(
name: Name,
resolver: Arc<OnceCell<TokioResolver>>,
maybe_fallback: Option<Arc<OnceCell<TokioResolver>>>,
maybe_static: Option<Arc<OnceCell<StaticResolver>>>,
independent: bool,
overall_dns_timeout: Duration,
) -> Result<Addrs, ResolveError> {
let resolver = resolver.get_or_try_init(|| HickoryDnsResolver::new_resolver(independent))?;
// Attempt a lookup using the primary resolver
let resolve_fut = tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
let primary_err = match resolve_fut.await {
Err(_) => ResolveError::Timeout,
Ok(Ok(lookup)) => {
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
Ok(addrs)
})
return Ok(addrs);
}
Ok(Err(e)) => {
// on failure use the fall back system configured DNS resolver
if !e.is_no_records_found() {
warn!("primary DNS failed w/ error: {e}");
}
e.into()
}
};
// If the primary resolver encountered an error, attempt a lookup using the fallback
// resolver if one is configured.
if let Some(ref fallback) = maybe_fallback {
let resolver =
fallback.get_or_try_init(|| HickoryDnsResolver::new_resolver_system(independent))?;
let resolve_fut =
tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
if let Ok(Ok(lookup)) = resolve_fut.await {
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
return Ok(addrs);
}
}
// If no record has been found and a static map of fallback addresses is configured
// check the table for our entry
if let Some(ref static_resolver) = maybe_static {
debug!("checking static");
let resolver =
static_resolver.get_or_init(|| HickoryDnsResolver::new_static_fallback(independent));
if let Ok(addrs) = resolver.resolve(name).await {
return Ok(addrs);
}
}
Err(primary_err)
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
impl Iterator for SocketAddrs {
@@ -162,28 +218,22 @@ impl Iterator for SocketAddrs {
impl HickoryDnsResolver {
/// Attempt to resolve a domain name to a set of ['IpAddr']s
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
let resolver = self.state.get_or_try_init(|| self.new_resolver())?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name).await {
Ok(res) => res,
Err(e) => {
if let Some(ref fallback) = self.fallback {
// on failure use the fall back system configured DNS resolver
if !e.is_no_records_found() {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
let resolver = fallback.get_or_try_init(|| self.new_resolver_system())?;
resolver.lookup_ip(name).await?
} else {
return Err(e.into());
}
}
};
Ok(lookup)
pub async fn resolve_str(
&self,
name: &str,
) -> Result<impl Iterator<Item = IpAddr> + use<>, ResolveError> {
let n =
Name::from_str(name).map_err(|_| ResolveError::InvalidNameError(name.to_string()))?;
resolve(
n,
self.state.clone(),
self.fallback.clone(),
self.static_base.clone(),
self.dont_use_shared,
self.overall_dns_timeout,
)
.await
.map(|addrs| addrs.map(|socket_addr| socket_addr.ip()))
}
/// Create a (lazy-initialized) resolver that is not shared across threads.
@@ -194,16 +244,20 @@ impl HickoryDnsResolver {
}
}
fn new_resolver(&self) -> Result<TokioResolver, HickoryDnsError> {
if self.dont_use_shared {
fn new_resolver(dont_use_shared: bool) -> Result<TokioResolver, ResolveError> {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if dont_use_shared {
new_resolver()
} else {
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
}
}
fn new_resolver_system(&self) -> Result<TokioResolver, HickoryDnsError> {
if self.dont_use_shared || SHARED_RESOLVER.fallback.is_none() {
fn new_resolver_system(dont_use_shared: bool) -> Result<TokioResolver, ResolveError> {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if dont_use_shared || SHARED_RESOLVER.fallback.is_none() {
new_resolver_system()
} else {
Ok(SHARED_RESOLVER
@@ -215,8 +269,18 @@ impl HickoryDnsResolver {
}
}
fn new_static_fallback(dont_use_shared: bool) -> StaticResolver {
if !dont_use_shared && let Some(ref shared_resolver) = SHARED_RESOLVER.static_base {
shared_resolver
.get_or_init(new_default_static_fallback)
.clone()
} else {
new_default_static_fallback()
}
}
/// Enable fallback to the system default resolver if the primary (DoX) resolver fails
pub fn enable_system_fallback(&mut self) -> Result<(), HickoryDnsError> {
pub fn enable_system_fallback(&mut self) -> Result<(), ResolveError> {
self.fallback = Some(Default::default());
let _ = self
.fallback
@@ -231,22 +295,51 @@ impl HickoryDnsResolver {
pub fn disable_system_fallback(&mut self) {
self.fallback = None;
}
/// Get the current map of hostname to address in use by the fallback static lookup if one
/// exists.
pub fn get_static_fallbacks(&self) -> Option<HashMap<String, Vec<IpAddr>>> {
Some(self.static_base.as_ref()?.get()?.get_addrs())
}
/// Set (or overwrite) the map of addresses used in the fallback static hostname lookup
pub fn set_static_fallbacks(&mut self, addrs: HashMap<String, Vec<IpAddr>>) {
let cell = OnceCell::new();
cell.set(StaticResolver::new(addrs))
.expect("infallible assign");
self.static_base = Some(Arc::new(cell));
}
}
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
fn new_resolver() -> Result<TokioResolver, HickoryDnsError> {
///
/// Timeout Defaults to 5 seconds
/// Number of retries after lookup failure before giving up Defaults to 2
///
/// Caches successfully resolved addresses for 30 minutes to prevent continual use of remote lookup.
/// This resolver is intended to be used for OUR API endpoints that do not rapidly rotate IPs.
fn new_resolver() -> Result<TokioResolver, ResolveError> {
info!("building new configured resolver");
let mut name_servers = NameServerConfigGroup::quad9_tls();
name_servers.merge(NameServerConfigGroup::quad9_https());
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
name_servers.merge(NameServerConfigGroup::cloudflare_https());
configure_and_build_resolver(name_servers)
}
fn configure_and_build_resolver(
name_servers: NameServerConfigGroup,
) -> Result<TokioResolver, ResolveError> {
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
let mut resolver_builder =
TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
resolver_builder.options_mut().server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
// Cache successful responses for queries received by this resolver for 30 min minimum.
resolver_builder.options_mut().positive_min_ttl = Some(Duration::from_secs(1800));
Ok(resolver_builder.build())
}
@@ -254,20 +347,27 @@ fn new_resolver() -> Result<TokioResolver, HickoryDnsError> {
/// Create a new resolver with the default configuration, which reads from the system DNS config
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
/// addresses to work with "happy eyeballs" algorithm.
fn new_resolver_system() -> Result<TokioResolver, HickoryDnsError> {
fn new_resolver_system() -> Result<TokioResolver, ResolveError> {
let mut resolver_builder = TokioResolver::builder_tokio()?;
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
Ok(resolver_builder.build())
}
fn new_default_static_fallback() -> StaticResolver {
StaticResolver::new(constants::default_static_addrs())
}
#[cfg(test)]
mod test {
use super::*;
use itertools::Itertools;
use std::collections::HashMap;
#[tokio::test]
async fn reqwest_hickory_doh() {
let resolver = HickoryDnsResolver::default();
async fn reqwest_with_custom_dns() {
let var_name = HickoryDnsResolver::default();
let resolver = var_name;
let client = reqwest::ClientBuilder::new()
.dns_resolver(resolver.into())
.build()
@@ -286,7 +386,7 @@ mod test {
}
#[tokio::test]
async fn dns_lookup() -> Result<(), HickoryDnsError> {
async fn dns_lookup() -> Result<(), ResolveError> {
let resolver = HickoryDnsResolver::default();
let domain = "ifconfig.me";
@@ -296,4 +396,124 @@ mod test {
Ok(())
}
#[tokio::test]
async fn static_resolver_as_fallback() -> Result<(), ResolveError> {
let example_domain = "non-existent.nymvpn.com";
let mut resolver = HickoryDnsResolver {
..Default::default()
};
let result = resolver.resolve_str(example_domain).await;
assert!(result.is_err()); // should be NXDomain
resolver.static_base = Some(Default::default());
let mut addr_map = HashMap::new();
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
addr_map.insert(example_domain.to_string(), vec![example_ip4, example_ip6]);
resolver.set_static_fallbacks(addr_map);
let mut addrs = resolver.resolve_str(example_domain).await?;
assert!(addrs.contains(&example_ip4));
assert!(addrs.contains(&example_ip6));
Ok(())
}
}
#[cfg(test)]
mod failure_test {
use super::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
/// IP addresses guaranteed to fail attempts to resolve
///
/// Addresses drawn from blocks set off by RFC5737 (ipv4) and RFC3849 (ipv6)
const GUARANTEED_BROKEN_IPS_1: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)),
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1111)),
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1001)),
];
// Create a resolver that behaves the same as the custom configured router, except for the fact
// that it is guaranteed to fail.
fn build_broken_resolver() -> Result<TokioResolver, ResolveError> {
info!("building new faulty resolver");
let mut broken_ns_group = NameServerConfigGroup::from_ips_tls(
GUARANTEED_BROKEN_IPS_1,
853,
"cloudflare-dns.com".to_string(),
true,
);
let broken_ns_https = NameServerConfigGroup::from_ips_https(
GUARANTEED_BROKEN_IPS_1,
443,
"cloudflare-dns.com".to_string(),
true,
);
broken_ns_group.merge(broken_ns_https);
configure_and_build_resolver(broken_ns_group)
}
#[tokio::test]
async fn dns_lookup_failures() -> Result<(), ResolveError> {
let time_start = std::time::Instant::now();
let r = OnceCell::new();
r.set(build_broken_resolver().expect("failed to build resolver"))
.expect("broken resolver init error");
// create a new resolver that won't mess with the shared resolver used by other tests
let resolver = HickoryDnsResolver {
dont_use_shared: true,
state: Arc::new(r),
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
build_broken_resolver()?;
let domain = "ifconfig.me";
let result = resolver.resolve_str(domain).await;
assert!(result.is_err_and(|e| matches!(e, ResolveError::Timeout)));
let duration = time_start.elapsed();
assert!(duration < resolver.overall_dns_timeout + Duration::from_secs(1));
Ok(())
}
#[tokio::test]
async fn fallback_to_static() -> Result<(), ResolveError> {
let r = OnceCell::new();
r.set(build_broken_resolver().expect("failed to build resolver"))
.expect("broken resolver init error");
// create a new resolver that won't mess with the shared resolver used by other tests
let resolver = HickoryDnsResolver {
dont_use_shared: true,
state: Arc::new(r),
static_base: Some(Default::default()),
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
build_broken_resolver()?;
// successful lookup using fallback to static resolver
let domain = "nymvpn.com";
let _ = resolver
.resolve_str(domain)
.await
.expect("failed to resolve address in static lookup");
// unsuccessful lookup - primary times out, and not in
let domain = "non-existent.nymtech.net";
let result = resolver.resolve_str(domain).await;
assert!(result.is_err_and(|e| matches!(e, ResolveError::Timeout)));
Ok(())
}
}
@@ -0,0 +1,95 @@
#![allow(missing_docs)]
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub const NYM_API_DOMAIN: &str = "validator.nymtech.net";
pub const NYM_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(212, 71, 233, 232))];
pub const NYM_VPN_API_DOMAIN: &str = "nymvpn.com";
pub const NYM_VPN_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 21))];
pub const NYM_FRONTDOOR_VERCEL_DOMAIN: &str = "nym-frontdoor.vercel.app";
pub const NYM_FRONTDOOR_VERCEL_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(64, 29, 17, 195)),
IpAddr::V4(Ipv4Addr::new(216, 198, 79, 195)),
];
pub const NYM_FRONTDOOR_FASTLY_DOMAIN: &str = "nym-frontdoor.global.ssl.fastly.net";
pub const NYM_FRONTDOOR_FASTLY_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
];
pub const NYMVPN_FRONTDOOR_FASTLY_DOMAIN: &str = "nymvpn-frontdoor.global.ssl.fastly.net";
pub const NYMVPN_FRONTDOOR_FASTLY_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
];
pub const YELP_FASTLY_DOMAIN: &str = "yelp.global.ssl.fastly.net";
pub const YELP_FASTLY_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
];
pub const VERCEL_APP_DOMAIN: &str = "vercel.app";
pub const VERCEL_APP_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(64, 29, 17, 195)),
IpAddr::V4(Ipv4Addr::new(216, 198, 79, 195)),
];
pub const VERCEL_COM_DOMAIN: &str = "vercel.com";
pub const VERCEL_COM_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(198, 169, 2, 129)),
IpAddr::V4(Ipv4Addr::new(198, 169, 1, 193)),
];
pub const NYM_COM_DOMAIN: &str = "nym.com";
pub const NYM_COM_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 22))];
pub const NYM_STATS_API_DOMAIN: &str = "nym-statistics-api.nymtech.cc";
pub const NYM_STATS_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(91, 92, 153, 96))];
pub const NYM_RPC_DOMAIN: &str = "rpc.nymtech.net";
pub const NYM_RPC_IPS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(194, 182, 169, 49)),
IpAddr::V4(Ipv4Addr::new(91, 92, 200, 116)),
IpAddr::V6(Ipv6Addr::new(
0x2a04, 0xc43, 0xe00, 0x6f28, 0x400, 0xd8ff, 0xfe00, 0x1483,
)),
IpAddr::V6(Ipv6Addr::new(
0x2a04, 0xc46, 0xe00, 0x6f28, 0x4b3, 0x68ff, 0xfe00, 0x460,
)),
];
pub fn default_static_addrs() -> HashMap<String, Vec<IpAddr>> {
let mut m = HashMap::new();
m.insert(NYM_API_DOMAIN.to_string(), NYM_API_IPS.to_vec());
m.insert(NYM_VPN_API_DOMAIN.to_string(), NYM_VPN_API_IPS.to_vec());
m.insert(
NYM_FRONTDOOR_VERCEL_DOMAIN.to_string(),
NYM_FRONTDOOR_VERCEL_IPS.to_vec(),
);
m.insert(
NYM_FRONTDOOR_FASTLY_DOMAIN.to_string(),
NYM_FRONTDOOR_FASTLY_IPS.to_vec(),
);
m.insert(
NYMVPN_FRONTDOOR_FASTLY_DOMAIN.to_string(),
NYMVPN_FRONTDOOR_FASTLY_IPS.to_vec(),
);
m.insert(YELP_FASTLY_DOMAIN.to_string(), YELP_FASTLY_IPS.to_vec());
m.insert(VERCEL_APP_DOMAIN.to_string(), VERCEL_APP_IPS.to_vec());
m.insert(VERCEL_COM_DOMAIN.to_string(), VERCEL_COM_IPS.to_vec());
m.insert(NYM_COM_DOMAIN.to_string(), NYM_COM_IPS.to_vec());
m.insert(NYM_STATS_API_DOMAIN.to_string(), NYM_STATS_API_IPS.to_vec());
m.insert(NYM_RPC_DOMAIN.to_string(), NYM_RPC_IPS.to_vec());
m
}
@@ -0,0 +1,89 @@
use crate::dns::ResolveError;
use std::{
collections::HashMap,
net::{IpAddr, SocketAddr},
sync::{Arc, Mutex},
};
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::*;
#[derive(Debug, Default, Clone)]
pub struct StaticResolver {
static_addr_map: Arc<Mutex<HashMap<String, Vec<IpAddr>>>>,
}
impl StaticResolver {
pub fn new(static_entries: HashMap<String, Vec<IpAddr>>) -> StaticResolver {
debug!("building static resolver");
Self {
static_addr_map: Arc::new(Mutex::new(static_entries)),
}
}
pub fn get_addrs(&self) -> HashMap<String, Vec<IpAddr>> {
self.static_addr_map.lock().unwrap().clone()
}
}
impl Resolve for StaticResolver {
fn resolve(&self, name: Name) -> Resolving {
debug!("looking up {name:?} in static resolver");
let addr_map = self.static_addr_map.clone();
Box::pin(async move {
let addr_map = addr_map.lock().unwrap();
let lookup = match addr_map.get(name.as_str()) {
None => return Err(ResolveError::StaticLookupMiss.into()),
Some(addrs) => addrs,
};
let addrs: Addrs = Box::new(
lookup
.clone()
.into_iter()
.map(|ip_addr| SocketAddr::new(ip_addr, 0)),
);
Ok(addrs)
})
}
}
#[cfg(test)]
mod test {
use itertools::Itertools;
use super::*;
use std::error::Error as StdError;
use std::str::FromStr;
#[tokio::test]
async fn lookup_using_static_resolver() -> Result<(), Box<dyn StdError + Send + Sync>> {
let example_domain = String::from("static.nymvpn.com");
// lookup for domain for which there is no entry
let resolver = StaticResolver::new(HashMap::new());
let url = reqwest::dns::Name::from_str(&example_domain).unwrap();
let result = resolver.resolve(url).await;
assert!(result.is_err());
match result {
Ok(_) => panic!("lookup with empty map should fail"),
Err(e) => assert_eq!(e.to_string(), ResolveError::StaticLookupMiss.to_string()),
}
// Successful lookup
let mut addr_map = HashMap::new();
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
addr_map.insert(example_domain.clone(), vec![example_ip4, example_ip6]);
let url = reqwest::dns::Name::from_str(&example_domain).unwrap();
let resolver = StaticResolver::new(addr_map);
let mut addrs = resolver.resolve(url).await?;
assert!(addrs.contains(&SocketAddr::new(example_ip4, 0)));
assert!(addrs.contains(&SocketAddr::new(example_ip6, 0)));
Ok(())
}
}
+1 -1
View File
@@ -179,7 +179,7 @@ mod dns;
mod path;
#[cfg(not(target_arch = "wasm32"))]
pub use dns::{HickoryDnsError, HickoryDnsResolver};
pub use dns::{HickoryDnsResolver, ResolveError};
// helper for generating user agent based on binary information
#[cfg(not(target_arch = "wasm32"))]
@@ -0,0 +1,40 @@
import Box from '@mui/material/Box';
import { Steps } from 'nextra/components'
import { Tabs } from 'nextra/components'
import { GitHubRepoSearch } from '../../code-snippets/mixfetchurl';
# Integration Options
Developers might want to either integrate a Mixnet client or just to interact with the blockchain. See the relevant section below.
## Integrating Mixnet Functionality
There are several options available to developers wanting to embed a Nym client in their application code.
<Tabs items={['Rust/Go/C++', 'Typescript/Javascript','Other']}>
<Tabs.Tab >
<>
Rust developers can rely on our Rust SDK to import Nym client functionality into their code. This can either be in the form of a standard message-based client, the `socks5` client, or the `TcpProxy` modules.
We aim to expose at least the majority of this functionality via FFI to Go and C/C++. This is detailed alongside the Rust SDK components in the [Rust SDK docs](./rust).
</>
</Tabs.Tab>
<Tabs.Tab>
<>
Typescript and Javascript developers have several options avaliable to them:
- [`mixfetch`](./typescript/examples/mix-fetch) is an almost-dropin replacement for the `fetch` library. The best way to integrate Nym's `mixFetch` into your application will be where external network calls and RPC happens, for example, something in the lines of `sendRawTransaction` if you have an ETH-compatible wallet or `JsonRpcClient` if you use CosmJS. Although you can simply search for any JS `fetch` calls in your code (using our tool below) that are easily replaceable with `mixFetch`, keep in mind that `fetch` is not the only way to make `JSONRPC` or `XHR` calls. We advise to approach the integration process in a semantic way, searching for a module that is the common denominator for external communication in the codebase. Usually these are API controllers, middlewares or repositories.
<GitHubRepoSearch />
- Otherwise, a well-modularized JS/TS codebase should permit the integration of one of our other SDK components, which will allow more flexibility and control (or if your codebase is not using something that can be covered by `fetch` for networking). Read more about our different SDK components in the [TS SDK overview page](./typescript/overview).
</ >
</Tabs.Tab>
<Tabs.Tab> If your app is not written in any of the supported languages, you might still be able to send traffic through a standalone [socks5 client](./clients/socks5) but will have to think about packaging and bundling the client binary with e.g. a `systemd` file for autostart to run the client as a daemon. If you want to discuss FFI options reach out to us via our public dev channel. </Tabs.Tab>
</Tabs>
## Interacting with Nyx
If instead of relying on the Mixnet you wish to interact with the Nyx chain, either as a payment processor or to get on-chain events, see [interacting with the chain](./chain).
> Note that depending on your setup, you might already be able to combine interacting with the chain with using the Mixnet: check the options above for more.
@@ -1,5 +1,7 @@
# Required Application Architecture
**TODO once you;ve reworked you can probably just remove this**
Due to the fact that there are a lot of components that make up the Nym network (the Mixnet, Blockchain, etc) there is often confusion / misunderstandings about what is required to run application traffic through the Mixnet and take advantage of its various privacy properties.
## What do I need?
+3 -4
View File
@@ -9,13 +9,12 @@ import Stack from "@mui/material/Stack";
import Paper from "@mui/material/Paper";
import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat";
const defaultUrl = "https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
const defaultUrl =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
const args = { mode: "unsafe-ignore-cors" };
const mixFetchOptions: SetupMixFetchOps = {
preferredGateway: "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW", // with WSS
// preferredNetworkRequester:
// "CTDxrcXgrZHWyCWnuCgjpJPghQUcEVz1HkhUr5mGdFnT.3UAww1YWNyVNYNWFQL1LaHYouQtDiXBGK5GiDZgpXkTK@2RFtU5BwxvJJXagAWAEuaPgb5ZVPRoy2542TT93Edw6v",
preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1",
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
@@ -1 +1 @@
Monday, November 10th 2025, 13:30:27 UTC
Thursday, November 13th 2025, 11:23:33 UTC
@@ -11,7 +11,7 @@ options:
--no_routing_history Display node stats without routing history
--no_verloc_metrics Display node stats without verloc metrics
-m, --markdown Display results in markdown format
-o [OUTPUT], --output [OUTPUT]
-o, --output [OUTPUT]
Save results to file (in current dir or supply with
path without filename)
```
@@ -13,6 +13,7 @@ import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
const nymApiUrl = "https://validator.nymtech.net/api";
const preferredGateway = "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1";
export const Traffic = () => {
const [nym, setNym] = useState<NymMixnetClient>();
@@ -31,6 +32,7 @@ export const Traffic = () => {
clientId: crypto.randomUUID(),
nymApiUrl,
forceTls: true, // force WSS
preferredGateway,
});
// check when is connected and set the self address
+8 -8
View File
@@ -1108,22 +1108,22 @@ const config = {
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
connect-src 'self' https://github.com *.vercel.app *.nymtech.net *.nymvpn.com *.nymte.ch *.nyx.network *.nym.com https://nym.com nymvpn.com https://nymvpn.com *.nymtech.cc;
connect-src 'self' wss://nym-node-cli.devrel.nymte.ch:9001 https://github.com *.vercel.app *.nymtech.net *.nymvpn.com *.nymte.ch *.nyx.network *.nym.com https://nym.com nymvpn.com https://nymvpn.com *.nymtech.cc;
frame-src 'self' https://vercel.live *.vercel.app *.nym.com https://nym.com;
worker-src 'self' blob: https://vercel.live *.vercel.app *.nym.com https://nym.com;
`;
return [
{
source: '/(.*)',
source: "/(.*)",
headers: [
{
key: 'Content-Security-Policy',
key: "Content-Security-Policy",
value: csp.replace(/\s{2,}/g, " ").trim(),
}
]
}
]
}
},
],
},
];
},
};
module.exports = config;
+2 -2
View File
@@ -11,8 +11,8 @@
"type": "menu",
"items": {
"developers": {
"title": "Developer Overview",
"href": "/developers/"
"title": "Integrations",
"href": "/developers/integrations"
},
"rust": {
"title": "Rust SDK",
+18 -8
View File
@@ -1,18 +1,28 @@
{
"index": "Introduction",
"concepts": "Core Concepts",
"binaries": "Binaries",
"integrations": "Integration Options",
"clients": "Clients",
"tools": "Tools",
"nymvpncli": "Nym VPN CLI",
"chain": "Interacting with Nyx",
"--": {
"type": "separator"
"type": "separator",
"title": "Mixnet Integrations"
},
"integrations": "Overview - Start Here!",
"native": "Native Apps",
"browsers": "Browser Apps",
"concepts": "Nym-Specific Concepts for Integrations",
"-": {
"type": "separator",
"title": "SDKs"
},
"rust": "Rust SDK",
"typescript": "Typescript SDK",
"---": {
"type": "separator",
"title": "Misc"
},
"chain": "Interacting with Nyx Blockchain",
"tools": "Tools",
"nymvpncli": "Nym VPN CLI",
"clients": "Standlone Clients",
"----": {
"type": "separator"
},
"archive": "Archive",
@@ -0,0 +1,40 @@
import { Callout } from 'nextra/components';
# Browser-Based Apps
Browsers are a very restricted environment to work in, with limited options for external communications (websockets, Web Transport API, WebRTC), mixed content restrictions (HTTPS-only), and no access to the file system or any syscalls. These aside, the main issue when trying to capture traffic and send it via a different transport - such as the Mixnet - is the lack of access to browser TLS negotiation from JS or the CA certificate store.
This means that the functionality offered by our current browser-based solutions are quite restricted / specific. There are currently two options for interacting with the Mixnet from the browser: `mixFetch`, and the WASM SDK.
![](/images/developers/nym-browser-arch.png)
Both `mixFetch` and the WASM client are delivered to the client bundled into a web application.
## mixFetch
Drop-in replacement for browser's `fetch` API that makes HTTP(S) requests via Exit Gateways using the SOCKS Network Requester.
Uses an embedded CA certificate store to establish TLS session between `mixFetch` and the remote host, creating a client-host secure channel from the browser to the host over the Mixnet.
Internally it uses the WASM client.
- [docs](./typescript/start#mixfetch)
- [example](./typescript/playground/mixfetch)
<Callout type="warning">
### Current Limitations of `mixFetch`
It cannot currently handle concurrent requests, and comes with a pre-bundled CA store. `mixFetchv2` - which will act more like a general-purpose userspace IP stack - is currently in development.
</Callout>
## WASM Client
Makes Sphinx packets and cover traffic using WASM and sent over a Websocket to the Entry Gateway and receive responses.
This only works in messaging mode (i.e. messages sent either as text or binary data), and currently doesnt support making IP packets that are routed to the Internet by an Exit Gateway IPR, nor does it currently expose any stream-like API. If you want to send HTTP(S) requests, use `mixFetch`.
Note that the limitations of CSPs and Mixed Content restrictions (i.e HTTPS only) apply to the Websocket connection as normal in browsers or embedded WebViews.
Runs in a web worker to leave UI thread free for the user.
- [docs](./typescript/start#mixnet-client)
- [example](./typescript/playground/traffic)
@@ -1,5 +1,4 @@
{
"socks5": "Socks5 (standalone)",
"webassembly-client": "Webassembly Client",
"websocket": "Websocket (standalone)"
"socks5": "SOCKS Proxy",
"websocket": "Websocket"
}
@@ -1,6 +0,0 @@
{
"required-architecture": "Required Architecture",
"messages": "Message Based Paradigm",
"message-queue": "Message Queue",
"credentials": "Credentials"
}
@@ -1,5 +1,11 @@
import { Callout } from 'nextra/components'
# Message Queue
<Callout type="info">
Although good to understand how the Nym Client works under the hood, this information is only of practical use if you're using the `Mixnet` module of the RustSDK and interacting with the SDK Mixnet Client at a low level. This is all mostly abstracted away with the `MixSocket`, `MixStream`, and `IpMixStream` abstractions.
</Callout>
## Sphinx Packet Streams
Clients, once connected to the Mixnet, **are always sending traffic into the Mixnet**; as well as the packets that you as a developer are sending from your application logic, they send [cover traffic](../../network/concepts/cover-traffic) at a constant rate defined by a Poisson process. This is part of the network's mitigation of timing attacks.
@@ -1,31 +0,0 @@
# Message-based Paradigm
## Message Format
For the moment, Mixnet clients work assuming they will be piped atomic messages looking something like this:
```
MixnetMessage {
Message: Message_Bytes,
To: Nym_Address,
Attached_SURBS: Number_Of_Surbs
}
```
That the client will then encrypt as Sphinx packets and send through the Mixnet.
Likewise, they assume that once they have received and decrypted a Sphinx packet, they will kick back a reconstructed message to the rest of your app logic that look something like:
```
ReconstructedMessage {
Message: Message_Bytes,
From: SURB_Sender_Tag
}
```
This is obviously quite different to e.g. simply being able to read/write from a stream returned from a function call to create a TCP connection, but there are several approaches that developers can take to dealing with this right now.
## Message Abstractions
- Rust/Go (and soon C++) developers can use the `TcpProxy` [stream abstraction](../rust/tcpproxy).
- Developers who are using Typescript/Javascript can also avoid having to deal directly with messages via using [MixFetch](../typescript/examples/mix-fetch).
- As can developers who are bundling and running the standalone [socks5 client](../clients/socks5) using some form of init script.
- There is a seperate pair of binaries which other developers can use to run as a persistent secondary proxy process built using the `TcpProxy` abstraction. These simply expose a `localhost` socket port to pipe traffic to and from in the same way as you would a TCP connection and can be found [here](https://github.com/nymtech/standalone-tcp-proxies).
@@ -1,7 +1,2 @@
# Introduction
Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.
## Where to Start?
If you are completely new to Nym, it is suggested to start with the top sections covering the core concepts, integration options, and the clients and tools available to you.
If you feel like starting with more practical examples, check out the relevent SDK docs in the sidebar to the left.
@@ -1,40 +1,16 @@
import Box from '@mui/material/Box';
import { Steps } from 'nextra/components'
import { Tabs } from 'nextra/components'
import { GitHubRepoSearch } from '../../code-snippets/mixfetchurl';
import { Callout } from 'nextra/components';
# Integration Options
Developers might want to either integrate a Mixnet client or just to interact with the blockchain. See the relevant section below.
# Integrating With Nym
Any application that wants to integrate with Nym involves sending its application traffic through the Mixnet using one of the available Nym Clients. There is no single solution for this, as different environments offer different access and transport options (e.g. if operating in a web browser, you do not have access to syscalls or sockets, have to deal with content security policies, etc).
## Integrating Mixnet Functionality
There are several options available to developers wanting to embed a Nym client in their application code.
As such, we have several solutions available for developers to choose from depending on the **environment** their application is expected to run in: native apps which are running on a desktop, or webapps running in a browser.
<Tabs items={['Rust/Go/C++', 'Typescript/Javascript','Other']}>
<Tabs.Tab >
<>
<Callout type="info" emoji="️">
The list of current options available to developers to do not cover all environments and setups - we are working on expanding this list and approaching more general solutions, but there is no one-size-fits-all approach when dealing with rerouting network traffic.
</Callout>
Rust developers can rely on our Rust SDK to import Nym client functionality into their code. This can either be in the form of a standard message-based client, the `socks5` client, or the `TcpProxy` modules.
Integration options are then further subdivided by app **architecture**; whether the application interacts with remote hosts on the public internet running independently of the app (e.g. public blockchain RPC endpoints, third-party APIs) or whether app developers have some control over the versions of the software being run on both sides of an interaction (e.g. peer to peer apps running the same software version, or client-server architectures which are running software written by the same team).
We aim to expose at least the majority of this functionality via FFI to Go and C/C++. This is detailed alongside the Rust SDK components in the [Rust SDK docs](./rust).
</>
</Tabs.Tab>
<Tabs.Tab>
<>
Typescript and Javascript developers have several options avaliable to them:
- [`mixfetch`](./typescript/examples/mix-fetch) is an almost-dropin replacement for the `fetch` library. The best way to integrate Nym's `mixFetch` into your application will be where external network calls and RPC happens, for example, something in the lines of `sendRawTransaction` if you have an ETH-compatible wallet or `JsonRpcClient` if you use CosmJS. Although you can simply search for any JS `fetch` calls in your code (using our tool below) that are easily replaceable with `mixFetch`, keep in mind that `fetch` is not the only way to make `JSONRPC` or `XHR` calls. We advise to approach the integration process in a semantic way, searching for a module that is the common denominator for external communication in the codebase. Usually these are API controllers, middlewares or repositories.
<GitHubRepoSearch />
- Otherwise, a well-modularized JS/TS codebase should permit the integration of one of our other SDK components, which will allow more flexibility and control (or if your codebase is not using something that can be covered by `fetch` for networking). Read more about our different SDK components in the [TS SDK overview page](./typescript/overview).
</ >
</Tabs.Tab>
<Tabs.Tab> If your app is not written in any of the supported languages, you might still be able to send traffic through a standalone [socks5 client](./clients/socks5) but will have to think about packaging and bundling the client binary with e.g. a `systemd` file for autostart to run the client as a daemon. If you want to discuss FFI options reach out to us via our public dev channel. </Tabs.Tab>
</Tabs>
## Interacting with Nyx
If instead of relying on the Mixnet you wish to interact with the Nyx chain, either as a payment processor or to get on-chain events, see [interacting with the chain](./chain).
> Note that depending on your setup, you might already be able to combine interacting with the chain with using the Mixnet: check the options above for more.
<Callout type="info" emoji="️">
This is because of the different security considerations each option offers. These are detailed in the following pages.
</Callout>
@@ -0,0 +1,86 @@
import { Callout } from 'nextra/components';
# Native / Desktop Apps
Developers wanting to integrate into desktop apps & CLIs can use our Rust SDK. There are two broad approaches to using the Mixnet (E2E or as a proxy), with different modules suited for each, each with their own specific usecase and limitations.
## Option 1: Mixnet End-To-End
You might want to embed Nym Clients in both sides of your app, and have them send all of your app network traffic through the Mixnet: maybe two clients in a peer to peer setup, or a client and a server where it is possible for you as a developer to release both the client and server side code, and have some ability to make sure that it is being run.
![](/images/developers/nym-arch-client-to-client.png)
There are several options available:
{ /* ### Stream Wrapper Module
Exposes `MixSocket`/`MixStream` abstractions that can be split a reader/writer halves that consumes bytes, with an interface inspired by `std::net::TcpStream`. For developers who just want to read/write bytes to/from the Mixnet working with something socket-like.
- docs TODO LINK
- example TODO LINK */ }
### Mixnet & Client Pool Modules
The Mixnet module of the SDK exposes low level connection functionality and the Mixnet Client. The Client Pool is one answer to concurrency, and allows developers to run several Nym Clients at once which can be quickly used.
{ /* This approach might be useful if you want to build custom connection logic, but **`MixSocket`/`MixStream` will probably be sufficient for the majority of usecases** where developers just want to send and receive traffic as streams. */ }
This approach might be useful if you want to build custom connection logic, but the TcpProxy Module will probably be sufficient for the majority of usecases where developers just want to send and receive traffic as streams.
- [docs](./rust/mixnet)
- [examples](./rust/mixnet/examples)
### TcpProxy Module
{ /* <Callout type="warning" emoji="⚠️">
This module has been superseded by the Stream Wrapper module, and will soon be deprecated. **New features will not be added to it.** The main drawback of this (which is fixed with `MixSocket`/`MixStream`) is that TLS is impossible, as it exposes a localhost port for the consuming process to communicate with.
</Callout> */ }
A pair of abstractions built for use in a client-server setup, which both expose a `localhost` TCP Socket which apps can read/write bytes to/from.
- [docs](./rust/tcpproxy)
- [examples](./rust/tcpproxy/examples/singleconn)
<Callout type="info" emoji="️">
There is a new abstraction coming soon mirroring the interface and use of a TCP Socket, making it easier for developers to use the Mixnet, and also perform TLS through a Mixnet connection. Stay tuned.
</Callout>
## Option 2: Mixnet-As-Proxy
For developers who are only able to control the client-side code, and/or need to communicate with a 3rd party service, such as a public blockchain RPC or a remote host they do not control.
![](/images/developers/nym-arch-ip-routing.png)
<Callout type="warning">
### Security Considerations
Since traffic is only packaged as Sphinx until it gets to the Exit Gateway, where it is unwrapped into either HTTPS packets (by a Network Requester) or IP packets (by an IP Packet Router), the last hop between the Gateway and the remote host **travels as normal internet traffic**.
As such, this option has fewer protections than the E2E option against a global passive adversary, but still grants you timing obfuscation and sender-receiver unlinkability between your client software and whatever service it is interacting with.
</Callout>
### SOCKS Client
Developers with apps that support SOCKS4,4a, or 5 can use the Socks Client exposed by the Mixnet module. This uses the Network Requester service of the chosen Exit Gateway to interact with the remote host via the chosen SOCKS proxy protocol. The Network Requester uses SURBs to anonymously reply to the original sender with whatever response it gets from the remote host.
- [docs](./rust/mixnet/examples/socks)
- [example](./rust/mixnet/examples/socks)
<Callout type="info" emoji="️">
There is a new abstraction coming soon that will allow the SDK to send IP packets, the beginning of a longer project to make a native Rust version of [`mixFetch`](./typescript/start#mixfetch). Stay tuned.
</Callout>
{ /* ### `IPMixStream`
This is a version of the `MixSocket` that consumes IP packets before wrapping them in Sphinx and forwarding through the Mixnet to the IP Packet Router of the chosen Exit Gateway, where they are unwrapped and treated as normal IP packets. The IPR uses SURBs to anonymously reply to the original sender with whatever response it gets from the remote host.
<Callout type="info" emoji="️">
Currently only consumes IP packets - those who do not want to work with IP packets directly should check `mixtcp` below for doing something with HTTP(S).
</Callout>
- docs TODO LINK
- examples TODO LINK
### `MixTCP`
A proof of concept TCP/IP crate containing a [`smoltcp`](https://docs.rs/smoltcp/latest/smoltcp/index.html) `device` that uses the Mixnet for transport. Examples of using this to make a TLS handshake and perform an HTTPS request can be found linked below.
<Callout type="info" emoji="️">
This crate is currently a proof of concept which is in active development. This crate will become the basis of a general-purpose HTTP-through-Mixnet crate in the near future.
</Callout>
- docs TODO LINK
- example TODO LINK */ }
+1 -14
View File
@@ -1,16 +1,3 @@
# Introduction
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a separate process on their machine.
Check the [development status](./rust/development-status) page to see the various modules that make up the SDK, and the [FFI](./rust/ffi) page for Go/C++ developers.
The Rust SDK allows exposes a few different modules, some more plug and play than others. Each of which handles exposes a Nym Client, which handles finding and using a route for packets through the Mixnet, encryption, and cover traffic, all under the hood.
@@ -1,8 +1,7 @@
{
"importing": "Importing",
"development-status": "Development Status",
"mixnet": "Mixnet Module",
"tcpproxy": "TcpProxy Module",
"client-pool": "Client Pool",
"mixnet": "Mixnet Module",
"client-pool": "Client Pool Module",
"ffi": "FFI"
}
@@ -2,15 +2,6 @@
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
We have a configurable-size Client Pool for processes that require multiple clients in quick succession (this is used by default by the [`TcpProxyClient`](./tcpproxy) for instance)
This will be useful for developers looking to build connection logic, or just are using raw SDK clients in a sitatuation where there are multiple connections with a lot of churn.
@@ -2,17 +2,6 @@
# FFI Bindings
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
<Callout type="info" emoji="️">
We are working on the intitial versions of the FFI code to allow developers to experiment and get feedback. Please get in touch if you think the FFI bindings are lacking certain functionality.
</Callout>
We currently have FFI bindings for Go and C/C++. See the table below to check the coverage of functionality we expect devs would like to see.
The [`nym/sdk/ffi`](https://github.com/nymtech/nym/tree/master/sdk/ffi) directory has the following structure:
@@ -50,7 +39,7 @@ At the time of writing the following functionality is not exposed to the shared
- `Socks5::new()`: creation and use of the [socks5/4a/4 proxy client](./mixnet/examples/socks).
## TcpProxy Module
A connection abstraction which exposes a local TCP socket which developers are able to interact with basically as expected, being able to read/write to/from a bytestream, without really having to take into account the workings of the Mixnet/Sphinx/the [message-based](../concepts/messages) format of the underlying client.
A connection abstraction which exposes a local TCP socket which developers are able to interact with basically as expected, being able to read/write to/from a bytestream, without really having to take into account the workings of the Mixnet/Sphinx/the message-based format of the underlying client.
<Callout type="info" emoji="️">
At the time of writing this functionality is **only** exposed to Go. C/C++ bindings will follow in the future in a larger update to the C FFI.
@@ -1,14 +1,6 @@
# Installation
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
The `nym-sdk` crate is **not yet available via [crates.io](https://crates.io)**. As such, in order to import the crate you must specify the Nym monorepo in your `Cargo.toml` file. Since the `HEAD` of `master` is always the most recent release, we recommend developers use that for their imports, unless they have a reason to pull in a specific historic version of the code.
```toml
@@ -1,14 +1,6 @@
# Mixnet Module
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
This module exposes the logic of creating and interacting with clients and Mixnet messages. This is recommended for those wanting to either start playing around with the Mixnet and how it works, or build connection logic.
> For developers wanting something more 'plug and play' we recommend the [`TcpProxy` module](./tcpproxy).
@@ -1,11 +1,4 @@
# Builder Patterns
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
Since there are two ways of creating an SDK client - ephemeral and with-storage - then there are two ways of applying the Builder Pattern to client creation.
@@ -1,13 +1,6 @@
# Mixnet Client Builder with Storage
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys.
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder_with_storage.rs).
@@ -1,13 +1,6 @@
# Mixnet Client Builder
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
You can spin up an ephemeral client like so. This client will not have a persistent identity and its keys will be dropped on restart. Since there is currently no way of reconnecting a client that has been disconnected after use, then treat disconnecting a client the same as dropping its keys entirely.
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder.rs).
@@ -2,14 +2,6 @@
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
<Callout type="warning" emoji="⚠️">
These examples are **not** the same as using a configurable network: these functions define a subset of nodes to use on a given network, whereas the [testnet](./testnet) example is an example of switching to use a different network entirely. The two can be combined, but if you are looking for how to connect your client to a testnet, see the `testnet` file.
</Callout>
@@ -1,13 +1,6 @@
# Custom Topology Provider
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood).
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/custom_topology_provider.rs)
@@ -33,7 +26,7 @@ impl MyTopologyProvider {
.expect("Failed to create API client builder")
.build::<nym_validator_client::models::RequestError>()
.expect("Failed to build API client");
MyTopologyProvider {
validator_client,
}
@@ -1,13 +1,6 @@
# Manually Overwrite Topology
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually.
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs)
@@ -2,13 +2,6 @@
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code.
Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet.
@@ -2,13 +2,6 @@
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4.
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/socks5.rs)
@@ -2,13 +2,6 @@
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
If you need to split the different actions of your client across different tasks, you can do so like this. You can think of this analogously to spliting a Tcp Stream into read/write. This functionality is also useful for embedding a sending and receiving client into different tasks.
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs)
@@ -2,13 +2,6 @@
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform these actions.
> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_handle_storage.rs)
@@ -2,13 +2,6 @@
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default.
You can read more about how SURBs function under the hood [here](../../../../network/traffic/anonymous-replies).
@@ -1,13 +1,7 @@
# Configurable Network
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
If you want to connect your Mixnet client to a different network than Mainnet, simply pull in a file from [`nym/envs`](https://github.com/nymtech/nym/tree/master/envs) as such:
```rust
@@ -1,15 +1,8 @@
# TcpProxy Module
import { Callout } from 'nextra/components';
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
This module exposes the `TcpProxyClient` and the `TcpProxyServer` which can be used to proxy traffic through the Mixnet in a way that is more familiar to developers than the methods exposed by the [`Mixnet` module](./mixnet).
Both `Client` and `Server` are intended to be initialised and then run in a background thread, exposing a configurable `localhost` socket which developers can read/write/stream to without having to worry about the [message-based](../concepts/messages) nature of sending and receiving traffic to/from the Mixnet.
Both `Client` and `Server` are intended to be initialised and then run in a background thread, exposing a configurable `localhost` socket which developers can read/write/stream to without having to worry about the message-based nature of sending and receiving traffic to/from the Mixnet.
> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy).
@@ -3,14 +3,6 @@
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
## Motivations
The motivation behind the creation of the `TcpProxy` module is to allow developers to interact with the Mixnet in a way that is far more familiar to them: simply setting up a connection with a transport, being returned a socket, and then being able to stream data to/from it, similar to something like the Tor [`arti`](https://gitlab.torproject.org/tpo/core/arti/-/tree/main/crates/arti-client) client.
@@ -1,13 +1,7 @@
# Multi Connection Example
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
This example starts off several Tcp connections on a loop to a remote endpoint: in this case the `TcpListener` behind the `NymProxyServer` instance on the echo server found in
[`nym/tools/echo-server/`](https://github.com/nymtech/nym/tree/develop/tools/echo-server). It pipes a few messages to it, logs the replies, and keeps track of the number of replies received per connection.
@@ -1,13 +1,7 @@
# Single Connection Example
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
This is a basic example which opens a single TCP connection and writes a bunch of messages between a client and some 'echo server' logic, so only uses a single session under the hood and doesn't really show off the message ordering capabilities; this is mainly just a quick introductory illustration on how:
- the mixnet does message ordering
- the NymProxyClient and NymProxyServer can be hooked into and used to communicate between two otherwise pretty vanilla TcpStreams
@@ -1,12 +1,5 @@
# Troubleshooting
import { Callout } from 'nextra/components'
<Callout type="warning">
There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with.
This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet.
It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/).
</Callout>
## Lots of `duplicate fragment received` messages
You might see a lot of `WARN` level logs about duplicate fragments in your logs, depending on the log level you're using. This occurs when a packet is retransmitted somewhere in the Mixnet, but then the original makes it to the destination client as well. This is not something to do with your client logic, but instead the state of the Mixnet.
@@ -1,7 +1,7 @@
# Tools
There are a few tools available to developers for chain interaction: the `nym-cli` tool, which operates as an easier-to-use wrapper around `nyxd`, to allow operators to script interactions with their infrastructure (and those who prefer CLI tools ;) ).
There are a few tools available to developers for chain interaction: the `nym-cli` tool, which operates as an easier-to-use wrapper around `nyxd`, to allow operators to script interactions with their infrastructure (and those who prefer CLI tools).
There is also a basic echo server tool which app developers can use as a quick endpoint for traffic testing. This will be deployed onto a persistent public server in the future so devs dont have to run it themselves.
Finally, there are a pair of standalone versions of the TcpProxy Rust SDK module for developers to begin experimenting with sending app traffic through them mixnet.
Finally, there are also a pair of standalone versions of the TcpProxy Rust SDK module for developers to begin experimenting with sending app traffic through them mixnet (**this module will soon be deprecated in place of the `MixSocket`/`MixStream` abstractions**).
@@ -18,7 +18,7 @@ Sounds great, are there any catches? Well, there are a few (for now):
<Callout type="info" emoji="️">
Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so.
For the moment you have to select a Gateway that has WSS enabled and Network Requester from [Harbourmaster Gateways for mixFetch list](https://harbourmaster.nymtech.net/).
For the moment you have to select a Gateway that has WSS from [Harbourmaster Gateways for mixFetch list](https://harbourmaster.nymtech.net/).
```
curl -X 'GET' \
@@ -33,9 +33,7 @@ curl -X 'GET' \
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
const mixFetchOptions: SetupMixFetchOps = {
preferredGateway: '23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', // with WSS
preferredNetworkRequester:
'HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb',
preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", // with WSS
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
@@ -10,4 +10,9 @@ import { Callout } from 'nextra/components'
<MixFetch />
<Callout type="info">
Open your browser's console to see the connection and send/receive logging for this example.
</Callout>
<FormattedMixFetchExampleCode />
@@ -7,5 +7,9 @@ import FormattedTrafficExampleCode from '../../../../code-examples/sdk/typescrip
Use this tool to experiment with the mixnet: send and receive messages!
<Callout type="info">
Open your browser's console to see the connection and send/receive logging for this example.
</Callout>
<Traffic />
<FormattedTrafficExampleCode />
@@ -32,7 +32,7 @@ Upcoming:
## Nym Clients
<Callout type="info" emoji="️">
You can read about setting up and using various clients in the [Developer Docs](../../developers/clients).
You can read about setting up and using various clients in the [Developer Docs](../../developers/clients/socks5).
</Callout>
A large proportion of the Nym Mixnet's functionality is implemented client-side.
@@ -48,4 +48,4 @@ Clients perform the following actions on behalf of users:
* Send Sphinx packet [cover traffic](../concepts/cover-traffic) when no real messages are being sent
* Retransmit [un-acknowledged packet sends](../traffic/acks)
> At the moment due to the fact that Nym clients are [message-based](../../developers/concepts/messages), using the Mixnet requires another client on the 'other side' of the mixet to send packets to, unless you're using the `nymvpn` client (part of the NymVPN app) or the `socks5` client, which operates as a SOCKS4,4a, or 5 proxy and is able to utilise the client embedded within the `nym-node`'s Exit Gateway functionality (prev. this functionality was a standalone service, the Network Requester). In the future we wish to remove this point of friction and have all Nym clients construct IP packets instead, easing the integration burden and abstracting away the message-based nature of client communication.
> At the moment due to the fact that Nym clients are message-based, using the Mixnet requires another client on the 'other side' of the mixet to send packets to, unless you're using the `nymvpn` client (part of the NymVPN app) or the `socks5` client, which operates as a SOCKS4,4a, or 5 proxy and is able to utilise the client embedded within the `nym-node`'s Exit Gateway functionality (prev. this functionality was a standalone service, the Network Requester). In the future we wish to remove this point of friction and have all Nym clients construct IP packets instead, easing the integration burden and abstracting away the message-based nature of client communication.
@@ -49,6 +49,76 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `v2025.20-leerdammer`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.20-leerdammer)
- [`nym-node`](nodes/nym-node.mdx) version `1.21.0`
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-11-12T08:19:33.288341371Z
Build Version: 1.21.0
Commit SHA: babf113fe5d396fa8a84fa939ad4b1b5b4d38b83
Commit Date: 2025-11-12T08:39:48.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.88.0
rustc Channel: stable
cargo Profile: release
```
### Operators Updates & Tools
- [New **Performance Measurement** page](/operators/performance-and-testing) explaining the logic behind node selection for NymVPN application
- [New **Gateway Probe Details** page](/operators/performance-and-testing/gateway-probe-details) explaining complexity and contradictions when measuring network performance
### Developer Tools
- [Typescript SDK 1.4.1](https://github.com/nymtech/nym/pull/6146): This PR is a new release of the Typescript SDK, `mixFetch` and `WASM` client. It also removes the Harbour Master client from `mixFetch`, replacing it with the Nym API's described endpoint for nym-nodes
- [Overhauled **developer integrations** pages](/developers/integrations) explaining the different restrictions for the different SDK options on offer
- [Fixed `mixFetch` and `WASM Client` playground + examples](/developers/typescript/start): new versions of the Typescript SDK and `mixFetch` have been published, examples and live playground have been updated accordingly
### Features
- [Tweak ts sdk actions](https://github.com/nymtech/nym/pull/6185): Using `taskset` to limit the number of of CPUs used by `wasm-pack` and `wasm-opt` commands used by ts linting CI
- [Configurable mixnet client startup timeout](https://github.com/nymtech/nym/pull/6148)
- [QUIC bridge deployment script v2](https://github.com/nymtech/nym/pull/6145): Script helping operators to install, configure and deploy QUIC bridge as systemd service
- [Expose more explicit `new_with_fronted_urls` builder for http API client](https://github.com/nymtech/nym/pull/6136)
- [Domain fronting](https://github.com/nymtech/nym/pull/6134): Enable URL rotation and retries for mixnet gateway [`init`](https://github.com/nymtech/nym/pull/6126)
### Bugfix
- [Add circuit breaker](https://github.com/nymtech/nym/pull/6143): When the mixnet client's `mix_tx` channel closes during network drops, `OutQueueControl` would retry sending packets through the closed channel, flooding logs and hanging the daemon. This affects all clients (VPN, SOCKS5, native clients), not just VPN .. (Read more in the [PR description](https://github.com/nymtech/nym/pull/6143))
- [Update internal owner address in transferred share](https://github.com/nymtech/nym/pull/6139)
- [Update quic_bridge_deployment.sh for IPv4 and .deb package](https://github.com/nymtech/nym/pull/6138): Updated ping commands to explicitly use IPv4 and adjusted file permission checks with sudo. Changed the forward address prompt to specify IPv4 and modified the binary download process to fetch and install the latest `.deb` release URL automatically
- [Update stored epoch share when changing ownership](https://github.com/nymtech/nym/pull/6135)
- [Update stored epoch share when changing announce address](https://github.com/nymtech/nym/pull/6131)
### Refactors & Maintenance
- [Re-merge: disconnect mixnet client if registration fails](https://github.com/nymtech/nym/pull/6169) ([\#6158](https://github.com/nymtech/nym/pull/6158))
- [Resolve `clippy 1.91` warnings](https://github.com/nymtech/nym/pull/6168)
- [Remove unused dependencies](https://github.com/nymtech/nym/pull/6151): Removing the crates that only shows up on the workspace `Cargo.toml`
- [Use typed-builder for registration client builder config](https://github.com/nymtech/nym/pull/6150)
- [tommy is too quick](https://github.com/nymtech/nym/pull/6149)
## `v2025.19-kase`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.19-kase)
@@ -21,10 +21,10 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-10-30T12:43:37.933354749Z
Build Version: 1.20.0
Commit SHA: 75a6d3426bd18dca600ad1cfa39b0a3c4f319c69
Commit Date: 2025-10-30T11:59:32.000000000+01:00
Build Timestamp: 2025-11-12T08:19:33.288341371Z
Build Version: 1.21.0
Commit SHA: babf113fe5d396fa8a84fa939ad4b1b5b4d38b83
Commit Date: 2025-11-12T08:39:48.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.88.0
rustc Channel: stable
@@ -9,23 +9,21 @@ Nym Node operators running Gateway functionality are already familiar with the m
## Preparation
We recommend to have installed all [the prerequisites](../binaries/building-nym.md#prerequisites) needed to build `nym-node` from source including latest [Rust Toolchain](https://www.rust-lang.org/tools/install), **and** make sure to have [Go](https://go.dev/doc/install) installed. Go is necessary as the probe uses the `rust2go` FFI library to use `netstack` when making requests.
We recommend to have installed all [the prerequisites](../binaries/building-nym.md#prerequisites) needed to build `nym-node` from source including latest [Rust Toolchain](https://www.rust-lang.org/tools/install), **and** make sure to have [Go](https://go.dev/doc/install) installed. Go is necessary as the probe uses the `rust2go` FFI library to use `netstack` when making requests.
## Installation
`nym-gateway-probe` source code is in [`nym-vpn-client`](https://github.com/nymtech/nym-vpn-client) repository. The client needs to be build from source.
`nym-gateway-probe` source code is in [`nym` monorepo](https://github.com/nymtech/nym). The probe needs to be built from source.
1. Clone the repository:
```sh
git clone https://github.com/nymtech/nym-vpn-client.git
git clone https://github.com/nymtech/nym.git
```
2. Build `nym-gateway-probe`:
```sh
cd nym-vpn-client/nym-vpn-core
cargo build --release -p nym-gateway-probe
```
@@ -34,65 +32,71 @@ cargo build --release -p nym-gateway-probe
To list all commands and options run the binary with `--help` command:
```sh
./target/release/nym-gateway-probe -h
./target/release/nym-gateway-probe -h
```
- Output:
```sh
Usage: nym-gateway-probe [OPTIONS] --mnemonic <MNEMONIC>
Usage: nym-gateway-probe [OPTIONS] [COMMAND]
Options:
-c, --config-env-file <CONFIG_ENV_FILE>
Path pointing to an env file describing the network
-g, --entry-gateway <ENTRY_GATEWAY>
The specific gateway specified by ID
-n, --node <NODE>
Identity of the node to test
--min-gateway-mixnet-performance <MIN_GATEWAY_MIXNET_PERFORMANCE>
--min-gateway-vpn-performance <MIN_GATEWAY_VPN_PERFORMANCE>
--only-wireguard
-i, --ignore-egress-epoch-role
Disable logging during probe
--no-log
-a, --amnezia-args <AMNEZIA_ARGS>
Arguments to be appended to the wireguard config enabling amnezia-wg configuration
--netstack-download-timeout-sec <NETSTACK_DOWNLOAD_TIMEOUT_SEC>
[default: 180]
--netstack-v4-dns <NETSTACK_V4_DNS>
[default: 1.1.1.1]
--netstack-v6-dns <NETSTACK_V6_DNS>
[default: 2606:4700:4700::1111]
--netstack-num-ping <NETSTACK_NUM_PING>
[default: 5]
--netstack-send-timeout-sec <NETSTACK_SEND_TIMEOUT_SEC>
[default: 3]
--netstack-recv-timeout-sec <NETSTACK_RECV_TIMEOUT_SEC>
[default: 3]
--netstack-ping-hosts-v4 <NETSTACK_PING_HOSTS_V4>
[default: nymtech.net]
--netstack-ping-ips-v4 <NETSTACK_PING_IPS_V4>
[default: 1.1.1.1]
--netstack-ping-hosts-v6 <NETSTACK_PING_HOSTS_V6>
[default: ipv6.google.com]
--netstack-ping-ips-v6 <NETSTACK_PING_IPS_V6>
[default: 2001:4860:4860::8888 2606:4700:4700::1111 2620:fe::fe]
--mnemonic <MNEMONIC>
-h, --help
Print help
-V, --version
Print version
Commands:
run-local Run the probe locally
help Print this message or the help of the given subcommand(s)
Options:
-c, --config-env-file <CONFIG_ENV_FILE>
Path pointing to an env file describing the network
-g, --entry-gateway <ENTRY_GATEWAY>
The specific gateway specified by ID
-n, --node <NODE>
Identity of the node to test
--min-gateway-mixnet-performance <MIN_GATEWAY_MIXNET_PERFORMANCE>
--only-wireguard
--ignore-egress-epoch-role
Disable logging during probe
--no-log
-a, --amnezia-args <AMNEZIA_ARGS>
Arguments to be appended to the wireguard config enabling amnezia-wg configuration
--netstack-download-timeout-sec <NETSTACK_DOWNLOAD_TIMEOUT_SEC>
[default: 180]
--metadata-timeout-sec <METADATA_TIMEOUT_SEC>
[default: 30]
--netstack-v4-dns <NETSTACK_V4_DNS>
[default: 1.1.1.1]
--netstack-v6-dns <NETSTACK_V6_DNS>
[default: 2606:4700:4700::1111]
--netstack-num-ping <NETSTACK_NUM_PING>
[default: 5]
--netstack-send-timeout-sec <NETSTACK_SEND_TIMEOUT_SEC>
[default: 3]
--netstack-recv-timeout-sec <NETSTACK_RECV_TIMEOUT_SEC>
[default: 3]
--netstack-ping-hosts-v4 <NETSTACK_PING_HOSTS_V4>
[default: nym.com]
--netstack-ping-ips-v4 <NETSTACK_PING_IPS_V4>
[default: 1.1.1.1]
--netstack-ping-hosts-v6 <NETSTACK_PING_HOSTS_V6>
[default: cloudflare.com]
--netstack-ping-ips-v6 <NETSTACK_PING_IPS_V6>
[default: 2001:4860:4860::8888 2606:4700:4700::1111 2620:fe::fe]
--ticket-materials <TICKET_MATERIALS>
--ticket-materials-revision <TICKET_MATERIALS_REVISION>
[default: 1]
-h, --help
Print help
-V, --version
Print version
```
To run the client, simply add `-n` flag followed by the ID key of the node you wish to test, as well as the mnemonic of a funded Nyx account; this is required to test the ticketbook generation.
To run the client, simply add `-g` flag followed by the ID key of the node you wish to test, as well as the mnemonic of a funded Nyx account; this is required to test the ticketbook generation. **Note that this accout needs to have NYM tokens; you cannot use an account with a NymVPN subscription**. Make sure to have at least a few NYM tokens in there.
```sh
./target/release/nym-gateway-probe -n <GATEWAY_IDENTITY_KEY> --mnemonic <MNEMONIC>
./target/release/nym-gateway-probe run-local -g <GATEWAY_IDENTITY_KEY> --mnemonic <MNEMONIC>
```
For any `nym-node --mode exit-gateway` the aim is to have this outcome:
@@ -125,4 +129,4 @@ For any `nym-node --mode exit-gateway` the aim is to have this outcome:
**If your Gateway is blacklisted, the probe will not work.**
If you don't provide a `-n` flag it will pick a random node to test.
If you don't provide a `-g` flag it will pick a random node to test.
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

@@ -1,6 +1,6 @@
[package]
name = "nym-credential-proxy"
version = "0.3.0"
version = "0.3.0-test"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+1 -1
View File
@@ -56,7 +56,7 @@ nym-ip-packet-requests = { path = "../common/ip-packet-requests" }
nym-sdk = { path = "../sdk/rust/nym-sdk" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-credentials = { path = "../common/credentials" }
nym-http-api-client-macro = { path = "../common/http-api-client-macro", features = ["debug-inventory"] }
nym-http-api-client-macro = { path = "../common/http-api-client-macro" }
nym-http-api-client = { path = "../common/http-api-client" }
nym-node-status-client = { path = "../nym-node-status-api/nym-node-status-client" }
+4 -4
View File
@@ -4,15 +4,15 @@ all: build build-node
build: test build-wasm
build-wasm:
wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/client
wasm-opt -Oz -o ../../dist/wasm/client/nym_client_wasm_bg.wasm ../../dist/wasm/client/nym_client_wasm_bg.wasm
taskset -c 0-11 wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/client
taskset -c 0-11 wasm-opt -Oz -o ../../dist/wasm/client/nym_client_wasm_bg.wasm ../../dist/wasm/client/nym_client_wasm_bg.wasm
build-debug-dev:
wasm-pack build --debug --scope nymproject --target no-modules
build-rust-node:
wasm-pack build --scope nymproject --target nodejs --out-dir ../../dist/node/wasm/client
wasm-opt -Oz -o ../../dist/node/wasm/client/nym_client_wasm_bg.wasm ../../dist/node/wasm/client/nym_client_wasm_bg.wasm
taskset -c 0-11 wasm-pack build --scope nymproject --target nodejs --out-dir ../../dist/node/wasm/client
taskset -c 0-11 wasm-opt -Oz -o ../../dist/node/wasm/client/nym_client_wasm_bg.wasm ../../dist/node/wasm/client/nym_client_wasm_bg.wasm
build-package-json-node:
node build-node.mjs
+2 -2
View File
@@ -1,5 +1,5 @@
all: build-full
build-full:
wasm-pack build --all-features --scope nymproject --target web --out-dir ../../dist/wasm/full-nym-wasm
wasm-opt -Oz -o ../../dist/wasm/full-nym-wasm/nym_wasm_sdk_bg.wasm ../../dist/wasm/full-nym-wasm/nym_wasm_sdk_bg.wasm
taskset -c 0-11 wasm-pack build --all-features --scope nymproject --target web --out-dir ../../dist/wasm/full-nym-wasm
taskset -c 0-11 wasm-opt -Oz -o ../../dist/wasm/full-nym-wasm/nym_wasm_sdk_bg.wasm ../../dist/wasm/full-nym-wasm/nym_wasm_sdk_bg.wasm
+3 -3
View File
@@ -9,8 +9,8 @@ build-go-opt:
$(MAKE) -C go-mix-conn build-go-opt
build-rust:
wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/mix-fetch
wasm-opt -Oz -o ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm
taskset -c 0-11 wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/mix-fetch
taskset -c 0-11 wasm-opt -Oz -o ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm
build-rust-debug:
wasm-pack build --debug --scope nymproject --target no-modules
@@ -23,4 +23,4 @@ check-fmt-go:
check-fmt-rust:
cargo fmt --check
cargo clippy --target wasm32-unknown-unknown -- -Dwarnings
cargo clippy --target wasm32-unknown-unknown -- -Dwarnings