Pull request 192: Rename server_display_name->name and dns_servers->dns_upstreams

Squashed commit of the following:

commit 66b133c3e7c18a9bbc2fdf8792268ad18d495b5d
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Tue Apr 7 18:47:13 2026 +0400

    Fix lint

commit 64a9b4862edcb10d9b4b3b38414ee70c03fc9725
Author: Andrey Yakushin <a.yakushin@adguard.com>
Date:   Tue Apr 7 18:44:49 2026 +0400

    Rename server_display_name->name and dns_servers->dns_upstreams
This commit is contained in:
Andrey Yakushin
2026-04-07 14:51:04 +00:00
parent 341043a2cd
commit 9e144fe4bc
12 changed files with 90 additions and 93 deletions

View File

@@ -5,7 +5,7 @@ endpoint configurations between devices and applications.
Status: version 1
- version 1: Added fields for version, server display name, and DNS servers.
- version 1: Added fields for version, server display name, and DNS upstreams.
- draft 2: Changed format to tt://? to use case-sensitive URL part (query) instead of case-insensitive (host)
- draft 1: Initial specification
@@ -92,7 +92,7 @@ in one or two bytes.
| `0x0A` | `anti_dpi` | Bool | 1 byte: `0x01` = true, `0x00` = false | no (default `false`) |
| `0x0B` | `client_random_prefix` | String | UTF-8 hex-encoded string in the following format: `prefix[/mask]` | no |
| `0x0C` | `name` | String | Human-readable server name for display in the client UI | no |
| `0x0D` | `dns_servers` | String[] | List of DNS server addresses (e.g. `"1.1.1.1"`, `"tls://dns.example.com"`, `"https://dns.example.com/dns-query"`) | no |
| `0x0D` | `dns_upstreams` | String[] | List of DNS upstream addresses (e.g. `"1.1.1.1"`, `"tls://dns.example.com"`, `"https://dns.example.com/dns-query"`) | no |
### Encoding Rules

View File

@@ -271,16 +271,16 @@ This outputs a `tt://?` deep-link URI that can be:
You can also provide additional options:
- `--name <display_name>`: Set a custom display name for the server in the client app.
- `--dns-server <dns_server>`: Specify a DNS server for the client. Can be an IP address
- `--dns-upstream <dns_upstream>`: Specify a DNS upstream for the client. Can be an IP address
or a secure DNS URI (e.g., `tls://1.1.1.1`, `https://dns.google/dns-query`).
This flag can be used multiple times to provide a list of DNS servers.
This flag can be used multiple times to provide a list of DNS upstreams.
Example with custom name and DNS servers:
Example with custom name and DNS upstreams:
```shell
./trusttunnel_endpoint vpn.toml hosts.toml -c <client_name> -a <address> \
--name "My Secure VPN" \
--dns-server 1.1.1.1 --dns-server tls://8.8.8.8
--dns-upstream 1.1.1.1 --dns-upstream tls://8.8.8.8
```
When `--generate-client-random-prefix` is used, the endpoint also appends an

View File

@@ -120,8 +120,8 @@ pub fn decode_tlv_payload(payload: &[u8]) -> Result<DeepLinkConfig> {
let mut upstream_protocol: Protocol = Protocol::Http2; // default
let mut anti_dpi: bool = false; // default
let mut client_random_prefix: Option<String> = None;
let mut server_display_name: Option<String> = None;
let mut dns_servers: Vec<String> = Vec::new();
let mut name: Option<String> = None;
let mut dns_upstreams: Vec<String> = Vec::new();
while let Some(field_result) = parser.next_field() {
let (tag_opt, value) = field_result?;
@@ -190,11 +190,11 @@ pub fn decode_tlv_payload(payload: &[u8]) -> Result<DeepLinkConfig> {
})?;
client_random_prefix = Some(prefix);
}
TlvTag::ServerDisplayName => {
server_display_name = Some(decode_string(&value)?);
TlvTag::Name => {
name = Some(decode_string(&value)?);
}
TlvTag::DnsServers => {
dns_servers = decode_string_array(&value)?;
TlvTag::DnsUpstreams => {
dns_upstreams = decode_string_array(&value)?;
}
}
}
@@ -219,8 +219,8 @@ pub fn decode_tlv_payload(payload: &[u8]) -> Result<DeepLinkConfig> {
certificate,
upstream_protocol,
anti_dpi,
server_display_name,
dns_servers,
name,
dns_upstreams,
};
config.validate()?;

