1use std::ffi::c_char;
2use std::ffi::c_int;
3use std::ffi::CStr;
4use std::io::Write;
5
6pub use broker::broker_client;
7pub use broker::BrokerSource;
8use bytes::Bytes;
9pub use client::Client;
10pub use client::{BridgeMode, BrokerKeys, Config};
11
12pub use get_dialer::ExitConstraint;
13use nanorpc::JrpcRequest;
14use nanorpc::RpcTransport;
15use once_cell::sync::OnceCell;
16
17mod auth;
18mod broker;
19mod bw_accounting;
20mod bw_token;
21mod china;
22mod client;
23mod client_inner;
24mod control_prot;
25mod database;
26mod device_metadata;
27mod http_proxy;
28mod litecopy;
29pub mod logging;
30
31mod get_dialer;
32mod pac;
33mod socks5;
34mod spoof_dns;
35mod stats;
36mod taskpool;
37mod traffcount;
38mod updates;
39mod vpn;
40
41static CLIENT: OnceCell<Client> = OnceCell::new();
44
45#[no_mangle]
46pub unsafe extern "C" fn start_client(cfg: *const c_char) -> libc::c_int {
47 let cfg_str = unsafe { CStr::from_ptr(cfg) }.to_str().unwrap();
48 let cfg: Config = serde_json::from_str(cfg_str).unwrap();
49
50 CLIENT.get_or_init(|| Client::start(cfg));
51
52 0
53}
54
55#[no_mangle]
56pub unsafe extern "C" fn daemon_rpc(
57 jrpc_req: *const c_char,
58 out_buf: *mut c_char,
59 out_buflen: c_int,
60) -> c_int {
61 let req_str = unsafe { CStr::from_ptr(jrpc_req) }.to_str().unwrap();
62 let jrpc: JrpcRequest = serde_json::from_str(req_str).unwrap();
63
64 if let Some(client) = CLIENT.get() {
65 let ctrl = client.control_client().0;
66 if let Ok(response) = smolscale::block_on(async move { ctrl.call_raw(jrpc).await }) {
67 let response_json = serde_json::to_string(&response).unwrap();
68 let response_c = std::ffi::CString::new(response_json).unwrap();
69 let bytes = response_c.as_bytes_with_nul();
70
71 unsafe { fill_buffer(out_buf, out_buflen, bytes) }
72 } else {
73 -2 }
75 } else {
76 -1 }
78}
79
80#[no_mangle]
81pub unsafe extern "C" fn send_pkt(pkt: *const c_char, pkt_len: c_int) -> c_int {
82 let slice: &'static [u8] = std::slice::from_raw_parts(pkt as *mut u8, pkt_len as usize);
83 if let Some(client) = CLIENT.get() {
84 if let Ok(_) = smol::future::block_on(client.send_vpn_packet(Bytes::copy_from_slice(slice)))
85 {
86 return 0;
87 }
88 }
89 -1
90}
91
92#[no_mangle]
93pub unsafe extern "C" fn recv_pkt(out_buf: *mut c_char, out_buflen: c_int) -> c_int {
94 if let Some(client) = CLIENT.get() {
95 if let Ok(pkt) = smol::future::block_on(client.recv_vpn_packet()) {
96 return fill_buffer(out_buf, out_buflen, &pkt);
97 }
98 }
99 -1
100}
101
102unsafe fn fill_buffer(buffer: *mut c_char, buflen: c_int, output: &[u8]) -> c_int {
103 let mut slice = std::slice::from_raw_parts_mut(buffer as *mut u8, buflen as usize);
104 if output.len() < slice.len() {
105 if slice.write_all(output).is_err() {
106 tracing::debug!("writing to buffer failed!");
107 -4
108 } else {
109 output.len() as c_int
110 }
111 } else {
112 tracing::debug!(" buffer not big enough!");
113 -3
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use smol::Timer;
120
121 use super::*;
122 use std::{
123 ffi::CString,
124 net::{Ipv4Addr, SocketAddr},
125 };
126
127 const CONTROL_ADDR: SocketAddr =
128 SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12222);
129
130 pub const PAC_ADDR: SocketAddr =
131 SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12223);
132
133 const SOCKS5_ADDR: SocketAddr =
134 SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9909);
135
136 pub const HTTP_ADDR: SocketAddr =
137 SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9910);
138
139 #[test]
140 fn test_clib() {
141 let cfg = super::Config {
142 socks5_listen: Some(SOCKS5_ADDR),
144 http_proxy_listen: Some(HTTP_ADDR),
145 control_listen: Some(CONTROL_ADDR),
146 exit_constraint: super::ExitConstraint::Auto,
147 bridge_mode: BridgeMode::Auto,
148 cache: None,
149 vpn_fd: None,
150 broker: Some(BrokerSource::Race(vec![
151 BrokerSource::Fronted {
152 front: "https://www.cdn77.com/".into(),
153 host: "1826209743.rsc.cdn77.org".into(),
154 override_dns: None,
155 },
156 BrokerSource::Fronted {
157 front: "https://vuejs.org/".into(),
158 host: "svitania-naidallszei-2.netlify.app".into(),
159 override_dns: None,
160 },
161 ])),
162 broker_keys: Some(BrokerKeys {
163 master: "88c1d2d4197bed815b01a22cadfc6c35aa246dddb553682037a118aebfaa3954".into(),
164 mizaru_free: "0558216cbab7a9c46f298f4c26e171add9af87d0694988b8a8fe52ee932aa754"
165 .into(),
166 mizaru_plus: "cf6f58868c6d9459b3a63bc2bd86165631b3e916bad7f62b578cd9614e0bcb3b"
167 .into(),
168 mizaru_bw: "".to_string(),
169 }),
170 vpn: false,
172 spoof_dns: false,
173 passthrough_china: false,
174 dry_run: false,
175 credentials: geph5_broker_protocol::Credential::Secret(String::new()),
176 sess_metadata: Default::default(),
177 task_limit: None,
178 pac_listen: Some(PAC_ADDR),
179 };
180 let cfg_str = CString::new(serde_json::to_string(&cfg).unwrap()).unwrap();
181 let cfg_ptr = cfg_str.as_ptr();
182
183 let start_client_ret = unsafe { start_client(cfg_ptr) };
184 assert!(start_client_ret == 0);
185
186 for _ in 0..2 {
188 let jrpc_req = JrpcRequest {
189 jsonrpc: "2.0".into(),
190 method: "user_info".into(),
191 params: [].into(),
192 id: nanorpc::JrpcId::Number(1),
193 };
194 let jrpc_req_str = CString::new(serde_json::to_string(&jrpc_req).unwrap()).unwrap();
195 let jrpc_req_ptr = jrpc_req_str.as_ptr();
196 let mut out_buf = vec![0; 1024 * 128]; let out_buf_ptr = out_buf.as_mut_ptr();
199
200 let rpc_ret = unsafe { daemon_rpc(jrpc_req_ptr, out_buf_ptr, out_buf.len() as _) };
201 println!("daemon_rpc retcode = {rpc_ret}");
202 assert!(rpc_ret >= 0);
203 let output = unsafe { CStr::from_ptr(out_buf_ptr) }.to_str().unwrap();
204 println!("daemon_rpc output = {output}");
205 smolscale::block_on(async { Timer::after(std::time::Duration::from_secs(1)).await });
206 }
207 }
208}