Skip to content

Commit ff53baa

Browse files
committed
prefer locally-known app_id when TXT record contains multiple app addresses
1 parent 603c6ee commit ff53baa

1 file changed

Lines changed: 103 additions & 2 deletions

File tree

gateway/src/proxy/tls_passthough.rs

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//
33
// SPDX-License-Identifier: BUSL-1.1
44

5+
use std::collections::{BTreeMap, BTreeSet};
56
use std::fmt::Debug;
67
use std::sync::atomic::Ordering;
78

@@ -34,8 +35,23 @@ impl AppAddress {
3435
}
3536
}
3637

38+
fn select_app_address(items: &[Box<[u8]>], known_apps: &BTreeMap<String, BTreeSet<String>>) -> Result<AppAddress> {
39+
let mut fallback = None;
40+
for data in items {
41+
if let Ok(addr) = AppAddress::parse(data) {
42+
if known_apps.contains_key(&addr.app_id) {
43+
return Ok(addr);
44+
}
45+
if fallback.is_none() {
46+
fallback = Some(addr);
47+
}
48+
}
49+
}
50+
fallback.context("no app address found in txt record")
51+
}
52+
3753
/// resolve app address by sni
38-
async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<AppAddress> {
54+
async fn resolve_app_address(prefix: &str, sni: &str, compat: bool, state: &Proxy) -> Result<AppAddress> {
3955
let txt_domain = format!("{prefix}.{sni}");
4056
let resolver = hickory_resolver::AsyncResolver::tokio_from_system_conf()
4157
.context("failed to create dns resolver")?;
@@ -53,6 +69,7 @@ async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<Ap
5369
let Some(txt_record) = lookup.iter().next() else {
5470
continue;
5571
};
72+
<<<<<<< HEAD
5673
let Some(data) = txt_record.txt_data().first() else {
5774
continue;
5875
};
@@ -90,6 +107,42 @@ async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<Ap
90107
});
91108
}
92109

110+
=======
111+
let locked = state.lock();
112+
if let Ok(addr) = select_app_address(txt_record.txt_data(), &locked.state.apps) {
113+
return Ok(addr);
114+
}
115+
}
116+
} else if let Ok(lookup) = resolver.txt_lookup(txt_domain).await {
117+
if let Some(txt_record) = lookup.iter().next() {
118+
let locked = state.lock();
119+
if let Ok(addr) = select_app_address(txt_record.txt_data(), &locked.state.apps) {
120+
return Ok(addr);
121+
}
122+
}
123+
}
124+
125+
// wildcard fallback: try {prefix}-wildcard.{parent_domain}
126+
if let Some((_, parent)) = sni.split_once('.') {
127+
let wildcard_domain = format!("{prefix}-wildcard.{parent}");
128+
let lookup = resolver
129+
.txt_lookup(&wildcard_domain)
130+
.await
131+
.with_context(|| {
132+
format!("failed to lookup wildcard app address for {sni} via {wildcard_domain}")
133+
})?;
134+
let txt_record = lookup
135+
.iter()
136+
.next()
137+
.with_context(|| format!("no txt record found for {sni} via {wildcard_domain}"))?;
138+
let locked = state.lock();
139+
return select_app_address(txt_record.txt_data(), &locked.state.apps)
140+
.with_context(|| {
141+
format!("failed to parse app address for {sni} via {wildcard_domain}")
142+
});
143+
}
144+
145+
>>>>>>> 8e3d6c27 (prefer locally-known app_id when TXT record contains multiple app addresses)
93146
anyhow::bail!("failed to resolve app address for {sni}");
94147
}
95148

@@ -102,7 +155,7 @@ pub(crate) async fn proxy_with_sni(
102155
let ns_prefix = &state.config.proxy.app_address_ns_prefix;
103156
let compat = state.config.proxy.app_address_ns_compat;
104157
let dns_timeout = state.config.proxy.timeouts.dns_resolve;
105-
let addr = timeout(dns_timeout, resolve_app_address(ns_prefix, sni, compat))
158+
let addr = timeout(dns_timeout, resolve_app_address(ns_prefix, sni, compat, &state))
106159
.await
107160
.with_context(|| format!("DNS TXT resolve timeout for {sni}"))?
108161
.with_context(|| format!("failed to resolve app address for {sni}"))?;
@@ -197,17 +250,65 @@ pub(crate) async fn proxy_to_app(
197250
#[cfg(test)]
198251
mod tests {
199252
use super::*;
253+
use crate::{
254+
config::{load_config_figment, Config, MutualConfig, TlsConfig},
255+
main_service::ProxyOptions,
256+
};
257+
use tempfile::TempDir;
258+
259+
fn boxed(s: &[u8]) -> Box<[u8]> {
260+
s.to_vec().into_boxed_slice()
261+
}
262+
263+
async fn create_test_proxy() -> (Proxy, TempDir) {
264+
let figment = load_config_figment(None);
265+
let mut config = figment.focus("core").extract::<Config>().unwrap();
266+
let temp_dir = TempDir::new().expect("failed to create temp dir");
267+
config.sync.data_dir = temp_dir.path().to_string_lossy().to_string();
268+
let proxy = Proxy::new(ProxyOptions {
269+
config,
270+
my_app_id: None,
271+
tls_config: TlsConfig {
272+
certs: "".to_string(),
273+
key: "".to_string(),
274+
mutual: MutualConfig { ca_certs: "".to_string() },
275+
},
276+
})
277+
.await
278+
.expect("failed to create proxy");
279+
(proxy, temp_dir)
280+
}
200281

201282
#[tokio::test]
202283
async fn test_resolve_app_address() {
284+
let (state, _dir) = create_test_proxy().await;
203285
let app_addr = resolve_app_address(
204286
"_dstack-app-address",
205287
"3327603e03f5bd1f830812ca4a789277fc31f577.app.dstack.org",
206288
false,
289+
&state,
207290
)
208291
.await
209292
.unwrap();
210293
assert_eq!(app_addr.app_id, "3327603e03f5bd1f830812ca4a789277fc31f577");
211294
assert_eq!(app_addr.port, 8090);
212295
}
296+
297+
#[test]
298+
fn test_select_app_address_prefers_local() {
299+
let items = vec![boxed(b"aaaaaa:443"), boxed(b"bbbbbb:8080")];
300+
let mut apps = BTreeMap::new();
301+
apps.insert("bbbbbb".to_string(), BTreeSet::new());
302+
let addr = select_app_address(&items, &apps).unwrap();
303+
assert_eq!(addr.app_id, "bbbbbb");
304+
assert_eq!(addr.port, 8080);
305+
}
306+
307+
#[test]
308+
fn test_select_app_address_fallback_when_none_local() {
309+
let items = vec![boxed(b"aaaaaa:443"), boxed(b"bbbbbb:8080")];
310+
let addr = select_app_address(&items, &BTreeMap::new()).unwrap();
311+
assert_eq!(addr.app_id, "aaaaaa");
312+
assert_eq!(addr.port, 443);
313+
}
213314
}

0 commit comments

Comments
 (0)