//! 鹊桥 (Magpie) — a general-purpose WireGuard VPN client, in Rust.
//!
//! magpie genkey 生成一对密钥 (私钥 + 公钥)
//! magpie pubkey <私钥> 由私钥推导公钥
//! magpie sample 生成一份示例配置 magpie.conf
//! magpie up [配置文件] 启动隧道 (默认 magpie.conf, 需管理员权限)
mod config;
mod server;
mod tunnel;
use base64::{engine::general_purpose::STANDARD, Engine};
use boringtun::noise::{Tunn, TunnResult};
use boringtun::x25519::{PublicKey, StaticSecret};
const BANNER: &str = r#"
╔═══════════════════════════════════════╗
║ 鹊桥 Magpie · WireGuard VPN client ║
╚═══════════════════════════════════════╝
"#;
fn main() {
let args: Vec<String> = std::env::args().collect();
let cmd = args.get(1).map(|s| s.as_str()).unwrap_or("help");
let result = match cmd {
"genkey" => cmd_genkey(),
"pubkey" => cmd_pubkey(args.get(2)),
"sample" => cmd_sample(),
"selftest" => cmd_selftest(),
"testserver" => (|| -> Result<(), String> {
let usage = "用法: magpie testserver <服务器私钥> <客户端公钥> [监听地址]";
let sp = args.get(2).ok_or(usage)?;
let cp = args.get(3).ok_or(usage)?;
let listen = args.get(4).map(|s| s.as_str()).unwrap_or("0.0.0.0:51820");
server::run(sp, cp, listen)
})(),
"up" => cmd_up(args.get(2).map(|s| s.as_str()).unwrap_or("magpie.conf")),
"help" | "-h" | "--help" => {
print_help();
Ok(())
}
other => Err(format!("未知命令: `{other}`,运行 `magpie help` 查看用法")),
};
if let Err(e) = result {
eprintln!("\n[错误] {e}");
std::process::exit(1);
}
}
fn print_help() {
print!("{BANNER}");
println!(
"用法:
magpie genkey 生成一对密钥 (私钥 + 公钥)
magpie pubkey <私钥> 由私钥推导公钥
magpie sample 生成示例配置 magpie.conf 及一对新密钥
magpie up [配置文件] 启动隧道 (默认 magpie.conf, 需以管理员身份运行)
magpie selftest 本机自检: 内存里跑通一次握手+加解密 (无需服务器/管理员)
magpie help 显示本帮助
说明:
· 这是一个标准 WireGuard 客户端,可连接任何标准 WireGuard 服务器。
· 配置文件就是标准的 wg .conf 格式,能直接用别处导出的配置。
· Windows 上 `up` 需要管理员权限,且 wintun.dll 要与 magpie.exe 同目录。"
);
}
fn gen_keypair() -> (StaticSecret, PublicKey) {
let mut k = [0u8; 32];
getrandom::getrandom(&mut k).expect("系统随机源不可用");
let secret = StaticSecret::from(k);
let public = PublicKey::from(&secret);
(secret, public)
}
fn cmd_genkey() -> Result<(), String> {
let (secret, public) = gen_keypair();
println!("PrivateKey = {}", STANDARD.encode(secret.to_bytes()));
println!("PublicKey = {}", STANDARD.encode(public.as_bytes()));
Ok(())
}
fn cmd_pubkey(arg: Option<&String>) -> Result<(), String> {
let priv_b64 = arg.ok_or("用法: magpie pubkey <base64私钥>")?;
let bytes = STANDARD
.decode(priv_b64.trim())
.map_err(|e| format!("私钥 base64 解码失败: {e}"))?;
if bytes.len() != 32 {
return Err(format!("私钥长度 {} 字节,应为 32 字节", bytes.len()));
}
let mut k = [0u8; 32];
k.copy_from_slice(&bytes);
let secret = StaticSecret::from(k);
let public = PublicKey::from(&secret);
println!("{}", STANDARD.encode(public.as_bytes()));
Ok(())
}
fn cmd_sample() -> Result<(), String> {
let path = "magpie.conf";
if std::path::Path::new(path).exists() {
return Err(format!("{path} 已存在,先删除或改名以免覆盖。"));
}
let (secret, public) = gen_keypair();
let conf = format!(
"# 鹊桥 Magpie 示例配置 (标准 WireGuard 格式)
# 本机这对密钥已为你生成;把下面的【公钥】交给服务器管理员登记:
# 你的公钥: {pub}
#
# 然后把服务器给你的信息填到 [Peer] 里,再运行: magpie up
[Interface]
PrivateKey = {priv}
Address = 10.7.0.2/24 # 服务器分配给你的内网 IP
DNS = 1.1.1.1 # 可选
MTU = 1420
[Peer]
PublicKey = <把服务器公钥填到这里>
# PresharedKey = <可选,若服务器启用了 PSK>
Endpoint = vpn.example.com:51820 # 服务器地址:端口
AllowedIPs = 10.7.0.0/24 # 只走内网网段=分流; 改成 0.0.0.0/0=全局
PersistentKeepalive = 25
",
pub = STANDARD.encode(public.as_bytes()),
priv = STANDARD.encode(secret.to_bytes()),
);
std::fs::write(path, conf).map_err(|e| format!("写入 {path} 失败: {e}"))?;
println!("已生成示例配置: {path}");
println!("你的公钥 (交给服务器管理员): {}", STANDARD.encode(public.as_bytes()));
println!("\n编辑 {path} 填好 [Peer] 后,运行: magpie up");
Ok(())
}
/// In-memory end-to-end test of the WireGuard core: stand up two peers
/// (a "server" and a "client"), perform the Noise handshake, then push a
/// packet through and confirm it comes out the other side byte-identical.
/// No network, no admin, no real server required.
fn cmd_selftest() -> Result<(), String> {
print!("{BANNER}");
println!("自检: 在内存中建立 客户端 ⇄ 服务器 两端并跑通隧道...\n");
let (srv_priv, srv_pub) = gen_keypair();
let (cli_priv, cli_pub) = gen_keypair();
let mut client = Tunn::new(cli_priv, srv_pub, None, None, 0, None)
.map_err(|e| format!("client Tunn: {e}"))?;
let mut server = Tunn::new(srv_priv, cli_pub, None, None, 1, None)
.map_err(|e| format!("server Tunn: {e}"))?;
let mut a = vec![0u8; 65536]; // scratch buffers
let mut b = vec![0u8; 65536];
// 1) client -> handshake initiation -> server
let init = match client.format_handshake_initiation(&mut a, false) {
TunnResult::WriteToNetwork(p) => p.to_vec(),
_ => return Err("客户端未能生成握手发起包".into()),
};
println!(" [1] 客户端发出握手发起包 ({} 字节)", init.len());
// 2) server processes initiation -> handshake response -> client
let resp = match server.decapsulate(None, &init, &mut b) {
TunnResult::WriteToNetwork(p) => p.to_vec(),
_ => return Err("服务器未能生成握手响应包".into()),
};
println!(" [2] 服务器回应握手响应包 ({} 字节)", resp.len());
// 3) client processes response -> session established
match client.decapsulate(None, &resp, &mut a) {
TunnResult::WriteToNetwork(_) | TunnResult::Done => {}
TunnResult::Err(e) => return Err(format!("客户端处理握手响应失败: {e:?}")),
_ => {}
}
if client.time_since_last_handshake().is_none() {
return Err("握手未完成".into());
}
println!(" [3] 握手完成,会话密钥已协商 ✓");
// 4) push a fake IPv4 packet through the encrypted tunnel
let payload = b"hello over the magpie bridge -- \xe9\xb9\x8a\xe6\xa1\xa5";
let mut pkt = vec![0x45u8, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 10, 7, 0, 2, 10, 7, 0, 1];
pkt.extend_from_slice(payload);
let total = (pkt.len() as u16).to_be_bytes();
pkt[2] = total[0];
pkt[3] = total[1];
let cipher = match client.encapsulate(&pkt, &mut a) {
TunnResult::WriteToNetwork(p) => p.to_vec(),
other => return Err(format!("加密数据包失败: {other:?}")),
};
println!(
" [4] 客户端加密 {} 字节明文 -> {} 字节密文",
pkt.len(),
cipher.len()
);
let recovered = match server.decapsulate(None, &cipher, &mut b) {
TunnResult::WriteToTunnelV4(p, _) => p.to_vec(),
other => return Err(format!("服务器解密失败: {other:?}")),
};
println!(" [5] 服务器解密还原 {} 字节", recovered.len());
if recovered == pkt {
println!("\n✓ 自检通过:原始报文经加密隧道往返后逐字节一致。");
println!(" 说明 WireGuard 加密内核工作正常,可以连接真实服务器了。");
Ok(())
} else {
Err("还原后的数据与原始数据不一致!".into())
}
}
fn cmd_up(path: &str) -> Result<(), String> {
print!("{BANNER}");
let text = std::fs::read_to_string(path)
.map_err(|e| format!("读取配置 {path} 失败: {e}\n 先运行 `magpie sample` 生成模板。"))?;
let cfg = config::parse(&text).map_err(|e| format!("配置 {path} 解析失败: {e}"))?;
println!("● 配置加载成功: {path}");
println!(" 本机地址 : {}", join_nets(&cfg.addresses));
println!(" 对端 : {}", cfg.endpoint_host);
println!(" AllowedIPs: {}", join_nets(&cfg.allowed_ips));
println!();
tunnel::run(cfg)
}
fn join_nets(nets: &[ipnet::IpNet]) -> String {
nets.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(", ")
}