View File

@@ -100,15 +100,15 @@ pub fn encode_tlv_payload(config: &DeepLinkConfig) -> Result<Vec<u8>> {
payload.extend(encode_protocol_field(config.upstream_protocol)?);
}
// server_display_name (optional)
if let Some(ref name) = config.server_display_name {
payload.extend(encode_string_field(TlvTag::ServerDisplayName, name)?);
// name (optional)
if let Some(ref name) = config.name {
payload.extend(encode_string_field(TlvTag::Name, name)?);
}
// dns_servers (optional, String[] encoding)
if !config.dns_servers.is_empty() {
let value = encode_string_array(&config.dns_servers)?;
payload.extend(encode_tlv(TlvTag::DnsServers, &value)?);
// dns_upstreams (optional, String[] encoding)
if !config.dns_upstreams.is_empty() {
let value = encode_string_array(&config.dns_upstreams)?;
payload.extend(encode_tlv(TlvTag::DnsUpstreams, &value)?);
}
Ok(payload)

View File

@@ -18,8 +18,8 @@ pub enum TlvTag {
UpstreamProtocol = 0x09,
AntiDpi = 0x0A,
ClientRandomPrefix = 0x0B,
ServerDisplayName = 0x0C,
DnsServers = 0x0D,
Name = 0x0C,
DnsUpstreams = 0x0D,
}
impl TlvTag {
@@ -41,8 +41,8 @@ impl TlvTag {
0x09 => Some(TlvTag::UpstreamProtocol),
0x0A => Some(TlvTag::AntiDpi),
0x0B => Some(TlvTag::ClientRandomPrefix),
0x0C => Some(TlvTag::ServerDisplayName),
0x0D => Some(TlvTag::DnsServers),
0x0C => Some(TlvTag::Name),
0x0D => Some(TlvTag::DnsUpstreams),
_ => None,
}
}
@@ -117,8 +117,8 @@ pub struct DeepLinkConfig {
pub certificate: Option<Vec<u8>>,
pub upstream_protocol: Protocol,
pub anti_dpi: bool,
pub server_display_name: Option<String>,
pub dns_servers: Vec<String>,
pub name: Option<String>,
pub dns_upstreams: Vec<String>,
}
impl DeepLinkConfig {
@@ -159,8 +159,8 @@ pub struct DeepLinkConfigBuilder {
certificate: Option<Vec<u8>>,
upstream_protocol: Option<Protocol>,
anti_dpi: Option<bool>,
server_display_name: Option<String>,
dns_servers: Option<Vec<String>>,
name: Option<String>,
dns_upstreams: Option<Vec<String>>,
}
impl DeepLinkConfigBuilder {
@@ -219,13 +219,13 @@ impl DeepLinkConfigBuilder {
self
}
pub fn server_display_name(mut self, server_display_name: Option<String>) -> Self {
self.server_display_name = server_display_name;
pub fn name(mut self, name: Option<String>) -> Self {
self.name = name;
self
}
pub fn dns_servers(mut self, dns_servers: Vec<String>) -> Self {
self.dns_servers = Some(dns_servers);
pub fn dns_upstreams(mut self, dns_upstreams: Vec<String>) -> Self {
self.dns_upstreams = Some(dns_upstreams);
self
}
@@ -262,8 +262,8 @@ impl DeepLinkConfigBuilder {
certificate: self.certificate,
upstream_protocol: self.upstream_protocol.unwrap_or_default(),
anti_dpi: self.anti_dpi.unwrap_or(false),
server_display_name: self.server_display_name,
dns_servers: self.dns_servers.unwrap_or_default(),
name: self.name,
dns_upstreams: self.dns_upstreams.unwrap_or_default(),
};
config.validate()?;
Ok(config)
@@ -355,8 +355,8 @@ mod tests {
upstream_protocol: Protocol::Http2,
anti_dpi: false,
client_random_prefix: None,
server_display_name: None,
dns_servers: vec![],
name: None,
dns_upstreams: vec![],
};
assert!(config.validate().is_err());

View File

@@ -50,8 +50,8 @@ fn arbitrary_config() -> impl Strategy<Value = DeepLinkConfig> {
upstream_protocol,
anti_dpi,
),
server_display_name,
dns_servers,
name,
dns_upstreams,
)| {
DeepLinkConfig {
hostname,
@@ -65,8 +65,8 @@ fn arbitrary_config() -> impl Strategy<Value = DeepLinkConfig> {
certificate,
upstream_protocol,
anti_dpi,
server_display_name,
dns_servers,
name,
dns_upstreams,
}
},
)
@@ -88,8 +88,8 @@ proptest! {
prop_assert_eq!(decoded.certificate, config.certificate);
prop_assert_eq!(decoded.upstream_protocol, config.upstream_protocol);
prop_assert_eq!(decoded.anti_dpi, config.anti_dpi);
prop_assert_eq!(decoded.server_display_name, config.server_display_name);
prop_assert_eq!(decoded.dns_servers, config.dns_servers);
prop_assert_eq!(decoded.name, config.name);
prop_assert_eq!(decoded.dns_upstreams, config.dns_upstreams);
}
#[test]

View File

@@ -281,14 +281,14 @@ fn test_roundtrip_through_both_implementations() {
}
#[test]
fn test_name_and_dns_servers_matches_python() {
fn test_name_and_dns_upstreams_matches_python() {
let toml = r#"
hostname = "vpn.example.com"
addresses = ["1.2.3.4:443"]
username = "alice"
password = "secret123"
name = "My Server"
dns_servers = ["1.1.1.1", "8.8.8.8"]
dns_upstreams = ["1.1.1.1", "8.8.8.8"]
"#;
let config = DeepLinkConfig::builder()
@@ -296,8 +296,8 @@ dns_servers = ["1.1.1.1", "8.8.8.8"]
.addresses(vec!["1.2.3.4:443".to_string()])
.username("alice".to_string())
.password("secret123".to_string())
.server_display_name(Some("My Server".to_string()))
.dns_servers(vec!["1.1.1.1".to_string(), "8.8.8.8".to_string()])
.name(Some("My Server".to_string()))
.dns_upstreams(vec!["1.1.1.1".to_string(), "8.8.8.8".to_string()])
.build()
.unwrap();
@@ -306,7 +306,7 @@ dns_servers = ["1.1.1.1", "8.8.8.8"]
assert_eq!(
rust_uri, python_uri,
"Rust and Python encoders produced different URIs for name/dns_servers"
"Rust and Python encoders produced different URIs for name/dns_upstreams"
);
let python_decoded = python_decode(&rust_uri);
@@ -315,7 +315,7 @@ dns_servers = ["1.1.1.1", "8.8.8.8"]
"Python decoder failed on name"
);
assert!(
python_decoded.contains("dns_servers"),
"Python decoder failed on dns_servers"
python_decoded.contains("dns_upstreams"),
"Python decoder failed on dns_upstreams"
);
}

View File

@@ -276,33 +276,30 @@ fn test_invalid_hex_client_random_prefix() {
}
#[test]
fn test_roundtrip_with_server_display_name() {
fn test_roundtrip_with_name() {
let config = DeepLinkConfig::builder()
.hostname("vpn.example.com".to_string())
.addresses(vec!["1.2.3.4:443".to_string()])
.username("user".to_string())
.password("pass".to_string())
.server_display_name(Some("My VPN Server".to_string()))
.name(Some("My VPN Server".to_string()))
.build()
.unwrap();
let uri = encode(&config).unwrap();
let decoded = decode(&uri).unwrap();
assert_eq!(
decoded.server_display_name,
Some("My VPN Server".to_string())
);
assert_eq!(decoded.name, Some("My VPN Server".to_string()));
}
#[test]
fn test_roundtrip_with_dns_servers() {
fn test_roundtrip_with_dns_upstreams() {
let config = DeepLinkConfig::builder()
.hostname("vpn.example.com".to_string())
.addresses(vec!["1.2.3.4:443".to_string()])
.username("user".to_string())
.password("pass".to_string())
.dns_servers(vec![
.dns_upstreams(vec![
"1.1.1.1".to_string(),
"tls://dns.example.com".to_string(),
"https://dns.example.com/dns-query".to_string(),
@@ -314,7 +311,7 @@ fn test_roundtrip_with_dns_servers() {
let decoded = decode(&uri).unwrap();
assert_eq!(
decoded.dns_servers,
decoded.dns_upstreams,
vec![
"1.1.1.1",
"tls://dns.example.com",
@@ -336,8 +333,8 @@ fn test_roundtrip_without_new_optional_fields() {
let uri = encode(&config).unwrap();
let decoded = decode(&uri).unwrap();
assert_eq!(decoded.server_display_name, None);
assert!(decoded.dns_servers.is_empty());
assert_eq!(decoded.name, None);
assert!(decoded.dns_upstreams.is_empty());
}
#[test]

View File

@@ -30,7 +30,7 @@ const PREFIX_PERCENT_PARAM_NAME: &str = "prefix_percent";
const PREFIX_MASK_PARAM_NAME: &str = "prefix_mask";
const FORMAT_PARAM_NAME: &str = "format";
const NAME_PARAM_NAME: &str = "name";
const DNS_SERVER_PARAM_NAME: &str = "dns_server";
const DNS_UPSTREAM_PARAM_NAME: &str = "dns_upstream";
const SENTRY_DSN_PARAM_NAME: &str = "sentry_dsn";
const THREADS_NUM_PARAM_NAME: &str = "threads_num";
const TRUSTTUNNEL_QR_URL: &str = "https://trusttunnel.org/qr.html";
@@ -178,12 +178,12 @@ fn main() {
.short('n')
.long("name")
.help("Human-readable server display name for the client configuration."),
clap::Arg::new(DNS_SERVER_PARAM_NAME)
clap::Arg::new(DNS_UPSTREAM_PARAM_NAME)
.action(clap::ArgAction::Append)
.requires(CLIENT_CONFIG_PARAM_NAME)
.short('d')
.long("dns-server")
.help("DNS server address to include in the client configuration. Can be specified multiple times."),
.long("dns-upstream")
.help("DNS upstream address to include in the client configuration. Can be specified multiple times."),
])
.disable_version_flag(true)
.get_matches();
@@ -433,8 +433,8 @@ fn main() {
}
let name = args.get_one::<String>(NAME_PARAM_NAME).cloned();
let dns_servers: Vec<String> = args
.get_many::<String>(DNS_SERVER_PARAM_NAME)
let dns_upstreams: Vec<String> = args
.get_many::<String>(DNS_UPSTREAM_PARAM_NAME)
.map(|vals| vals.cloned().collect())
.unwrap_or_default();
@@ -446,7 +446,7 @@ fn main() {
custom_sni,
client_random_prefix,
name,
dns_servers,
dns_upstreams,
);
let format = args

View File

@@ -16,7 +16,7 @@ pub fn build(
custom_sni: Option<String>,
client_random_prefix: Option<String>,
name: Option<String>,
dns_servers: Vec<String>,
dns_upstreams: Vec<String>,
) -> ClientConfig {
let user = username
.iter()
@@ -51,7 +51,7 @@ pub fn build(
upstream_protocol: "http2".into(),
anti_dpi: false,
name: name.unwrap_or_default(),
dns_servers,
dns_upstreams,
}
}
@@ -87,8 +87,8 @@ pub struct ClientConfig {
anti_dpi: bool,
/// Human-readable server display name
name: String,
/// DNS servers to use when connected to this endpoint
dns_servers: Vec<String>,
/// DNS upstreams to use when connected to this endpoint
dns_upstreams: Vec<String>,
}
impl ClientConfig {
@@ -113,9 +113,9 @@ impl ClientConfig {
if !self.name.is_empty() {
doc["name"] = value(&self.name);
}
if !self.dns_servers.is_empty() {
let vec = toml_edit::Array::from_iter(self.dns_servers.iter().map(|x| x.as_str()));
doc["dns_servers"] = value(vec);
if !self.dns_upstreams.is_empty() {
let vec = toml_edit::Array::from_iter(self.dns_upstreams.iter().map(|x| x.as_str()));
doc["dns_upstreams"] = value(vec);
}
doc.to_string()
}
@@ -161,12 +161,12 @@ impl ClientConfig {
certificate,
upstream_protocol,
anti_dpi: self.anti_dpi,
server_display_name: if self.name.is_empty() {
name: if self.name.is_empty() {
None
} else {
Some(self.name.clone())
},
dns_servers: self.dns_servers.clone(),
dns_upstreams: self.dns_upstreams.clone(),
};
trusttunnel_deeplink::encode(&config)
@@ -216,7 +216,7 @@ anti_dpi = false
name = ""
{}
dns_servers = []
dns_upstreams = []
"#,
ClientConfig::doc_hostname().to_toml_comment(),
ClientConfig::doc_addresses().to_toml_comment(),
@@ -230,7 +230,7 @@ dns_servers = []
ClientConfig::doc_upstream_protocol().to_toml_comment(),
ClientConfig::doc_anti_dpi().to_toml_comment(),
ClientConfig::doc_name().to_toml_comment(),
ClientConfig::doc_dns_servers().to_toml_comment(),
ClientConfig::doc_dns_upstreams().to_toml_comment(),
)
});
#[cfg(test)]
@@ -253,7 +253,7 @@ mod tests {
upstream_protocol: "http2".into(),
anti_dpi: false,
name: String::new(),
dns_servers: vec![],
dns_upstreams: vec![],
}
}
}

View File

@@ -83,8 +83,8 @@ TAG_CERTIFICATE = 0x08
TAG_UPSTREAM_PROTOCOL = 0x09
TAG_ANTI_DPI = 0x0A
TAG_CLIENT_RANDOM_PREFIX = 0x0B
TAG_SERVER_DISPLAY_NAME = 0x0C
TAG_DNS_SERVERS = 0x0D
TAG_NAME = 0x0C
TAG_DNS_UPSTREAMS = 0x0D
CURRENT_VERSION = 1
@@ -160,14 +160,14 @@ def encode_config(cfg: dict) -> bytes:
raise ValueError(f"unknown upstream_protocol: {proto}")
buf += tlv(TAG_UPSTREAM_PROTOCOL, bytes([PROTOCOL_MAP[proto]]))
# server_display_name (optional)
# name (optional)
if "name" in cfg and cfg["name"]:
buf += tlv(TAG_SERVER_DISPLAY_NAME, cfg["name"].encode())
buf += tlv(TAG_NAME, cfg["name"].encode())
# dns_servers (optional, String[] encoding)
dns = cfg.get("dns_servers")
# dns_upstreams (optional, String[] encoding)
dns = cfg.get("dns_upstreams")
if dns:
buf += tlv(TAG_DNS_SERVERS, encode_string_array(dns))
buf += tlv(TAG_DNS_UPSTREAMS, encode_string_array(dns))
return bytes(buf)

View File

@@ -55,8 +55,8 @@ TAG_CERTIFICATE = 0x08
TAG_UPSTREAM_PROTOCOL = 0x09
TAG_ANTI_DPI = 0x0A
TAG_CLIENT_RANDOM_PREFIX = 0x0B
TAG_SERVER_DISPLAY_NAME = 0x0C
TAG_DNS_SERVERS = 0x0D
TAG_NAME = 0x0C
TAG_DNS_UPSTREAMS = 0x0D
CURRENT_VERSION = 1
@@ -193,10 +193,10 @@ def decode_config(data: bytes) -> dict:
cfg["anti_dpi"] = value[0] != 0
elif tag == TAG_CLIENT_RANDOM_PREFIX:
cfg["client_random_prefix"] = value.decode()
elif tag == TAG_SERVER_DISPLAY_NAME:
elif tag == TAG_NAME:
cfg["name"] = value.decode()
elif tag == TAG_DNS_SERVERS:
cfg["dns_servers"] = _decode_string_array(value)
elif tag == TAG_DNS_UPSTREAMS:
cfg["dns_upstreams"] = _decode_string_array(value)
# Unknown tags are silently ignored per spec.
if addresses:
@@ -246,7 +246,7 @@ _FIELD_ORDER: list[tuple[str, str]] = [
("upstream_protocol", "Protocol to be used to communicate with the endpoint [http2, http3]"),
("anti_dpi", "Is anti-DPI measures should be enabled"),
("name", "Human-readable server display name"),
("dns_servers", "DNS servers to use when connected to this endpoint"),
("dns_upstreams", "DNS upstreams to use when connected to this endpoint"),
]