@@ -34,43 +34,50 @@ impl AppAddress {
3434 }
3535}
3636
37- /// resolve app address by sni
37+ /// Resolve app address by sni, with wildcard domain fallback.
38+ ///
39+ /// Lookup order:
40+ /// 1. `{prefix}.{sni}` — exact match (e.g. `_dstack-app-address.foo.myapp.com`)
41+ /// 2. (compat) `_tapp-address.{sni}` — legacy exact match
42+ /// 3. `{prefix}-wildcard.{parent}` — wildcard match (e.g. `_dstack-app-address-wildcard.myapp.com`)
3843async fn resolve_app_address ( prefix : & str , sni : & str , compat : bool ) -> Result < AppAddress > {
39- let txt_domain = format ! ( "{prefix}.{sni}" ) ;
4044 let resolver = hickory_resolver:: AsyncResolver :: tokio_from_system_conf ( )
4145 . context ( "failed to create dns resolver" ) ?;
4246
47+ // 1. Try exact match
48+ let mut domains = vec ! [ format!( "{prefix}.{sni}" ) ] ;
4349 if compat && prefix != "_tapp-address" {
44- let txt_domain_legacy = format ! ( "_tapp-address.{sni}" ) ;
45- let ( lookup, lookup_legacy) = tokio:: join!(
46- resolver. txt_lookup( txt_domain) ,
47- resolver. txt_lookup( txt_domain_legacy) ,
48- ) ;
49- for lookup in [ lookup, lookup_legacy] {
50- let Ok ( lookup) = lookup else {
51- continue ;
52- } ;
53- let Some ( txt_record) = lookup. iter ( ) . next ( ) else {
54- continue ;
55- } ;
56- let Some ( data) = txt_record. txt_data ( ) . first ( ) else {
50+ domains. push ( format ! ( "_tapp-address.{sni}" ) ) ;
51+ }
52+
53+ // 2. Try wildcard fallback on parent domain
54+ if let Some ( ( _, parent) ) = sni. split_once ( '.' ) {
55+ domains. push ( format ! ( "{prefix}-wildcard.{parent}" ) ) ;
56+ }
57+
58+ for domain in & domains {
59+ let Ok ( lookup) = resolver. txt_lookup ( domain. as_str ( ) ) . await else {
60+ continue ;
61+ } ;
62+ let Some ( txt_record) = lookup. iter ( ) . next ( ) else {
63+ continue ;
64+ } ;
65+ let Some ( data) = txt_record. txt_data ( ) . first ( ) else {
66+ continue ;
67+ } ;
68+ match AppAddress :: parse ( data) {
69+ Ok ( addr) => {
70+ debug ! ( "resolved app address via TXT {domain}" ) ;
71+ return Ok ( addr) ;
72+ }
73+ Err ( e) => {
74+ warn ! ( "failed to parse app address from TXT {domain}: {e}" ) ;
5775 continue ;
58- } ;
59- return AppAddress :: parse ( data) . context ( "failed to parse app address" ) ;
76+ }
6077 }
61- anyhow:: bail!( "failed to resolve legacy app address" ) ;
62- } else {
63- let lookup = resolver
64- . txt_lookup ( txt_domain)
65- . await
66- . context ( "failed to lookup app address" ) ?;
67- let txt_record = lookup. iter ( ) . next ( ) . context ( "no txt record found" ) ?;
68- let data = txt_record
69- . txt_data ( )
70- . first ( )
71- . context ( "no data in txt record" ) ?;
72- AppAddress :: parse ( data) . context ( "failed to parse app address" )
7378 }
79+
80+ bail ! ( "failed to resolve app address for {sni} (tried: {})" , domains. join( ", " ) ) ;
7481}
7582
7683pub ( crate ) async fn proxy_with_sni (
0 commit comments