//! Local test server credentials only. Never accepts a real subscription on argv. use minivless::{ config, guardian, storage::Preferences, tunnel::{AppState, Phase}, vless, }; use std::{ io::{Read, Write}, net::{SocketAddr, TcpStream, UdpSocket}, path::{Path, PathBuf}, time::{Duration, Instant}, }; fn main() { let a: Vec = std::env::args().collect(); if a.get(1).map(String::as_str) == Some("--core-guardian") { std::process::exit(guardian::run(Path::new(&a[2]), Path::new(&a[3]))); } if a.get(1).map(String::as_str) == Some("--fetch") { let mut s = TcpStream::connect_timeout( &a[2].parse::().unwrap(), Duration::from_secs(4), ) .unwrap(); s.set_read_timeout(Some(Duration::from_secs(4))).unwrap(); s.write_all(b"GET / HTTP/1.0\r\nHost: test\r\n\r\n") .unwrap(); let mut text = String::new(); s.read_to_string(&mut text).unwrap(); println!("{}", text.split("\r\n\r\n").nth(1).unwrap()); return; } if a.get(1).map(String::as_str) == Some("--dns") { let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); socket .set_read_timeout(Some(Duration::from_secs(5))) .unwrap(); let query = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x04test\x07example\x00\x00\x01\x00\x01"; socket .send_to(query, a.get(2).map(String::as_str).unwrap_or("8.8.8.8:53")) .unwrap(); let mut reply = [0; 512]; let (n, _) = socket.recv_from(&mut reply).unwrap(); assert!(n >= 4); println!( "{}.{}.{}.{}", reply[n - 4], reply[n - 3], reply[n - 2], reply[n - 1] ); return; } if a.get(1).map(String::as_str) == Some("--udp") { let addr = a[2].parse::().unwrap(); let socket = UdpSocket::bind(if addr.is_ipv6() { "[::]:0" } else { "0.0.0.0:0" }) .unwrap(); socket .set_read_timeout(Some(Duration::from_secs(4))) .unwrap(); socket.send_to(b"peer", addr).unwrap(); let mut b = [0; 512]; let (n, _) = socket.recv_from(&mut b).unwrap(); println!("{}", String::from_utf8_lossy(&b[..n])); return; } if a.get(1).map(String::as_str) == Some("--emit-configs") { let dir = PathBuf::from(&a[2]); std::fs::create_dir_all(&dir).unwrap(); for (name,q) in [("plain",""),("tls","?security=tls&sni=example.com"),("reality","?security=reality&sni=example.com&pbk=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&sid=0123&fp=chrome&flow=xtls-rprx-vision"),("ws","?type=ws&security=tls&path=%2Fsocket&host=example.com")] { let v=vless::parse(&format!("vless://b831381d-6324-4d53-ad4f-8cda48b30811@192.0.2.2:2443{q}")).unwrap(); for split in [true,false] {let c=config::generate(&v,&["/usr/bin/curl".into()],split,1000);std::fs::write(dir.join(format!("{name}-{split}.json")),serde_json::to_vec_pretty(&c).unwrap()).unwrap();} } return; } assert_eq!(a.get(1).map(String::as_str), Some("--connect")); let state = AppState::new( a[3].clone().into(), Some(a[2].clone().into()), std::env::current_exe().unwrap(), ); let request = Preferences { // An ignored query value must not interfere with internal version parsing // even though that value is redacted from user-visible core logs. vless_url: "vless://b831381d-6324-4d53-ad4f-8cda48b30811@192.0.2.2:2443?remark=1.14".into(), applications: vec![a[4].clone()], split_tunneling: a[5] == "split", ..Default::default() }; state.connect(request).unwrap(); let until = Instant::now() + Duration::from_secs(20); while Instant::now() < until { let status = state.status(); if status.phase == Phase::Connected { println!("READY"); std::io::stdout().flush().unwrap(); break; } if status.phase == Phase::Error { eprintln!( "{}\n{}", status.error.unwrap_or_default(), state.logs().join("\n") ); std::process::exit(1); } std::thread::sleep(Duration::from_millis(50)); } if state.status().phase != Phase::Connected { state.shutdown(); panic!("startup timeout"); } let mut b = [0; 1]; let _ = std::io::stdin().read(&mut b); state.shutdown(); println!("STOPPED"); }