1use crate::error::VCLError;
23use crate::keepalive::{KeepaliveConfig, KeepaliveManager, KeepaliveAction, KeepalivePreset};
24use crate::reconnect::{ReconnectConfig, ReconnectManager};
25use crate::dns::{DnsConfig, DnsFilter, DnsAction, DnsQueryType};
26use crate::obfuscation::{ObfuscationConfig, ObfuscationMode, Obfuscator, recommended_mode};
27use crate::mtu::{MtuConfig, MtuNegotiator};
28use crate::metrics::VCLMetrics;
29use std::net::IpAddr;
30use std::time::{Duration, Instant};
31use tracing::{debug, info, warn};
32
33#[derive(Debug, Clone)]
35pub struct TunnelConfig {
36 pub local_ip: String,
38 pub remote_ip: String,
40 pub mtu: u16,
42 pub obfuscation_mode: ObfuscationMode,
44 pub keepalive: KeepalivePreset,
46 pub dns_protection: bool,
48 pub dns_servers: Vec<String>,
50 pub blocked_domains: Vec<String>,
52 pub split_domains: Vec<String>,
54 pub max_reconnect_attempts: Option<u32>,
56}
57
58impl TunnelConfig {
59 pub fn mobile(local_ip: &str, remote_ip: &str) -> Self {
62 TunnelConfig {
63 local_ip: local_ip.to_string(),
64 remote_ip: remote_ip.to_string(),
65 mtu: 1380,
66 obfuscation_mode: ObfuscationMode::Full,
67 keepalive: KeepalivePreset::Mobile,
68 dns_protection: true,
69 dns_servers: vec![
70 "1.1.1.1:53".to_string(),
71 "1.0.0.1:53".to_string(),
72 ],
73 blocked_domains: Vec::new(),
74 split_domains: Vec::new(),
75 max_reconnect_attempts: Option::None,
76 }
77 }
78
79 pub fn home(local_ip: &str, remote_ip: &str) -> Self {
81 TunnelConfig {
82 local_ip: local_ip.to_string(),
83 remote_ip: remote_ip.to_string(),
84 mtu: 1420,
85 obfuscation_mode: ObfuscationMode::TlsMimicry,
86 keepalive: KeepalivePreset::Home,
87 dns_protection: true,
88 dns_servers: vec![
89 "1.1.1.1:53".to_string(),
90 ],
91 blocked_domains: Vec::new(),
92 split_domains: Vec::new(),
93 max_reconnect_attempts: Some(10),
94 }
95 }
96
97 pub fn corporate(local_ip: &str, remote_ip: &str) -> Self {
99 TunnelConfig {
100 local_ip: local_ip.to_string(),
101 remote_ip: remote_ip.to_string(),
102 mtu: 1400,
103 obfuscation_mode: ObfuscationMode::Http2Mimicry,
104 keepalive: KeepalivePreset::Corporate,
105 dns_protection: true,
106 dns_servers: vec![
107 "8.8.8.8:53".to_string(),
108 ],
109 blocked_domains: Vec::new(),
110 split_domains: Vec::new(),
111 max_reconnect_attempts: Some(5),
112 }
113 }
114
115 pub fn auto(local_ip: &str, remote_ip: &str, network_hint: &str) -> Self {
117 let mode = recommended_mode(network_hint);
118 let keepalive = match network_hint.to_lowercase().as_str() {
119 "mobile" | "mts" | "beeline" | "megafon" => KeepalivePreset::Mobile,
120 "corporate" | "office" => KeepalivePreset::Corporate,
121 _ => KeepalivePreset::Home,
122 };
123 TunnelConfig {
124 local_ip: local_ip.to_string(),
125 remote_ip: remote_ip.to_string(),
126 mtu: 1400,
127 obfuscation_mode: mode,
128 keepalive,
129 dns_protection: true,
130 dns_servers: vec!["1.1.1.1:53".to_string()],
131 blocked_domains: Vec::new(),
132 split_domains: Vec::new(),
133 max_reconnect_attempts: Option::None,
134 }
135 }
136
137 pub fn block_domain(mut self, domain: &str) -> Self {
139 self.blocked_domains.push(domain.to_string());
140 self
141 }
142
143 pub fn split_domain(mut self, domain: &str) -> Self {
145 self.split_domains.push(domain.to_string());
146 self
147 }
148
149 pub fn with_dns(mut self, servers: Vec<&str>) -> Self {
151 self.dns_servers = servers.iter().map(|s| s.to_string()).collect();
152 self
153 }
154}
155
156#[derive(Debug, Clone, PartialEq)]
158pub enum TunnelState {
159 Stopped,
161 Connecting,
163 Connected,
165 Reconnecting,
167 Failed,
169}
170
171#[derive(Debug, Clone)]
173pub struct TunnelStats {
174 pub state: TunnelState,
176 pub bytes_sent: u64,
178 pub bytes_received: u64,
180 pub loss_rate: f64,
182 pub keepalive_rtt: Option<Duration>,
184 pub reconnect_count: u64,
186 pub dns_intercepted: u64,
188 pub dns_blocked: u64,
190 pub obfuscation_overhead: f64,
192 pub uptime: Duration,
194 pub mtu: u16,
196}
197
198pub struct VCLTunnel {
203 config: TunnelConfig,
204 state: TunnelState,
205 keepalive: KeepaliveManager,
206 reconnect: ReconnectManager,
207 dns: DnsFilter,
208 obfuscator: Obfuscator,
209 mtu: MtuNegotiator,
210 metrics: VCLMetrics,
211 started_at: Option<Instant>,
212 reconnect_count: u64,
213}
214
215impl VCLTunnel {
216 pub fn new(config: TunnelConfig) -> Self {
218 let keepalive = KeepaliveManager::from_preset(config.keepalive.clone());
219
220 let reconnect_config = ReconnectConfig {
221 max_attempts: config.max_reconnect_attempts,
222 ..match config.keepalive {
223 KeepalivePreset::Mobile => ReconnectConfig::mobile(),
224 KeepalivePreset::Corporate => ReconnectConfig::stable(),
225 _ => ReconnectConfig::default(),
226 }
227 };
228 let reconnect = ReconnectManager::new(reconnect_config);
229
230 let mut dns_config = DnsConfig {
231 upstream_servers: config.dns_servers.clone(),
232 split_dns_domains: config.split_domains.clone(),
233 blocked_domains: config.blocked_domains.clone(),
234 enable_cache: true,
235 cache_ttl: Duration::from_secs(300),
236 max_cache_size: 1024,
237 };
238 let dns = DnsFilter::new(dns_config);
239
240 let obf_config = match &config.obfuscation_mode {
241 ObfuscationMode::None => ObfuscationConfig::none(),
242 ObfuscationMode::Padding => ObfuscationConfig::padding(),
243 ObfuscationMode::SizeNormalization => ObfuscationConfig::size_normalization(),
244 ObfuscationMode::TlsMimicry => ObfuscationConfig::tls_mimicry(),
245 ObfuscationMode::Http2Mimicry => ObfuscationConfig::http2_mimicry(),
246 ObfuscationMode::Full => ObfuscationConfig::full(),
247 };
248 let obfuscator = Obfuscator::new(obf_config);
249
250 let mtu_config = MtuConfig {
251 start_mtu: config.mtu as usize,
252 max_mtu: config.mtu as usize,
253 ..MtuConfig::default()
254 };
255 let mut mtu = MtuNegotiator::new(mtu_config);
256 mtu.set_mtu(config.mtu as usize);
257
258 info!(
259 local = %config.local_ip,
260 remote = %config.remote_ip,
261 mtu = config.mtu,
262 obfuscation = ?config.obfuscation_mode,
263 "VCLTunnel created"
264 );
265
266 VCLTunnel {
267 config,
268 state: TunnelState::Stopped,
269 keepalive,
270 reconnect,
271 dns,
272 obfuscator,
273 mtu,
274 metrics: VCLMetrics::new(),
275 started_at: Option::None,
276 reconnect_count: 0,
277 }
278 }
279
280 pub fn on_connecting(&mut self) {
284 self.state = TunnelState::Connecting;
285 self.started_at = Some(Instant::now());
286 info!(remote = %self.config.remote_ip, "VCLTunnel connecting");
287 }
288
289 pub fn on_connected(&mut self) {
291 self.state = TunnelState::Connected;
292 self.reconnect.on_connect();
293 self.metrics.record_handshake();
294 info!(remote = %self.config.remote_ip, "VCLTunnel connected");
295 }
296
297 pub fn on_disconnected(&mut self) {
299 self.state = TunnelState::Reconnecting;
300 self.reconnect.on_disconnect();
301 warn!(remote = %self.config.remote_ip, "VCLTunnel disconnected");
302 }
303
304 pub fn on_failed(&mut self) {
306 self.state = TunnelState::Failed;
307 warn!("VCLTunnel permanently failed");
308 }
309
310 pub fn stop(&mut self) {
312 self.state = TunnelState::Stopped;
313 info!("VCLTunnel stopped");
314 }
315
316 pub fn check_keepalive(&mut self) -> KeepaliveAction {
322 self.keepalive.check()
323 }
324
325 pub fn keepalive_sent(&mut self) {
327 self.keepalive.record_keepalive_sent();
328 }
329
330 pub fn keepalive_pong_received(&mut self) {
332 self.keepalive.record_pong_received();
333 if let Some(rtt) = self.keepalive.srtt() {
334 debug!(rtt_ms = rtt.as_millis(), "Keepalive RTT updated");
335 }
336 }
337
338 pub fn record_activity(&mut self) {
340 self.keepalive.record_activity();
341 }
342
343 pub fn should_reconnect(&mut self) -> bool {
347 self.reconnect.should_reconnect()
348 }
349
350 pub fn reconnect_attempt_start(&mut self) {
352 self.reconnect.on_attempt_start();
353 self.reconnect_count += 1;
354 info!(attempt = self.reconnect.attempts(), "Reconnect attempt starting");
355 }
356
357 pub fn reconnect_failed(&mut self) {
359 self.reconnect.on_failure();
360 if self.reconnect.is_giving_up() {
361 self.on_failed();
362 }
363 }
364
365 pub fn is_giving_up(&self) -> bool {
367 self.reconnect.is_giving_up()
368 }
369
370 pub fn time_until_reconnect(&self) -> Duration {
372 self.reconnect.time_until_reconnect()
373 }
374
375 pub fn obfuscate(&mut self, data: &[u8]) -> Vec<u8> {
379 let result = self.obfuscator.obfuscate(data);
380 self.metrics.record_sent(data.len());
381 result
382 }
383
384 pub fn deobfuscate(&mut self, data: &[u8]) -> Result<Vec<u8>, VCLError> {
386 let result = self.obfuscator.deobfuscate(data)?;
387 self.metrics.record_received(result.len());
388 Ok(result)
389 }
390
391 pub fn jitter_ms(&self) -> u64 {
393 self.obfuscator.jitter_ms()
394 }
395
396 pub fn dns_decide(&mut self, domain: &str) -> DnsAction {
400 self.dns.decide(domain, &DnsQueryType::A)
401 }
402
403 pub fn dns_cache(&mut self, domain: &str, addr: IpAddr) {
405 self.dns.cache_response(domain, addr);
406 }
407
408 pub fn is_dns_packet(data: &[u8]) -> bool {
410 DnsFilter::is_dns_packet(data)
411 }
412
413 pub fn block_domain(&mut self, domain: &str) {
415 self.dns.block_domain(domain);
416 }
417
418 pub fn add_split_domain(&mut self, domain: &str) {
420 self.dns.add_split_domain(domain);
421 }
422
423 pub fn fragment_size(&self) -> usize {
427 self.mtu.recommended_fragment_size()
428 }
429
430 pub fn current_mtu(&self) -> usize {
432 self.mtu.current_mtu()
433 }
434
435 pub fn set_mtu(&mut self, mtu: usize) {
437 self.mtu.set_mtu(mtu);
438 info!(mtu, "VCLTunnel MTU updated");
439 }
440
441 pub fn record_retransmit(&mut self) {
445 self.metrics.record_retransmit();
446 }
447
448 pub fn stats(&self) -> TunnelStats {
450 TunnelStats {
451 state: self.state.clone(),
452 bytes_sent: self.metrics.bytes_sent,
453 bytes_received: self.metrics.bytes_received,
454 loss_rate: self.metrics.loss_rate(),
455 keepalive_rtt: self.keepalive.srtt(),
456 reconnect_count: self.reconnect_count,
457 dns_intercepted: self.dns.total_intercepted(),
458 dns_blocked: self.dns.total_blocked(),
459 obfuscation_overhead: self.obfuscator.overhead_ratio(),
460 uptime: self.started_at.map(|t| t.elapsed()).unwrap_or(Duration::ZERO),
461 mtu: self.mtu.current_mtu() as u16,
462 }
463 }
464
465 pub fn state(&self) -> &TunnelState {
467 &self.state
468 }
469
470 pub fn is_connected(&self) -> bool {
472 self.state == TunnelState::Connected
473 }
474
475 pub fn config(&self) -> &TunnelConfig {
477 &self.config
478 }
479
480 pub fn metrics(&self) -> &VCLMetrics {
482 &self.metrics
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 fn mobile_tunnel() -> VCLTunnel {
491 VCLTunnel::new(TunnelConfig::mobile("10.0.0.1", "10.0.0.2"))
492 }
493
494 fn home_tunnel() -> VCLTunnel {
495 VCLTunnel::new(TunnelConfig::home("10.0.0.1", "10.0.0.2"))
496 }
497
498 fn corporate_tunnel() -> VCLTunnel {
499 VCLTunnel::new(TunnelConfig::corporate("10.0.0.1", "10.0.0.2"))
500 }
501
502 #[test]
505 fn test_mobile_config() {
506 let c = TunnelConfig::mobile("10.0.0.1", "10.0.0.2");
507 assert_eq!(c.local_ip, "10.0.0.1");
508 assert_eq!(c.remote_ip, "10.0.0.2");
509 assert_eq!(c.mtu, 1380);
510 assert_eq!(c.obfuscation_mode, ObfuscationMode::Full);
511 assert!(c.dns_protection);
512 assert!(c.max_reconnect_attempts.is_none());
513 }
514
515 #[test]
516 fn test_home_config() {
517 let c = TunnelConfig::home("10.0.0.1", "10.0.0.2");
518 assert_eq!(c.mtu, 1420);
519 assert_eq!(c.obfuscation_mode, ObfuscationMode::TlsMimicry);
520 assert_eq!(c.max_reconnect_attempts, Some(10));
521 }
522
523 #[test]
524 fn test_corporate_config() {
525 let c = TunnelConfig::corporate("10.0.0.1", "10.0.0.2");
526 assert_eq!(c.obfuscation_mode, ObfuscationMode::Http2Mimicry);
527 assert_eq!(c.max_reconnect_attempts, Some(5));
528 }
529
530 #[test]
531 fn test_auto_config_mobile() {
532 let c = TunnelConfig::auto("10.0.0.1", "10.0.0.2", "mts");
533 assert_eq!(c.obfuscation_mode, ObfuscationMode::Full);
534 }
535
536 #[test]
537 fn test_auto_config_home() {
538 let c = TunnelConfig::auto("10.0.0.1", "10.0.0.2", "home");
539 assert_eq!(c.obfuscation_mode, ObfuscationMode::TlsMimicry);
540 }
541
542 #[test]
543 fn test_config_block_domain() {
544 let c = TunnelConfig::mobile("10.0.0.1", "10.0.0.2")
545 .block_domain("ads.com")
546 .block_domain("tracking.io");
547 assert_eq!(c.blocked_domains.len(), 2);
548 }
549
550 #[test]
551 fn test_config_split_domain() {
552 let c = TunnelConfig::mobile("10.0.0.1", "10.0.0.2")
553 .split_domain("corp.internal");
554 assert_eq!(c.split_domains.len(), 1);
555 }
556
557 #[test]
558 fn test_config_with_dns() {
559 let c = TunnelConfig::mobile("10.0.0.1", "10.0.0.2")
560 .with_dns(vec!["8.8.8.8:53", "8.8.4.4:53"]);
561 assert_eq!(c.dns_servers.len(), 2);
562 }
563
564 #[test]
567 fn test_initial_state() {
568 let t = mobile_tunnel();
569 assert_eq!(t.state(), &TunnelState::Stopped);
570 assert!(!t.is_connected());
571 }
572
573 #[test]
574 fn test_on_connecting() {
575 let mut t = mobile_tunnel();
576 t.on_connecting();
577 assert_eq!(t.state(), &TunnelState::Connecting);
578 }
579
580 #[test]
581 fn test_on_connected() {
582 let mut t = mobile_tunnel();
583 t.on_connecting();
584 t.on_connected();
585 assert_eq!(t.state(), &TunnelState::Connected);
586 assert!(t.is_connected());
587 }
588
589 #[test]
590 fn test_on_disconnected() {
591 let mut t = mobile_tunnel();
592 t.on_connecting();
593 t.on_connected();
594 t.on_disconnected();
595 assert_eq!(t.state(), &TunnelState::Reconnecting);
596 assert!(!t.is_connected());
597 }
598
599 #[test]
600 fn test_stop() {
601 let mut t = mobile_tunnel();
602 t.on_connecting();
603 t.on_connected();
604 t.stop();
605 assert_eq!(t.state(), &TunnelState::Stopped);
606 }
607
608 #[test]
609 fn test_on_failed() {
610 let mut t = mobile_tunnel();
611 t.on_failed();
612 assert_eq!(t.state(), &TunnelState::Failed);
613 }
614
615 #[test]
618 fn test_obfuscate_deobfuscate() {
619 let mut t = mobile_tunnel();
620 let data = b"secret tunnel data";
621 let obfuscated = t.obfuscate(data);
622 let restored = t.deobfuscate(&obfuscated).unwrap();
623 assert_eq!(restored, data);
624 }
625
626 #[test]
627 fn test_obfuscate_records_metrics() {
628 let mut t = mobile_tunnel();
629 t.obfuscate(b"hello");
630 t.obfuscate(b"world");
631 assert_eq!(t.metrics().bytes_sent, 10);
632 }
633
634 #[test]
635 fn test_deobfuscate_records_metrics() {
636 let mut t = mobile_tunnel();
637 let obf = t.obfuscate(b"hello");
638 t.deobfuscate(&obf).unwrap();
639 assert_eq!(t.metrics().bytes_received, 5);
640 }
641
642 #[test]
643 fn test_jitter_ms() {
644 let t = mobile_tunnel();
645 assert!(t.jitter_ms() <= 15); }
647
648 #[test]
651 fn test_dns_forward() {
652 let mut t = home_tunnel();
653 let action = t.dns_decide("example.com");
654 assert_eq!(action, DnsAction::ForwardThroughTunnel);
655 }
656
657 #[test]
658 fn test_dns_block_runtime() {
659 let mut t = home_tunnel();
660 t.block_domain("evil.com");
661 let action = t.dns_decide("evil.com");
662 assert_eq!(action, DnsAction::Block);
663 }
664
665 #[test]
666 fn test_dns_block_from_config() {
667 let config = TunnelConfig::home("10.0.0.1", "10.0.0.2")
668 .block_domain("ads.com");
669 let mut t = VCLTunnel::new(config);
670 assert_eq!(t.dns_decide("ads.com"), DnsAction::Block);
671 }
672
673 #[test]
674 fn test_dns_split_from_config() {
675 let config = TunnelConfig::home("10.0.0.1", "10.0.0.2")
676 .split_domain("corp.internal");
677 let mut t = VCLTunnel::new(config);
678 assert_eq!(t.dns_decide("host.corp.internal"), DnsAction::AllowDirect);
679 }
680
681 #[test]
682 fn test_dns_cache() {
683 let mut t = home_tunnel();
684 let addr: IpAddr = "1.2.3.4".parse().unwrap();
685 t.dns_cache("cached.com", addr);
686 let action = t.dns_decide("cached.com");
687 assert_eq!(action, DnsAction::ReturnCached(addr));
688 }
689
690 #[test]
691 fn test_is_dns_packet() {
692 let pkt = vec![
693 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
694 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
695 ];
696 assert!(VCLTunnel::is_dns_packet(&pkt));
697 assert!(!VCLTunnel::is_dns_packet(&[0u8; 4]));
698 }
699
700 #[test]
703 fn test_mtu_initial() {
704 let t = mobile_tunnel();
705 assert_eq!(t.current_mtu(), 1380);
706 assert!(t.fragment_size() > 0);
707 assert!(t.fragment_size() < 1380);
708 }
709
710 #[test]
711 fn test_set_mtu() {
712 let mut t = home_tunnel();
713 t.set_mtu(1280);
714 assert_eq!(t.current_mtu(), 1280);
715 }
716
717 #[test]
720 fn test_stats_initial() {
721 let t = mobile_tunnel();
722 let s = t.stats();
723 assert_eq!(s.bytes_sent, 0);
724 assert_eq!(s.bytes_received, 0);
725 assert_eq!(s.reconnect_count, 0);
726 assert_eq!(s.dns_intercepted, 0);
727 assert_eq!(s.loss_rate, 0.0);
728 assert_eq!(s.mtu, 1380);
729 }
730
731 #[test]
732 fn test_stats_after_traffic() {
733 let mut t = mobile_tunnel();
734 t.on_connecting();
735 t.on_connected();
736 let obf = t.obfuscate(b"hello world");
737 t.deobfuscate(&obf).unwrap();
738 let s = t.stats();
739 assert_eq!(s.bytes_sent, 11);
740 assert_eq!(s.bytes_received, 11);
741 assert_eq!(s.state, TunnelState::Connected);
742 assert!(s.uptime > Duration::ZERO);
743 }
744
745 #[test]
746 fn test_stats_dns_counts() {
747 let mut t = home_tunnel();
748 t.block_domain("bad.com");
749 t.dns_decide("good.com");
750 t.dns_decide("bad.com");
751 let s = t.stats();
752 assert_eq!(s.dns_intercepted, 2);
753 assert_eq!(s.dns_blocked, 1);
754 }
755
756 #[test]
757 fn test_reconnect_count_increments() {
758 let mut t = mobile_tunnel();
759 t.on_connected();
760 t.on_disconnected();
761 t.reconnect_attempt_start();
762 t.reconnect_attempt_start();
763 assert_eq!(t.reconnect_count, 2);
764 }
765
766 #[test]
769 fn test_reconnect_after_disconnect() {
770 let mut t = VCLTunnel::new(TunnelConfig {
771 max_reconnect_attempts: Some(3),
772 ..TunnelConfig::home("10.0.0.1", "10.0.0.2")
773 });
774 t.on_connected();
775 t.on_disconnected();
776 assert!(!t.is_giving_up());
777 }
778
779 #[test]
780 fn test_giving_up_after_max_attempts() {
781 let mut t = VCLTunnel::new(TunnelConfig {
782 max_reconnect_attempts: Some(1),
783 ..TunnelConfig::home("10.0.0.1", "10.0.0.2")
784 });
785 t.on_connected();
786 t.on_disconnected();
787 t.reconnect_failed();
788 assert!(t.is_giving_up());
789 assert_eq!(t.state(), &TunnelState::Failed);
790 }
791
792 #[test]
793 fn test_config_ref() {
794 let t = mobile_tunnel();
795 assert_eq!(t.config().local_ip, "10.0.0.1");
796 assert_eq!(t.config().remote_ip, "10.0.0.2");
797 }
798
799 #[test]
800 fn test_all_three_presets_create_ok() {
801 let _m = mobile_tunnel();
802 let _h = home_tunnel();
803 let _c = corporate_tunnel();
804 }
805}