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