Files

122 lines
4.6 KiB
Rust

//! 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<String> = 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::<SocketAddr>().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::<SocketAddr>().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");
}