Files
TrustTunnel/deeplink
Ilia Zhirov fe596bc58a Pull request 163: TRUST-268 support dual stack hostname only connection to server
Squashed commit of the following:

commit 52522b5b230f0abf1acb085432b181db6214006a
Merge: 2ad5749 9d0de3e
Author: Sergey Fionov <sfionov@adguard.com>
Date:   Thu Feb 26 09:48:16 2026 +0200

    Merge remote-tracking branch 'origin/master' into TRUST-268-support-dual-stack-hostname-only-connection-to-server

commit 2ad5749fff
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Thu Feb 19 16:31:55 2026 +0500

    Fix formatting

commit ab0597f558
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Thu Feb 19 16:11:13 2026 +0500

    Code cleanup

commit d8329217cf
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Thu Feb 19 15:52:08 2026 +0500

    Update changelog

commit c90821b4c8
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Thu Feb 19 15:14:45 2026 +0500

    Support hostnames in deeplinks

commit f7e184a5e8
Merge: 71fdf97 8d5f207
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Thu Feb 19 14:36:29 2026 +0500

    Merge branch 'master' into TRUST-268-support-dual-stack-hostname-only-connection-to-server
    
    # Conflicts:
    #	CHANGELOG.md
    #	README.md
    #	endpoint/src/main.rs
    #	lib/src/client_config.rs

commit 71fdf97343
Author: Ilia Zhirov <i.zhirov@adguard.com>
Date:   Fri Feb 13 19:43:24 2026 +0500

    Explicitly set IPV6_V6ONLY=false for dual-stack listen sockets
    
    Change addresses type from Vec<SocketAddr> to Vec<String>
    
    Accept domain names in -a flag for client config export
    
    Warn when -a domain does not match any hostname in hosts.toml
    
    Update -a flag documentation to reflect domain name support
    
    Add unit tests for parse_endpoint_address
    
    Code quality improvements
    
    Unmap IPv6-mapped IPv4 addresses (::ffff:a.b.c.d) before rules evaluation
    
    Add more tests
    
    Code cleanup
2026-02-26 07:52:05 +00:00
..

TrustTunnel Deep-Link Library

A standalone Rust library for encoding and decoding TrustTunnel configuration deep-links using the tt:// URI scheme.

Features

  • Complete TLV encoding/decoding - Implements the full TrustTunnel deep-link specification
  • Error handling - Comprehensive error types with helpful messages
  • Base64url encoding - URL-safe, compact representation
  • Certificate support - PEM/DER conversion utilities
  • Property-based testing - Verified with proptest for correctness

Installation

Add to your Cargo.toml:

[dependencies]
trusttunnel-deeplink = { git = "https://github.com/TrustTunnel/TrustTunnel/deeplink" }

Quick Start

Encoding a Configuration

use trusttunnel_deeplink::{encode, DeepLinkConfig};
use std::net::SocketAddr;

let config = DeepLinkConfig::builder()
    .hostname("vpn.example.com".to_string())
    .addresses(vec!["1.2.3.4:443".parse::<SocketAddr>().unwrap()])
    .username("alice".to_string())
    .password("secret123".to_string())
    .build()
    .unwrap();

let uri = encode(&config).unwrap();
println!("Deep-link: {}", uri);
// Output: tt://AQ92cG4uZXhhbXBsZS5jb20CAzEuMi4zLjQ6NDQzBQVhbGljZQYJc2VjcmV0MTIz
use trusttunnel_deeplink::decode;

let uri = "tt://AQ92cG4uZXhhbXBsZS5jb20CAzEuMi4zLjQ6NDQzBQVhbGljZQYJc2VjcmV0MTIz";
let config = decode(uri).unwrap();

println!("Hostname: {}", config.hostname);
println!("Username: {}", config.username);

Configuration Fields

The DeepLinkConfig struct supports the following fields:

Field Type Required Default Description
hostname String Yes - Server hostname
addresses Vec<SocketAddr> Yes - Server addresses (IP:port)
username String Yes - Authentication username
password String Yes - Authentication password
custom_sni Option<String> No None Custom SNI for TLS
has_ipv6 bool No true IPv6 support enabled
skip_verification bool No false Skip certificate verification
certificate Option<Vec<u8>> No None DER-encoded certificate chain
upstream_protocol Protocol No Http2 Upstream protocol (HTTP/2 or HTTP/3)
anti_dpi bool No false Anti-DPI measures enabled

Advanced Usage

Working with Certificates

use trusttunnel_deeplink::cert::{pem_to_der, der_to_pem};

// Convert PEM certificate to DER
let pem = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----";
let der = pem_to_der(pem).unwrap();

// Convert DER back to PEM
let pem_again = der_to_pem(&der).unwrap();

Builder Pattern

use trusttunnel_deeplink::{DeepLinkConfig, Protocol};

let config = DeepLinkConfig::builder()
    .hostname("vpn.example.com".to_string())
    .addresses(vec!["1.2.3.4:443".parse().unwrap()])
    .username("user".to_string())
    .password("pass".to_string())
    .custom_sni(Some("cdn.example.org".to_string()))
    .has_ipv6(false)
    .upstream_protocol(Protocol::Http3)
    .anti_dpi(true)
    .build()
    .unwrap();

Error Handling

The library uses a custom DeepLinkError type with specific error variants:

use trusttunnel_deeplink::{decode, DeepLinkError};

match decode("invalid://uri") {
    Ok(config) => println!("Success!"),
    Err(DeepLinkError::InvalidScheme(scheme)) => {
        println!("Invalid URI scheme: {}", scheme);
    }
    Err(DeepLinkError::MissingRequiredField(field)) => {
        println!("Missing required field: {}", field);
    }
    Err(e) => println!("Other error: {}", e),
}

Testing

Run the test suite:

# Unit tests
cargo test -p trusttunnel-deeplink

# Integration tests (roundtrip)
cargo test -p trusttunnel-deeplink --test roundtrip

# Python compatibility tests
cargo test -p trusttunnel-deeplink --test python_compat

# Property-based tests
cargo test -p trusttunnel-deeplink --test proptest