mirror of
https://github.com/TrustTunnel/TrustTunnel.git
synced 2026-04-24 03:30:41 +00:00
Support hostnames in deeplinks
This commit is contained in:
@@ -2,7 +2,6 @@ use crate::error::{DeepLinkError, Result};
|
||||
use crate::types::{DeepLinkConfig, Protocol, TlvTag};
|
||||
use crate::varint::decode_varint;
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
/// Decode a string from UTF-8 bytes.
|
||||
fn decode_string(data: &[u8]) -> Result<String> {
|
||||
@@ -33,14 +32,6 @@ fn decode_protocol(data: &[u8]) -> Result<Protocol> {
|
||||
Protocol::from_u8(data[0])
|
||||
}
|
||||
|
||||
/// Decode a socket address from a UTF-8 string.
|
||||
fn decode_address(data: &[u8]) -> Result<SocketAddr> {
|
||||
let addr_str = decode_string(data)?;
|
||||
addr_str
|
||||
.parse()
|
||||
.map_err(|e| DeepLinkError::InvalidAddress(format!("{}: {}", e, addr_str)))
|
||||
}
|
||||
|
||||
/// TLV parser with stateful offset tracking.
|
||||
struct TlvParser<'a> {
|
||||
data: &'a [u8],
|
||||
@@ -99,7 +90,7 @@ pub fn decode_tlv_payload(payload: &[u8]) -> Result<DeepLinkConfig> {
|
||||
let mut parser = TlvParser::new(payload);
|
||||
|
||||
let mut hostname: Option<String> = None;
|
||||
let mut addresses: Vec<SocketAddr> = Vec::new();
|
||||
let mut addresses: Vec<String> = Vec::new();
|
||||
let mut username: Option<String> = None;
|
||||
let mut password: Option<String> = None;
|
||||
let mut custom_sni: Option<String> = None;
|
||||
@@ -124,7 +115,7 @@ pub fn decode_tlv_payload(payload: &[u8]) -> Result<DeepLinkConfig> {
|
||||
hostname = Some(decode_string(&value)?);
|
||||
}
|
||||
TlvTag::Address => {
|
||||
addresses.push(decode_address(&value)?);
|
||||
addresses.push(decode_string(&value)?);
|
||||
}
|
||||
TlvTag::CustomSni => {
|
||||
custom_sni = Some(decode_string(&value)?);
|
||||
@@ -239,11 +230,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_address() {
|
||||
let addr = decode_address(b"1.2.3.4:443").unwrap();
|
||||
assert_eq!(addr.to_string(), "1.2.3.4:443");
|
||||
fn test_decode_address_ip() {
|
||||
let addr = decode_string(b"1.2.3.4:443").unwrap();
|
||||
assert_eq!(addr, "1.2.3.4:443");
|
||||
}
|
||||
|
||||
assert!(decode_address(b"invalid").is_err());
|
||||
#[test]
|
||||
fn test_decode_address_domain() {
|
||||
let addr = decode_string(b"vpn.example.com:443").unwrap();
|
||||
assert_eq!(addr, "vpn.example.com:443");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn encode_tlv_payload(config: &DeepLinkConfig) -> Result<Vec<u8>> {
|
||||
payload.extend(encode_string_field(TlvTag::Password, &config.password)?);
|
||||
|
||||
for addr in &config.addresses {
|
||||
payload.extend(encode_string_field(TlvTag::Address, &addr.to_string())?);
|
||||
payload.extend(encode_string_field(TlvTag::Address, addr)?);
|
||||
}
|
||||
|
||||
// client_random_prefix: include if present and non-empty
|
||||
@@ -101,7 +101,6 @@ pub fn encode(config: &DeepLinkConfig) -> Result<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[test]
|
||||
fn test_encode_tlv() {
|
||||
@@ -155,7 +154,7 @@ mod tests {
|
||||
fn test_encode_tlv_payload_minimal() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse::<SocketAddr>().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("alice".to_string())
|
||||
.password("secret".to_string())
|
||||
.build()
|
||||
@@ -171,7 +170,7 @@ mod tests {
|
||||
fn test_encode_tlv_payload_with_optional_fields() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("alice".to_string())
|
||||
.password("secret".to_string())
|
||||
.custom_sni(Some("example.org".to_string()))
|
||||
@@ -192,7 +191,7 @@ mod tests {
|
||||
fn test_encode_full_uri() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("alice".to_string())
|
||||
.password("secret".to_string())
|
||||
.build()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::error::{DeepLinkError, Result};
|
||||
use std::fmt;
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// TLV tag identifiers (per DEEP_LINK.md specification)
|
||||
@@ -100,7 +99,7 @@ impl fmt::Display for Protocol {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DeepLinkConfig {
|
||||
pub hostname: String,
|
||||
pub addresses: Vec<SocketAddr>,
|
||||
pub addresses: Vec<String>,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub client_random_prefix: Option<String>,
|
||||
@@ -140,7 +139,7 @@ impl DeepLinkConfig {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DeepLinkConfigBuilder {
|
||||
hostname: Option<String>,
|
||||
addresses: Option<Vec<SocketAddr>>,
|
||||
addresses: Option<Vec<String>>,
|
||||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
client_random_prefix: Option<String>,
|
||||
@@ -158,7 +157,7 @@ impl DeepLinkConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn addresses(mut self, addresses: Vec<SocketAddr>) -> Self {
|
||||
pub fn addresses(mut self, addresses: Vec<String>) -> Self {
|
||||
self.addresses = Some(addresses);
|
||||
self
|
||||
}
|
||||
@@ -282,7 +281,7 @@ mod tests {
|
||||
fn test_builder_success() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("alice".to_string())
|
||||
.password("secret".to_string())
|
||||
.build()
|
||||
@@ -305,11 +304,23 @@ mod tests {
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_domain_address() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["vpn.example.com:443".to_string()])
|
||||
.username("alice".to_string())
|
||||
.password("secret".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(config.addresses[0], "vpn.example.com:443");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_empty_hostname() {
|
||||
let config = DeepLinkConfig {
|
||||
hostname: String::new(),
|
||||
addresses: vec!["1.2.3.4:443".parse().unwrap()],
|
||||
addresses: vec!["1.2.3.4:443".to_string()],
|
||||
username: "alice".to_string(),
|
||||
password: "secret".to_string(),
|
||||
custom_sni: None,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use proptest::prelude::*;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use trusttunnel_deeplink::{decode, encode, DeepLinkConfig, Protocol};
|
||||
|
||||
fn arbitrary_socket_addr() -> impl Strategy<Value = SocketAddr> {
|
||||
fn arbitrary_address_string() -> impl Strategy<Value = String> {
|
||||
prop_oneof![
|
||||
any::<Ipv4Addr>().prop_map(|ip| SocketAddr::new(IpAddr::V4(ip), 443)),
|
||||
any::<Ipv6Addr>().prop_map(|ip| SocketAddr::new(IpAddr::V6(ip), 443)),
|
||||
(any::<[u8; 4]>(), 1u16..=65535).prop_map(|(ip, port)| {
|
||||
format!("{}.{}.{}.{}:{}", ip[0], ip[1], ip[2], ip[3], port)
|
||||
}),
|
||||
"[a-z]{3,15}\\.[a-z]{2,10}\\.[a-z]{2,5}:[0-9]{2,5}",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,7 +21,7 @@ fn arbitrary_hex_string() -> impl Strategy<Value = Option<String>> {
|
||||
fn arbitrary_config() -> impl Strategy<Value = DeepLinkConfig> {
|
||||
(
|
||||
"[a-z]{3,20}\\.[a-z]{3,10}\\.[a-z]{2,5}",
|
||||
prop::collection::vec(arbitrary_socket_addr(), 1..5),
|
||||
prop::collection::vec(arbitrary_address_string(), 1..5),
|
||||
"[a-z0-9_]{3,20}",
|
||||
"[a-zA-Z0-9!@#$%]{8,30}",
|
||||
arbitrary_hex_string(),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::process::Command;
|
||||
use trusttunnel_deeplink::{decode, encode, DeepLinkConfig, Protocol};
|
||||
|
||||
@@ -82,7 +81,7 @@ password = "secret123"
|
||||
// Rust encode
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse::<SocketAddr>().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("alice".to_string())
|
||||
.password("secret123".to_string())
|
||||
.build()
|
||||
@@ -126,8 +125,8 @@ skip_verification = false
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("secure.vpn.example.com".to_string())
|
||||
.addresses(vec![
|
||||
"192.168.1.1:8443".parse::<SocketAddr>().unwrap(),
|
||||
"10.0.0.1:443".parse().unwrap(),
|
||||
"192.168.1.1:8443".to_string(),
|
||||
"10.0.0.1:443".to_string(),
|
||||
])
|
||||
.username("premium_user".to_string())
|
||||
.password("very_secret_password".to_string())
|
||||
@@ -189,10 +188,7 @@ upstream_protocol = "http2"
|
||||
// Verify fields
|
||||
assert_eq!(rust_config.hostname, "test.example.org");
|
||||
assert_eq!(rust_config.addresses.len(), 1);
|
||||
assert_eq!(
|
||||
rust_config.addresses[0],
|
||||
"203.0.113.1:9443".parse::<SocketAddr>().unwrap()
|
||||
);
|
||||
assert_eq!(rust_config.addresses[0], "203.0.113.1:9443");
|
||||
assert_eq!(rust_config.username, "testuser");
|
||||
assert_eq!(rust_config.password, "testpass");
|
||||
assert_eq!(rust_config.upstream_protocol, Protocol::Http2);
|
||||
@@ -213,7 +209,7 @@ client_random_prefix = "aabbccddee"
|
||||
// Rust encode
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("crp.example.com".to_string())
|
||||
.addresses(vec!["10.20.30.40:8443".parse::<SocketAddr>().unwrap()])
|
||||
.addresses(vec!["10.20.30.40:8443".to_string()])
|
||||
.username("testuser".to_string())
|
||||
.password("testpass".to_string())
|
||||
.client_random_prefix(Some("aabbccddee".to_string()))
|
||||
@@ -241,7 +237,7 @@ fn test_roundtrip_through_both_implementations() {
|
||||
// Start with Rust config
|
||||
let original_config = DeepLinkConfig::builder()
|
||||
.hostname("roundtrip.example.com".to_string())
|
||||
.addresses(vec!["198.51.100.1:443".parse::<SocketAddr>().unwrap()])
|
||||
.addresses(vec!["198.51.100.1:443".to_string()])
|
||||
.username("roundtrip_user".to_string())
|
||||
.password("roundtrip_pass".to_string())
|
||||
.custom_sni(Some("sni.example.com".to_string()))
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::net::SocketAddr;
|
||||
use trusttunnel_deeplink::{decode, encode, DeepLinkConfig, Protocol};
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_minimal_config() {
|
||||
let original = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse::<SocketAddr>().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("alice".to_string())
|
||||
.password("secret123".to_string())
|
||||
.build()
|
||||
@@ -30,8 +29,8 @@ fn test_roundtrip_maximal_config() {
|
||||
let original = DeepLinkConfig::builder()
|
||||
.hostname("secure.vpn.example.com".to_string())
|
||||
.addresses(vec![
|
||||
"192.168.1.1:8443".parse().unwrap(),
|
||||
"10.0.0.1:443".parse().unwrap(),
|
||||
"192.168.1.1:8443".to_string(),
|
||||
"10.0.0.1:443".to_string(),
|
||||
])
|
||||
.username("premium_user".to_string())
|
||||
.password("very_secret_password_123".to_string())
|
||||
@@ -68,7 +67,7 @@ fn test_roundtrip_with_certificate() {
|
||||
|
||||
let original = DeepLinkConfig::builder()
|
||||
.hostname("vpn.secure.com".to_string())
|
||||
.addresses(vec!["203.0.113.1:443".parse().unwrap()])
|
||||
.addresses(vec!["203.0.113.1:443".to_string()])
|
||||
.username("user".to_string())
|
||||
.password("pass".to_string())
|
||||
.certificate(Some(cert_der.clone()))
|
||||
@@ -85,7 +84,7 @@ fn test_roundtrip_with_certificate() {
|
||||
fn test_roundtrip_without_certificate() {
|
||||
let original = DeepLinkConfig::builder()
|
||||
.hostname("vpn.trusted.com".to_string())
|
||||
.addresses(vec!["198.51.100.1:443".parse().unwrap()])
|
||||
.addresses(vec!["198.51.100.1:443".to_string()])
|
||||
.username("user".to_string())
|
||||
.password("pass".to_string())
|
||||
.certificate(None)
|
||||
@@ -103,9 +102,9 @@ fn test_roundtrip_multiple_addresses() {
|
||||
let original = DeepLinkConfig::builder()
|
||||
.hostname("multi.vpn.com".to_string())
|
||||
.addresses(vec![
|
||||
"1.1.1.1:443".parse().unwrap(),
|
||||
"8.8.8.8:8443".parse().unwrap(),
|
||||
"9.9.9.9:9443".parse().unwrap(),
|
||||
"1.1.1.1:443".to_string(),
|
||||
"8.8.8.8:8443".to_string(),
|
||||
"9.9.9.9:9443".to_string(),
|
||||
])
|
||||
.username("multiaddr".to_string())
|
||||
.password("test123".to_string())
|
||||
@@ -126,7 +125,7 @@ fn test_roundtrip_long_values() {
|
||||
|
||||
let original = DeepLinkConfig::builder()
|
||||
.hostname(long_hostname.clone())
|
||||
.addresses(vec!["1.2.3.4:443".parse().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("user".to_string())
|
||||
.password(long_password.clone())
|
||||
.build()
|
||||
@@ -143,7 +142,7 @@ fn test_roundtrip_long_values() {
|
||||
fn test_roundtrip_special_characters() {
|
||||
let original = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("user@example.com".to_string())
|
||||
.password("p@ss!w0rd#123".to_string())
|
||||
.custom_sni(Some("cdn-123.example.org".to_string()))
|
||||
@@ -163,8 +162,8 @@ fn test_roundtrip_ipv6_addresses() {
|
||||
let original = DeepLinkConfig::builder()
|
||||
.hostname("vpn6.example.com".to_string())
|
||||
.addresses(vec![
|
||||
"[2001:db8::1]:443".parse().unwrap(),
|
||||
"[::1]:8443".parse().unwrap(),
|
||||
"[2001:db8::1]:443".to_string(),
|
||||
"[::1]:8443".to_string(),
|
||||
])
|
||||
.username("ipv6user".to_string())
|
||||
.password("ipv6pass".to_string())
|
||||
@@ -181,7 +180,7 @@ fn test_roundtrip_ipv6_addresses() {
|
||||
fn test_roundtrip_default_values_omitted() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("user".to_string())
|
||||
.password("pass".to_string())
|
||||
.has_ipv6(true) // default value
|
||||
@@ -205,7 +204,7 @@ fn test_roundtrip_default_values_omitted() {
|
||||
fn test_roundtrip_non_default_values() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("vpn.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("user".to_string())
|
||||
.password("pass".to_string())
|
||||
.has_ipv6(false) // non-default
|
||||
@@ -229,7 +228,7 @@ fn test_roundtrip_non_default_values() {
|
||||
fn test_roundtrip_with_client_random_prefix() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("crp.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse::<SocketAddr>().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("testuser".to_string())
|
||||
.password("testpass".to_string())
|
||||
.client_random_prefix(Some("aabbcc".to_string()))
|
||||
@@ -249,7 +248,7 @@ fn test_roundtrip_with_client_random_prefix() {
|
||||
fn test_roundtrip_without_client_random_prefix() {
|
||||
let config = DeepLinkConfig::builder()
|
||||
.hostname("nocrp.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse::<SocketAddr>().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("testuser".to_string())
|
||||
.password("testpass".to_string())
|
||||
.client_random_prefix(None)
|
||||
@@ -267,7 +266,7 @@ fn test_roundtrip_without_client_random_prefix() {
|
||||
fn test_invalid_hex_client_random_prefix() {
|
||||
let result = DeepLinkConfig::builder()
|
||||
.hostname("test.example.com".to_string())
|
||||
.addresses(vec!["1.2.3.4:443".parse::<SocketAddr>().unwrap()])
|
||||
.addresses(vec!["1.2.3.4:443".to_string()])
|
||||
.username("testuser".to_string())
|
||||
.password("testpass".to_string())
|
||||
.client_random_prefix(Some("notvalidhex".to_string()))
|
||||
|
||||
@@ -120,17 +120,10 @@ impl ClientConfig {
|
||||
.parse()
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
|
||||
|
||||
// Deep-link format only supports IP addresses, not domain names.
|
||||
let addresses: Vec<std::net::SocketAddr> = self
|
||||
.addresses
|
||||
.iter()
|
||||
.filter_map(|a| a.parse().ok())
|
||||
.collect();
|
||||
|
||||
// Build deep-link config
|
||||
let config = DeepLinkConfig {
|
||||
hostname: self.hostname.clone(),
|
||||
addresses,
|
||||
addresses: self.addresses.clone(),
|
||||
username: self.username.clone(),
|
||||
password: self.password.clone(),
|
||||
client_random_prefix: if self.client_random_prefix.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user