Skip to content

Commit 1c58db6

Browse files
committed
gw: Add flag p in sni
1 parent c889ee5 commit 1c58db6

File tree

2 files changed

+80
-67
lines changed

2 files changed

+80
-67
lines changed

gateway/src/proxy.rs

Lines changed: 74 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,79 @@ struct DstInfo {
7070
port: u16,
7171
is_tls: bool,
7272
is_h2: bool,
73+
is_pp: bool,
74+
}
75+
76+
fn parse_app_addr(addr: &str) -> Result<DstInfo> {
77+
let (app_id, port_part) = addr
78+
.rsplit_once('-')
79+
.or_else(|| addr.rsplit_once(':'))
80+
.unwrap_or((addr, ""));
81+
if app_id.is_empty() {
82+
bail!("app id is empty");
83+
}
84+
let mut dst = DstInfo {
85+
app_id: app_id.to_owned(),
86+
port: 80,
87+
is_tls: false,
88+
is_h2: false,
89+
is_pp: false,
90+
};
91+
92+
if port_part.is_empty() {
93+
return Ok(dst);
94+
};
95+
96+
// Parse suffixes from right to left: g, s, p
97+
let part_bytes = port_part.as_bytes();
98+
let mut end_idx = part_bytes.len();
99+
100+
// Parse from right to left until we hit a digit
101+
while end_idx > 0 {
102+
let ch = part_bytes[end_idx - 1] as char;
103+
match ch {
104+
c if c.is_ascii_digit() => {
105+
break;
106+
}
107+
'g' => {
108+
if dst.is_h2 {
109+
bail!("invalid app address: duplicate suffix 'g'");
110+
}
111+
dst.is_h2 = true;
112+
end_idx -= 1;
113+
}
114+
's' => {
115+
if dst.is_tls {
116+
bail!("invalid app address: duplicate suffix 's'");
117+
}
118+
dst.is_tls = true;
119+
end_idx -= 1;
120+
}
121+
'p' => {
122+
if dst.is_pp {
123+
bail!("invalid app address: duplicate suffix 'p'");
124+
}
125+
dst.is_pp = true;
126+
end_idx -= 1;
127+
}
128+
_ => {
129+
bail!("invalid app address: unrecognized suffix character '{ch}'");
130+
}
131+
}
132+
}
133+
134+
if dst.is_h2 && dst.is_tls {
135+
bail!("invalid app address: both 's' and 'g' suffixes are present");
136+
}
137+
138+
let port_str = &port_part[..end_idx];
139+
let port = if port_str.is_empty() {
140+
None
141+
} else {
142+
Some(port_str.parse::<u16>().context("invalid port")?)
143+
};
144+
dst.port = port.unwrap_or(if dst.is_tls { 443 } else { 80 });
145+
Ok(dst)
73146
}
74147

75148
fn parse_destination(sni: &str, dotted_base_domain: &str) -> Result<DstInfo> {
@@ -80,53 +153,7 @@ fn parse_destination(sni: &str, dotted_base_domain: &str) -> Result<DstInfo> {
80153
if subdomain.contains('.') {
81154
bail!("only one level of subdomain is supported, got sni={sni}, subdomain={subdomain}");
82155
}
83-
let mut parts = subdomain.split('-');
84-
let app_id = parts.next().context("no app id found")?.to_owned();
85-
if app_id.is_empty() {
86-
bail!("app id is empty");
87-
}
88-
let last_part = parts.next();
89-
let is_tls;
90-
let port;
91-
let is_h2;
92-
match last_part {
93-
None => {
94-
is_tls = false;
95-
is_h2 = false;
96-
port = None;
97-
}
98-
Some(last_part) => {
99-
let (port_str, has_g) = match last_part.strip_suffix('g') {
100-
Some(without_g) => (without_g, true),
101-
None => (last_part, false),
102-
};
103-
104-
let (port_str, has_s) = match port_str.strip_suffix('s') {
105-
Some(without_s) => (without_s, true),
106-
None => (port_str, false),
107-
};
108-
if has_g && has_s {
109-
bail!("invalid sni format: `gs` is not allowed");
110-
}
111-
is_h2 = has_g;
112-
is_tls = has_s;
113-
port = if port_str.is_empty() {
114-
None
115-
} else {
116-
Some(port_str.parse::<u16>().context("invalid port")?)
117-
};
118-
}
119-
};
120-
let port = port.unwrap_or(if is_tls { 443 } else { 80 });
121-
if parts.next().is_some() {
122-
bail!("invalid sni format");
123-
}
124-
Ok(DstInfo {
125-
app_id,
126-
port,
127-
is_tls,
128-
is_h2,
129-
})
156+
parse_app_addr(subdomain)
130157
}
131158

132159
pub static NUM_CONNECTIONS: AtomicU64 = AtomicU64::new(0);

gateway/src/proxy/tls_passthough.rs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,23 @@
33
// SPDX-License-Identifier: Apache-2.0
44

55
use anyhow::{Context, Result};
6-
use std::fmt::Debug;
76
use tokio::{io::AsyncWriteExt, net::TcpStream, task::JoinSet, time::timeout};
87
use tracing::{debug, info};
98

109
use crate::{
1110
main_service::Proxy,
1211
models::{Counting, EnteredCounter},
12+
proxy::{parse_app_addr, DstInfo},
1313
};
1414

1515
use super::{io_bridge::bridge, AddressGroup};
1616

17-
#[derive(Debug)]
18-
struct AppAddress {
19-
app_id: String,
20-
port: u16,
21-
}
22-
23-
impl AppAddress {
24-
fn parse(data: &[u8]) -> Result<Self> {
25-
// format: "3327603e03f5bd1f830812ca4a789277fc31f577:555"
26-
let data = String::from_utf8(data.to_vec()).context("invalid app address")?;
27-
let (app_id, port) = data.split_once(':').context("invalid app address")?;
28-
Ok(Self {
29-
app_id: app_id.to_string(),
30-
port: port.parse().context("invalid port")?,
31-
})
32-
}
17+
fn parse_txt_addr(addr: &[u8]) -> Result<DstInfo> {
18+
parse_app_addr(core::str::from_utf8(addr).context("invalid app address")?)
3319
}
3420

3521
/// resolve app address by sni
36-
async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<AppAddress> {
22+
async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<DstInfo> {
3723
let txt_domain = format!("{prefix}.{sni}");
3824
let resolver = hickory_resolver::AsyncResolver::tokio_from_system_conf()
3925
.context("failed to create dns resolver")?;
@@ -54,7 +40,7 @@ async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<Ap
5440
let Some(data) = txt_record.txt_data().first() else {
5541
continue;
5642
};
57-
return AppAddress::parse(data).context("failed to parse app address");
43+
return parse_txt_addr(data).context("failed to parse app address");
5844
}
5945
anyhow::bail!("failed to resolve app address");
6046
} else {
@@ -67,7 +53,7 @@ async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<Ap
6753
.txt_data()
6854
.first()
6955
.context("no data in txt record")?;
70-
AppAddress::parse(data).context("failed to parse app address")
56+
parse_txt_addr(data).context("failed to parse app address")
7157
}
7258
}
7359

0 commit comments

Comments
 (0)