22//
33// SPDX-License-Identifier: Apache-2.0
44
5+ use std:: collections:: { BTreeMap , BTreeSet } ;
56use std:: fmt:: Debug ;
67use std:: sync:: atomic:: Ordering ;
78
@@ -39,8 +40,24 @@ impl AppAddress {
3940 }
4041}
4142
43+ /// find app by app id in current memory box
44+ fn select_app_address ( items : & [ Box < [ u8 ] > ] , known_apps : & BTreeMap < String , BTreeSet < String > > ) -> Result < AppAddress > {
45+ let mut fallback = None ;
46+ for data in items {
47+ if let Ok ( addr) = AppAddress :: parse ( data) {
48+ if known_apps. contains_key ( & addr. app_id ) {
49+ return Ok ( addr) ;
50+ }
51+ if fallback. is_none ( ) {
52+ fallback = Some ( addr) ;
53+ }
54+ }
55+ }
56+ fallback. context ( "no app address found in txt record" )
57+ }
58+
4259/// resolve app address by sni
43- async fn resolve_app_address ( prefix : & str , sni : & str , compat : bool ) -> Result < AppAddress > {
60+ async fn resolve_app_address ( prefix : & str , sni : & str , compat : bool , state : & Proxy ) -> Result < AppAddress > {
4461 let txt_domain = format ! ( "{prefix}.{sni}" ) ;
4562 let resolver = hickory_resolver:: AsyncResolver :: tokio_from_system_conf ( )
4663 . context ( "failed to create dns resolver" ) ?;
@@ -58,17 +75,16 @@ async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<Ap
5875 let Some ( txt_record) = lookup. iter ( ) . next ( ) else {
5976 continue ;
6077 } ;
61- let Some ( data) = txt_record. txt_data ( ) . first ( ) else {
62- continue ;
63- } ;
64- return AppAddress :: parse ( data)
65- . with_context ( || format ! ( "failed to parse app address for {sni}" ) ) ;
78+ let locked = state. lock ( ) ;
79+ if let Ok ( addr) = select_app_address ( txt_record. txt_data ( ) , & locked. state . apps ) {
80+ return Ok ( addr) ;
81+ }
6682 }
6783 } else if let Ok ( lookup) = resolver. txt_lookup ( txt_domain) . await {
6884 if let Some ( txt_record) = lookup. iter ( ) . next ( ) {
69- if let Some ( data ) = txt_record . txt_data ( ) . first ( ) {
70- return AppAddress :: parse ( data )
71- . with_context ( || format ! ( "failed to parse app address for {sni}" ) ) ;
85+ let locked = state . lock ( ) ;
86+ if let Ok ( addr ) = select_app_address ( txt_record . txt_data ( ) , & locked . state . apps ) {
87+ return Ok ( addr ) ;
7288 }
7389 }
7490 }
@@ -86,13 +102,11 @@ async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<Ap
86102 . iter ( )
87103 . next ( )
88104 . with_context ( || format ! ( "no txt record found for {sni} via {wildcard_domain}" ) ) ?;
89- let data = txt_record
90- . txt_data ( )
91- . first ( )
92- . with_context ( || format ! ( "no data in txt record for {sni} via {wildcard_domain}" ) ) ?;
93- return AppAddress :: parse ( data) . with_context ( || {
94- format ! ( "failed to parse app address for {sni} via {wildcard_domain}" )
95- } ) ;
105+ let locked = state. lock ( ) ;
106+ return select_app_address ( txt_record. txt_data ( ) , & locked. state . apps )
107+ . with_context ( || {
108+ format ! ( "failed to parse app address for {sni} via {wildcard_domain}" )
109+ } ) ;
96110 }
97111
98112 anyhow:: bail!( "failed to resolve app address for {sni}" ) ;
@@ -108,7 +122,7 @@ pub(crate) async fn proxy_with_sni(
108122 let ns_prefix = & state. config . proxy . app_address_ns_prefix ;
109123 let compat = state. config . proxy . app_address_ns_compat ;
110124 let dns_timeout = state. config . proxy . timeouts . dns_resolve ;
111- let addr = timeout ( dns_timeout, resolve_app_address ( ns_prefix, sni, compat) )
125+ let addr = timeout ( dns_timeout, resolve_app_address ( ns_prefix, sni, compat, & state ) )
112126 . await
113127 . with_context ( || format ! ( "DNS TXT resolve timeout for {sni}" ) ) ?
114128 . with_context ( || format ! ( "failed to resolve app address for {sni}" ) ) ?;
@@ -218,17 +232,65 @@ pub(crate) async fn proxy_to_app(
218232#[ cfg( test) ]
219233mod tests {
220234 use super :: * ;
235+ use crate :: {
236+ config:: { load_config_figment, Config , MutualConfig , TlsConfig } ,
237+ main_service:: ProxyOptions ,
238+ } ;
239+ use tempfile:: TempDir ;
240+
241+ fn boxed ( s : & [ u8 ] ) -> Box < [ u8 ] > {
242+ s. to_vec ( ) . into_boxed_slice ( )
243+ }
244+
245+ async fn create_test_proxy ( ) -> ( Proxy , TempDir ) {
246+ let figment = load_config_figment ( None ) ;
247+ let mut config = figment. focus ( "core" ) . extract :: < Config > ( ) . unwrap ( ) ;
248+ let temp_dir = TempDir :: new ( ) . expect ( "failed to create temp dir" ) ;
249+ config. sync . data_dir = temp_dir. path ( ) . to_string_lossy ( ) . to_string ( ) ;
250+ let proxy = Proxy :: new ( ProxyOptions {
251+ config,
252+ my_app_id : None ,
253+ tls_config : TlsConfig {
254+ certs : "" . to_string ( ) ,
255+ key : "" . to_string ( ) ,
256+ mutual : MutualConfig { ca_certs : "" . to_string ( ) } ,
257+ } ,
258+ } )
259+ . await
260+ . expect ( "failed to create proxy" ) ;
261+ ( proxy, temp_dir)
262+ }
221263
222264 #[ tokio:: test]
223265 async fn test_resolve_app_address ( ) {
266+ let ( state, _dir) = create_test_proxy ( ) . await ;
224267 let app_addr = resolve_app_address (
225268 "_dstack-app-address" ,
226269 "3327603e03f5bd1f830812ca4a789277fc31f577.app.dstack.org" ,
227270 false ,
271+ & state,
228272 )
229273 . await
230274 . unwrap ( ) ;
231275 assert_eq ! ( app_addr. app_id, "3327603e03f5bd1f830812ca4a789277fc31f577" ) ;
232276 assert_eq ! ( app_addr. port, 8090 ) ;
233277 }
278+
279+ #[ test]
280+ fn test_select_app_address_prefers_local ( ) {
281+ let items = vec ! [ boxed( b"aaaaaa:443" ) , boxed( b"bbbbbb:8080" ) ] ;
282+ let mut apps = BTreeMap :: new ( ) ;
283+ apps. insert ( "bbbbbb" . to_string ( ) , BTreeSet :: new ( ) ) ;
284+ let addr = select_app_address ( & items, & apps) . unwrap ( ) ;
285+ assert_eq ! ( addr. app_id, "bbbbbb" ) ;
286+ assert_eq ! ( addr. port, 8080 ) ;
287+ }
288+
289+ #[ test]
290+ fn test_select_app_address_fallback_when_none_local ( ) {
291+ let items = vec ! [ boxed( b"aaaaaa:443" ) , boxed( b"bbbbbb:8080" ) ] ;
292+ let addr = select_app_address ( & items, & BTreeMap :: new ( ) ) . unwrap ( ) ;
293+ assert_eq ! ( addr. app_id, "aaaaaa" ) ;
294+ assert_eq ! ( addr. port, 443 ) ;
295+ }
234296}
0 commit comments