@@ -50,7 +50,7 @@ fn is_subdomain(sni: &str, base_domain: &str) -> bool {
50
50
51
51
struct DstInfo {
52
52
app_id : String ,
53
- port : Option < u16 > ,
53
+ port : u16 ,
54
54
is_tls : bool ,
55
55
}
56
56
@@ -62,23 +62,38 @@ fn parse_destination(sni: &str, dotted_base_domain: &str) -> Result<DstInfo> {
62
62
if subdomain. contains ( '.' ) {
63
63
bail ! ( "only one level of subdomain is supported" ) ;
64
64
}
65
+ let mut parts = subdomain. split ( '-' ) ;
66
+ let app_id = parts. next ( ) . context ( "no app id found" ) ?. to_owned ( ) ;
67
+ if app_id. is_empty ( ) {
68
+ bail ! ( "app id is empty" ) ;
69
+ }
70
+ let last_part = parts. next ( ) ;
65
71
let is_tls;
66
- let subdomain = match subdomain. strip_suffix ( "s" ) {
72
+ let port;
73
+ match last_part {
67
74
None => {
68
75
is_tls = false ;
69
- subdomain
76
+ port = None ;
70
77
}
71
- Some ( subdomain) => {
72
- is_tls = true ;
73
- subdomain
78
+ Some ( last_part) => {
79
+ let port_str = match last_part. strip_suffix ( 's' ) {
80
+ None => {
81
+ is_tls = false ;
82
+ last_part
83
+ }
84
+ Some ( last_part) => {
85
+ is_tls = true ;
86
+ last_part
87
+ }
88
+ } ;
89
+ port = if port_str. is_empty ( ) {
90
+ None
91
+ } else {
92
+ Some ( port_str. parse :: < u16 > ( ) . context ( "invalid port" ) ?)
93
+ } ;
74
94
}
75
95
} ;
76
- let mut parts = subdomain. split ( '-' ) ;
77
- let app_id = parts. next ( ) . context ( "no app id found" ) ?. to_owned ( ) ;
78
- let port = parts
79
- . next ( )
80
- . map ( |p| p. parse ( ) . context ( "invalid port" ) )
81
- . transpose ( ) ?;
96
+ let port = port. unwrap_or ( if is_tls { 443 } else { 80 } ) ;
82
97
if parts. next ( ) . is_some ( ) {
83
98
bail ! ( "invalid sni format" ) ;
84
99
}
@@ -106,15 +121,9 @@ async fn handle_connection(
106
121
if is_subdomain ( & sni, dotted_base_domain) {
107
122
let dst = parse_destination ( & sni, dotted_base_domain) ?;
108
123
if dst. is_tls {
109
- tls_passthough:: proxy_to_app (
110
- state,
111
- inbound,
112
- buffer,
113
- & dst. app_id ,
114
- dst. port . unwrap_or ( 443 ) ,
115
- )
116
- . await
117
- . with_context ( || format ! ( "error on connection {sni}" ) )
124
+ tls_passthough:: proxy_to_app ( state, inbound, buffer, & dst. app_id , dst. port )
125
+ . await
126
+ . with_context ( || format ! ( "error on connection {sni}" ) )
118
127
} else {
119
128
tls_terminate_proxy
120
129
. proxy ( inbound, buffer, & dst. app_id , dst. port )
@@ -200,7 +209,70 @@ pub fn start(config: ProxyConfig, app_state: Proxy) {
200
209
} ) ;
201
210
}
202
211
203
- // async fn connect_to_app(state: &AppState, app_id: &str, port: u16) -> Result<TcpStream> {
204
- // let host = state.lock().select_a_host(app_id).context(format!("tapp {app_id} not found"))?;
205
- // TcpStream::connect((host.ip, port))
206
- // }
212
+ #[ cfg( test) ]
213
+ mod tests {
214
+ use super :: * ;
215
+
216
+ #[ test]
217
+ fn test_parse_destination ( ) {
218
+ let base_domain = ".example.com" ;
219
+
220
+ // Test basic app_id only
221
+ let result = parse_destination ( "myapp.example.com" , base_domain) . unwrap ( ) ;
222
+ assert_eq ! ( result. app_id, "myapp" ) ;
223
+ assert_eq ! ( result. port, 80 ) ;
224
+ assert ! ( !result. is_tls) ;
225
+
226
+ // Test app_id with custom port
227
+ let result = parse_destination ( "myapp-8080.example.com" , base_domain) . unwrap ( ) ;
228
+ assert_eq ! ( result. app_id, "myapp" ) ;
229
+ assert_eq ! ( result. port, 8080 ) ;
230
+ assert ! ( !result. is_tls) ;
231
+
232
+ // Test app_id with TLS
233
+ let result = parse_destination ( "myapp-443s.example.com" , base_domain) . unwrap ( ) ;
234
+ assert_eq ! ( result. app_id, "myapp" ) ;
235
+ assert_eq ! ( result. port, 443 ) ;
236
+ assert ! ( result. is_tls) ;
237
+
238
+ // Test app_id with custom port and TLS
239
+ let result = parse_destination ( "myapp-8443s.example.com" , base_domain) . unwrap ( ) ;
240
+ assert_eq ! ( result. app_id, "myapp" ) ;
241
+ assert_eq ! ( result. port, 8443 ) ;
242
+ assert ! ( result. is_tls) ;
243
+
244
+ // Test default port but ends with s
245
+ let result = parse_destination ( "myapps.example.com" , base_domain) . unwrap ( ) ;
246
+ assert_eq ! ( result. app_id, "myapps" ) ;
247
+ assert_eq ! ( result. port, 80 ) ;
248
+ assert ! ( !result. is_tls) ;
249
+
250
+ // Test default port but ends with s in port part
251
+ let result = parse_destination ( "myapp-s.example.com" , base_domain) . unwrap ( ) ;
252
+ assert_eq ! ( result. app_id, "myapp" ) ;
253
+ assert_eq ! ( result. port, 443 ) ;
254
+ assert ! ( result. is_tls) ;
255
+ }
256
+
257
+ #[ test]
258
+ fn test_parse_destination_errors ( ) {
259
+ let base_domain = ".example.com" ;
260
+
261
+ // Test invalid domain suffix
262
+ assert ! ( parse_destination( "myapp.wrong.com" , base_domain) . is_err( ) ) ;
263
+
264
+ // Test multiple subdomains
265
+ assert ! ( parse_destination( "invalid.myapp.example.com" , base_domain) . is_err( ) ) ;
266
+
267
+ // Test invalid port format
268
+ assert ! ( parse_destination( "myapp-65536.example.com" , base_domain) . is_err( ) ) ;
269
+ assert ! ( parse_destination( "myapp-abc.example.com" , base_domain) . is_err( ) ) ;
270
+
271
+ // Test too many parts
272
+ assert ! ( parse_destination( "myapp-8080-extra.example.com" , base_domain) . is_err( ) ) ;
273
+
274
+ // Test empty app_id
275
+ assert ! ( parse_destination( "-8080.example.com" , base_domain) . is_err( ) ) ;
276
+ assert ! ( parse_destination( "myapp-8080ss.example.com" , base_domain) . is_err( ) ) ;
277
+ }
278
+ }
0 commit comments