1use std::ffi::CStr;
2use std::ffi::c_char;
3use std::ffi::c_int;
4use std::io::Write;
5
6pub use broker::broker_client;
7pub use broker::{BrokerSource, TunneledBrokerSource};
8use bytes::Bytes;
9pub use client::Client;
10pub use client::{BrokerKeys, Config};
11pub use geph5_broker_protocol::ExitConstraint;
12use nanorpc::JrpcRequest;
13use nanorpc::RpcTransport;
14use once_cell::sync::OnceCell;
15
16mod auth;
17mod bound_dialer;
18mod broker;
19mod bw_accounting;
20mod bw_token;
21mod china;
22mod client;
23mod control_prot;
24mod database;
25mod device_metadata;
26mod dial_logging;
27mod http_proxy;
28mod litecopy;
29pub mod logging;
30mod session;
31
32mod get_dialer;
33mod pac;
34mod port_forward;
35mod route_cache;
36mod socks5;
37mod spoof_dns;
38mod stats;
39mod taskpool;
40mod timeout;
41mod traffcount;
42mod tunneled_http;
43mod updates;
44mod vpn;
45
46static CLIENT: OnceCell<Client> = OnceCell::new();
49
50#[unsafe(no_mangle)]
51pub unsafe extern "C" fn start_client(cfg: *const c_char, vpn_fd: c_int) -> libc::c_int {
52 let Ok(cfg_str) = unsafe { CStr::from_ptr(cfg) }.to_str() else {
55 return -5; };
57 let cfg: Config = match serde_json::from_str(cfg_str) {
58 Ok(cfg) => cfg,
59 Err(err) => {
60 tracing::error!(err = %err, "start_client got invalid config JSON");
61 return -6;
62 }
63 };
64
65 #[cfg(unix)]
66 let vpn_fd = if vpn_fd >= 0 { Some(vpn_fd) } else { None };
67 #[cfg(not(unix))]
68 let vpn_fd: Option<i32> = {
69 let _ = vpn_fd;
70 None
71 };
72
73 CLIENT.get_or_init(|| Client::start_with_vpn_fd(cfg, vpn_fd));
74
75 0
76}
77
78#[unsafe(no_mangle)]
79pub unsafe extern "C" fn daemon_rpc(
80 jrpc_req: *const c_char,
81 out_buf: *mut c_char,
82 out_buflen: c_int,
83) -> c_int {
84 let Ok(req_str) = unsafe { CStr::from_ptr(jrpc_req) }.to_str() else {
86 return -5; };
88 let jrpc: JrpcRequest = match serde_json::from_str(req_str) {
89 Ok(jrpc) => jrpc,
90 Err(err) => {
91 tracing::error!(err = %err, req = req_str, "daemon_rpc got invalid JSON-RPC");
92 return -6;
93 }
94 };
95
96 if let Some(client) = CLIENT.get() {
97 let ctrl = client.control_client().0;
98 if let Ok(response) = geph5_rt::block_on(async move { ctrl.call_raw(jrpc).await }) {
99 let Ok(response_json) = serde_json::to_string(&response) else {
100 return -7;
101 };
102 let Ok(response_c) = std::ffi::CString::new(response_json) else {
103 return -7;
104 };
105 let bytes = response_c.as_bytes_with_nul();
106
107 unsafe { fill_buffer(out_buf, out_buflen, bytes) }
108 } else {
109 -2 }
111 } else {
112 -1 }
114}
115
116#[unsafe(no_mangle)]
117pub unsafe extern "C" fn send_pkt(pkt: *const c_char, pkt_len: c_int) -> c_int {
118 let slice: &'static [u8] =
119 unsafe { std::slice::from_raw_parts(pkt as *mut u8, pkt_len as usize) };
120 if let Some(client) = CLIENT.get()
121 && let Ok(_) =
122 geph5_rt::block_on(client.send_vpn_packet(Bytes::copy_from_slice(slice)))
123 {
124 return 0;
125 }
126 -1
127}
128
129#[unsafe(no_mangle)]
130pub unsafe extern "C" fn recv_pkt(out_buf: *mut c_char, out_buflen: c_int) -> c_int {
131 if let Some(client) = CLIENT.get()
132 && let Ok(pkt) = geph5_rt::block_on(client.recv_vpn_packet())
133 {
134 return unsafe { fill_buffer(out_buf, out_buflen, &pkt) };
135 }
136 -1
137}
138
139unsafe fn fill_buffer(buffer: *mut c_char, buflen: c_int, output: &[u8]) -> c_int {
140 let mut slice = unsafe { std::slice::from_raw_parts_mut(buffer as *mut u8, buflen as usize) };
141 if output.len() < slice.len() {
142 if slice.write_all(output).is_err() {
143 tracing::debug!("writing to buffer failed!");
144 -4
145 } else {
146 output.len() as c_int
147 }
148 } else {
149 tracing::debug!(" buffer not big enough!");
150 -3
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use serde_json::json;
158 use std::{
159 ffi::CString,
160 net::{Ipv4Addr, SocketAddr},
161 };
162
163 const CONTROL_ADDR: SocketAddr =
164 SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12222);
165
166 pub const PAC_ADDR: SocketAddr =
167 SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12223);
168
169 const SOCKS5_ADDR: SocketAddr =
170 SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9909);
171
172 pub const HTTP_ADDR: SocketAddr =
173 SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9910);
174
175 #[test]
176 fn test_clib() {
177 let cfg = super::Config {
178 socks5_listen: Some(SOCKS5_ADDR),
180 http_proxy_listen: Some(HTTP_ADDR),
181 control_listen: Some(CONTROL_ADDR),
182 control_listen_unix: None,
183 control_listen_pipe: None,
184 exit_constraint: super::ExitConstraint::Auto,
185 allow_direct: false,
186 port_forward: vec![],
187 cache: None,
188 broker: Some(BrokerSource::Race(vec![
189 BrokerSource::Fronted {
190 front: "https://www.cdn77.com/".into(),
191 host: "1826209743.rsc.cdn77.org".into(),
192 override_dns: None,
193 },
194 BrokerSource::Fronted {
195 front: "https://vuejs.org/".into(),
196 host: "svitania-naidallszei-2.netlify.app".into(),
197 override_dns: None,
198 },
199 ])),
200 tunneled_broker: None,
201 broker_keys: Some(BrokerKeys {
202 master: "88c1d2d4197bed815b01a22cadfc6c35aa246dddb553682037a118aebfaa3954".into(),
203 mizaru_free: "0558216cbab7a9c46f298f4c26e171add9af87d0694988b8a8fe52ee932aa754"
204 .into(),
205 mizaru_plus: "cf6f58868c6d9459b3a63bc2bd86165631b3e916bad7f62b578cd9614e0bcb3b"
206 .into(),
207 mizaru_bw: "".to_string(),
208 }),
209 spoof_dns: false,
211 passthrough_china: false,
212 allow_lan: true,
213 dry_run: false,
214 credentials: geph5_broker_protocol::Credential::Secret(String::new()),
215 sess_metadata: Default::default(),
216 task_limit: None,
217 pac_listen: Some(PAC_ADDR),
218 };
219 let cfg_str = CString::new(serde_json::to_string(&cfg).unwrap()).unwrap();
220 let cfg_ptr = cfg_str.as_ptr();
221
222 let start_client_ret = unsafe { start_client(cfg_ptr, -1) };
223 assert!(start_client_ret == 0);
224
225 for _ in 0..2 {
227 let cred = geph5_broker_protocol::Credential::Secret(String::new());
228 let jrpc_req = JrpcRequest {
229 jsonrpc: "2.0".into(),
230 method: "broker_rpc".into(),
231 params: vec![
232 json!("get_user_info_by_cred"),
233 serde_json::to_value(vec![cred]).unwrap(),
234 ]
235 .into(),
236 id: nanorpc::JrpcId::Number(1),
237 };
238 let jrpc_req_str = CString::new(serde_json::to_string(&jrpc_req).unwrap()).unwrap();
239 let jrpc_req_ptr = jrpc_req_str.as_ptr();
240 let mut out_buf = vec![0; 1024 * 128]; let out_buf_ptr = out_buf.as_mut_ptr();
243
244 let rpc_ret = unsafe { daemon_rpc(jrpc_req_ptr, out_buf_ptr, out_buf.len() as _) };
245 println!("daemon_rpc retcode = {rpc_ret}");
246 assert!(rpc_ret >= 0);
247 let output = unsafe { CStr::from_ptr(out_buf_ptr) }.to_str().unwrap();
248 println!("daemon_rpc output = {output}");
249 let resp: nanorpc::JrpcResponse = serde_json::from_str(output).unwrap();
250 assert!(resp.error.is_none(), "daemon_rpc error: {:?}", resp.error);
251 geph5_rt::block_on(async {
252 tokio::time::sleep(std::time::Duration::from_secs(1)).await
253 });
254 }
255 }
256}