1use std::sync::Arc;
12
13use crate::balancer::Balancer;
14
15#[derive(Debug, PartialEq)]
17pub enum SniResult {
18 Found(String),
20 NotPresent,
23 Incomplete,
25}
26
27fn be16(buf: &[u8], p: usize) -> Option<usize> {
29 let hi = *buf.get(p)? as usize;
30 let lo = *buf.get(p + 1)? as usize;
31 Some((hi << 8) | lo)
32}
33
34fn be24(buf: &[u8], p: usize) -> Option<usize> {
36 let a = *buf.get(p)? as usize;
37 let b = *buf.get(p + 1)? as usize;
38 let c = *buf.get(p + 2)? as usize;
39 Some((a << 16) | (b << 8) | c)
40}
41
42const MAX_RECORD_LEN: usize = 16_640;
45
46pub fn extract_sni(buf: &[u8]) -> SniResult {
49 if buf.len() < 5 {
51 return SniResult::Incomplete;
52 }
53 if buf[0] != 0x16 {
54 return SniResult::NotPresent; }
56 if buf[1] != 0x03 {
57 return SniResult::NotPresent; }
59 let record_len = match be16(buf, 3) {
60 Some(n) if n <= MAX_RECORD_LEN => n,
61 Some(_) => return SniResult::NotPresent, None => return SniResult::Incomplete,
63 };
64 let record_end = (5 + record_len).min(buf.len());
65
66 if record_end < 9 {
68 return SniResult::Incomplete;
69 }
70 if buf[5] != 0x01 {
71 return SniResult::NotPresent; }
73 let hs_len = match be24(buf, 6) {
74 Some(n) => n,
75 None => return SniResult::Incomplete,
76 };
77 let hs_end = (9 + hs_len).min(record_end);
78
79 let mut p = 9;
81 p = match advance(p, 2 + 32, hs_end) {
83 Some(p) => p,
84 None => return SniResult::Incomplete,
85 };
86 p = match skip_vec(buf, p, 1, hs_end) {
88 Skip::Ok(p) => p,
89 Skip::Short => return SniResult::Incomplete,
90 };
91 p = match skip_vec(buf, p, 2, hs_end) {
93 Skip::Ok(p) => p,
94 Skip::Short => return SniResult::Incomplete,
95 };
96 p = match skip_vec(buf, p, 1, hs_end) {
98 Skip::Ok(p) => p,
99 Skip::Short => return SniResult::Incomplete,
100 };
101
102 if p == hs_end {
104 return SniResult::NotPresent; }
106 let ext_total = match be16(buf, p) {
107 Some(n) => n,
108 None => return SniResult::Incomplete,
109 };
110 p += 2;
111 let ext_end = (p + ext_total).min(hs_end);
112
113 while p + 4 <= ext_end {
115 let ext_type = match be16(buf, p) {
116 Some(n) => n,
117 None => return SniResult::Incomplete,
118 };
119 let ext_len = match be16(buf, p + 2) {
120 Some(n) => n,
121 None => return SniResult::Incomplete,
122 };
123 let body_start = p + 4;
124 let body_end = body_start + ext_len;
125 if body_end > buf.len() {
126 return SniResult::Incomplete; }
128 if body_end > ext_end {
129 return SniResult::NotPresent; }
131 if ext_type == 0x0000 {
132 return parse_server_name(buf, body_start, body_end);
133 }
134 p = body_end;
135 }
136
137 SniResult::NotPresent
138}
139
140fn parse_server_name(buf: &[u8], start: usize, end: usize) -> SniResult {
142 let list_len = match be16(buf, start) {
144 Some(n) => n,
145 None => return SniResult::Incomplete,
146 };
147 let mut q = start + 2;
148 let list_end = q + list_len;
149 if list_end > buf.len() {
150 return SniResult::Incomplete;
151 }
152 if list_end > end {
153 return SniResult::NotPresent;
154 }
155
156 while q + 3 <= list_end {
158 let name_type = buf[q];
159 let name_len = match be16(buf, q + 1) {
160 Some(n) => n,
161 None => return SniResult::Incomplete,
162 };
163 q += 3;
164 let name_end = q + name_len;
165 if name_end > buf.len() {
166 return SniResult::Incomplete;
167 }
168 if name_end > list_end {
169 return SniResult::NotPresent;
170 }
171 if name_type == 0x00 {
172 return match std::str::from_utf8(&buf[q..name_end]) {
173 Ok(s) if !s.is_empty() => SniResult::Found(s.to_ascii_lowercase()),
174 _ => SniResult::NotPresent,
175 };
176 }
177 q = name_end; }
179
180 SniResult::NotPresent
181}
182
183fn advance(p: usize, n: usize, ceiling: usize) -> Option<usize> {
185 let next = p + n;
186 (next <= ceiling).then_some(next)
187}
188
189enum Skip {
190 Ok(usize),
191 Short,
192}
193
194fn skip_vec(buf: &[u8], p: usize, len_bytes: usize, ceiling: usize) -> Skip {
197 let len = match len_bytes {
198 1 => match buf.get(p) {
199 Some(&b) if p < ceiling => b as usize,
200 _ => return Skip::Short,
201 },
202 _ => match be16(buf, p) {
203 Some(n) if p + 2 <= ceiling => n,
204 _ => return Skip::Short,
205 },
206 };
207 match advance(p + len_bytes, len, ceiling) {
208 Some(next) => Skip::Ok(next),
209 None => Skip::Short,
210 }
211}
212
213#[derive(Debug)]
216pub(crate) enum SniPattern {
217 Exact(String),
218 Wildcard(String),
220}
221
222impl SniPattern {
223 pub(crate) fn parse(s: &str) -> Self {
224 let s = s.to_ascii_lowercase();
225 match s.strip_prefix("*.") {
226 Some(rest) => SniPattern::Wildcard(rest.to_string()),
227 None => SniPattern::Exact(s),
228 }
229 }
230
231 pub(crate) fn matches(&self, host: &str) -> bool {
232 let host = host.to_ascii_lowercase();
233 match self {
234 SniPattern::Exact(e) => *e == host,
235 SniPattern::Wildcard(base) => match host.strip_suffix(base.as_str()) {
236 Some(prefix) => {
239 prefix.ends_with('.')
240 && prefix.len() > 1
241 && !prefix[..prefix.len() - 1].contains('.')
242 }
243 None => false,
244 },
245 }
246 }
247}
248
249pub struct SniRouter {
252 routes: Vec<(SniPattern, Arc<Balancer>)>,
253 default: Arc<Balancer>,
254}
255
256impl SniRouter {
257 pub fn new(routes: Vec<(String, Arc<Balancer>)>, default: Arc<Balancer>) -> Self {
259 Self {
260 routes: routes
261 .into_iter()
262 .map(|(name, bal)| (SniPattern::parse(&name), bal))
263 .collect(),
264 default,
265 }
266 }
267
268 pub fn has_sni_routes(&self) -> bool {
270 !self.routes.is_empty()
271 }
272
273 pub fn select(&self, sni: Option<&str>) -> &Arc<Balancer> {
275 if let Some(host) = sni {
276 for (pattern, balancer) in &self.routes {
277 if pattern.matches(host) {
278 return balancer;
279 }
280 }
281 }
282 &self.default
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::balancer::{Strategy, Target};
290
291 fn build_client_hello(sni: &str) -> Vec<u8> {
293 build_client_hello_ext(Some(sni))
294 }
295
296 fn build_client_hello_ext(sni: Option<&str>) -> Vec<u8> {
298 let mut ext = Vec::new();
299 if let Some(sni) = sni {
300 let name = sni.as_bytes();
301 let mut sni_body = Vec::new();
302 let entry_len = 1 + 2 + name.len();
303 sni_body.extend_from_slice(&(entry_len as u16).to_be_bytes()); sni_body.push(0x00); sni_body.extend_from_slice(&(name.len() as u16).to_be_bytes());
306 sni_body.extend_from_slice(name);
307 ext.extend_from_slice(&0x0000u16.to_be_bytes()); ext.extend_from_slice(&(sni_body.len() as u16).to_be_bytes());
309 ext.extend_from_slice(&sni_body);
310 }
311
312 let mut body = Vec::new();
313 body.extend_from_slice(&[0x03, 0x03]); body.extend_from_slice(&[0u8; 32]); body.push(0x00); body.extend_from_slice(&2u16.to_be_bytes()); body.extend_from_slice(&[0x00, 0x2f]); body.push(0x01); body.push(0x00); body.extend_from_slice(&(ext.len() as u16).to_be_bytes()); body.extend_from_slice(&ext);
322
323 let mut hs = vec![0x01]; hs.extend_from_slice(&(body.len() as u32).to_be_bytes()[1..]); hs.extend_from_slice(&body);
326
327 let mut rec = vec![0x16, 0x03, 0x01];
328 rec.extend_from_slice(&(hs.len() as u16).to_be_bytes());
329 rec.extend_from_slice(&hs);
330 rec
331 }
332
333 #[test]
334 fn test_extract_sni_found() {
335 let hello = build_client_hello("example.com");
336 assert_eq!(extract_sni(&hello), SniResult::Found("example.com".into()));
337 }
338
339 #[test]
340 fn test_extract_sni_lowercases() {
341 let hello = build_client_hello("Example.COM");
342 assert_eq!(extract_sni(&hello), SniResult::Found("example.com".into()));
343 }
344
345 #[test]
346 fn test_extract_sni_no_extension() {
347 let hello = build_client_hello_ext(None);
348 assert_eq!(extract_sni(&hello), SniResult::NotPresent);
349 }
350
351 #[test]
352 fn test_extract_sni_not_tls() {
353 assert_eq!(
354 extract_sni(&[0x17, 0x03, 0x01, 0x00, 0x05]),
355 SniResult::NotPresent
356 );
357 assert_eq!(extract_sni(&[0xff; 20]), SniResult::NotPresent);
358 }
359
360 #[test]
361 fn test_extract_sni_empty_and_truncated() {
362 assert_eq!(extract_sni(&[]), SniResult::Incomplete);
363 assert_eq!(extract_sni(&[0x16, 0x03]), SniResult::Incomplete);
364 let hello = build_client_hello("example.com");
365 assert_eq!(
367 extract_sni(&hello[..hello.len() - 5]),
368 SniResult::Incomplete
369 );
370 }
371
372 #[test]
373 fn test_extract_sni_never_panics_on_any_truncation() {
374 let hello = build_client_hello("api.internal.example.com");
376 for n in 0..=hello.len() {
377 let _ = extract_sni(&hello[..n]);
378 }
379 assert_eq!(
381 extract_sni(&hello),
382 SniResult::Found("api.internal.example.com".into())
383 );
384 }
385
386 #[test]
387 fn test_extract_sni_absurd_record_len() {
388 let mut buf = vec![0x16, 0x03, 0x01];
390 buf.extend_from_slice(&60000u16.to_be_bytes());
391 buf.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]);
392 assert_eq!(extract_sni(&buf), SniResult::NotPresent);
393 }
394
395 #[test]
396 fn test_pattern_exact() {
397 let p = SniPattern::parse("api.example.com");
398 assert!(p.matches("api.example.com"));
399 assert!(p.matches("API.EXAMPLE.COM"));
400 assert!(!p.matches("x.example.com"));
401 assert!(!p.matches("example.com"));
402 }
403
404 #[test]
405 fn test_pattern_wildcard_one_label() {
406 let p = SniPattern::parse("*.example.com");
407 assert!(p.matches("a.example.com"));
408 assert!(p.matches("A.Example.com"));
409 assert!(!p.matches("example.com")); assert!(!p.matches("a.b.example.com")); assert!(!p.matches("xexample.com")); }
413
414 fn balancer(host: &str) -> Arc<Balancer> {
415 Arc::new(
416 Balancer::new(
417 vec![Target {
418 host: host.into(),
419 port: 443,
420 }],
421 Strategy::RoundRobin,
422 )
423 .unwrap(),
424 )
425 }
426
427 #[test]
428 fn test_router_select() {
429 let router = SniRouter::new(
430 vec![
431 ("a.example.com".into(), balancer("a")),
432 ("*.b.example.com".into(), balancer("b")),
433 ],
434 balancer("default"),
435 );
436 assert_eq!(router.select(Some("a.example.com")).target(0).host, "a");
437 assert_eq!(router.select(Some("x.b.example.com")).target(0).host, "b");
438 assert_eq!(
439 router.select(Some("unmatched.com")).target(0).host,
440 "default"
441 );
442 assert_eq!(router.select(None).target(0).host, "default");
443 assert!(router.has_sni_routes());
444 }
445}