geph5_client/
lib.rs

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::{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 control_prot;
24mod database;
25mod device_metadata;
26mod http_proxy;
27mod litecopy;
28pub mod logging;
29mod session;
30
31mod get_dialer;
32mod pac;
33mod port_forward;
34mod socks5;
35mod spoof_dns;
36mod stats;
37mod taskpool;
38mod traffcount;
39mod updates;
40mod vpn;
41
42// C interface
43
44static CLIENT: OnceCell<Client> = OnceCell::new();
45
46#[unsafe(no_mangle)]
47pub unsafe extern "C" fn start_client(cfg: *const c_char) -> libc::c_int {
48    let cfg_str = unsafe { CStr::from_ptr(cfg) }.to_str().unwrap();
49    let cfg: Config = serde_json::from_str(cfg_str).unwrap();
50
51    CLIENT.get_or_init(|| Client::start(cfg));
52
53    0
54}
55
56#[unsafe(no_mangle)]
57pub unsafe extern "C" fn daemon_rpc(
58    jrpc_req: *const c_char,
59    out_buf: *mut c_char,
60    out_buflen: c_int,
61) -> c_int {
62    let req_str = unsafe { CStr::from_ptr(jrpc_req) }.to_str().unwrap();
63    let jrpc: JrpcRequest = serde_json::from_str(req_str).unwrap();
64
65    if let Some(client) = CLIENT.get() {
66        let ctrl = client.control_client().0;
67        if let Ok(response) = smolscale::block_on(async move { ctrl.call_raw(jrpc).await }) {
68            let response_json = serde_json::to_string(&response).unwrap();
69            let response_c = std::ffi::CString::new(response_json).unwrap();
70            let bytes = response_c.as_bytes_with_nul();
71
72            unsafe { fill_buffer(out_buf, out_buflen, bytes) }
73        } else {
74            -2 // jrpc error
75        }
76    } else {
77        -1 // daemon not started
78    }
79}
80
81#[unsafe(no_mangle)]
82pub unsafe extern "C" fn send_pkt(pkt: *const c_char, pkt_len: c_int) -> c_int {
83    let slice: &'static [u8] =
84        unsafe { std::slice::from_raw_parts(pkt as *mut u8, pkt_len as usize) };
85    if let Some(client) = CLIENT.get()
86        && let Ok(_) = smol::future::block_on(client.send_vpn_packet(Bytes::copy_from_slice(slice)))
87        {
88            return 0;
89        }
90    -1
91}
92
93#[unsafe(no_mangle)]
94pub unsafe extern "C" fn recv_pkt(out_buf: *mut c_char, out_buflen: c_int) -> c_int {
95    if let Some(client) = CLIENT.get()
96        && let Ok(pkt) = smol::future::block_on(client.recv_vpn_packet()) {
97            return unsafe { fill_buffer(out_buf, out_buflen, &pkt) };
98        }
99    -1
100}
101
102unsafe fn fill_buffer(buffer: *mut c_char, buflen: c_int, output: &[u8]) -> c_int {
103    let mut slice = unsafe { 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            // These fields are the base defaults:
143            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            allow_direct: false,
148            port_forward: vec![],
149            cache: None,
150            vpn_fd: None,
151            broker: Some(BrokerSource::Race(vec![
152                BrokerSource::Fronted {
153                    front: "https://www.cdn77.com/".into(),
154                    host: "1826209743.rsc.cdn77.org".into(),
155                    override_dns: None,
156                },
157                BrokerSource::Fronted {
158                    front: "https://vuejs.org/".into(),
159                    host: "svitania-naidallszei-2.netlify.app".into(),
160                    override_dns: None,
161                },
162            ])),
163            broker_keys: Some(BrokerKeys {
164                master: "88c1d2d4197bed815b01a22cadfc6c35aa246dddb553682037a118aebfaa3954".into(),
165                mizaru_free: "0558216cbab7a9c46f298f4c26e171add9af87d0694988b8a8fe52ee932aa754"
166                    .into(),
167                mizaru_plus: "cf6f58868c6d9459b3a63bc2bd86165631b3e916bad7f62b578cd9614e0bcb3b"
168                    .into(),
169                mizaru_bw: "".to_string(),
170            }),
171            // Values that can be overridden by `args`:
172            vpn: false,
173            spoof_dns: false,
174            passthrough_china: false,
175            dry_run: false,
176            credentials: geph5_broker_protocol::Credential::Secret(String::new()),
177            sess_metadata: Default::default(),
178            task_limit: None,
179            pac_listen: Some(PAC_ADDR),
180        };
181        let cfg_str = CString::new(serde_json::to_string(&cfg).unwrap()).unwrap();
182        let cfg_ptr = cfg_str.as_ptr();
183
184        let start_client_ret = unsafe { start_client(cfg_ptr) };
185        assert!(start_client_ret == 0);
186
187        // call daemon_rpc;
188        for _ in 0..2 {
189            let jrpc_req = JrpcRequest {
190                jsonrpc: "2.0".into(),
191                method: "user_info".into(),
192                params: [].into(),
193                id: nanorpc::JrpcId::Number(1),
194            };
195            let jrpc_req_str = CString::new(serde_json::to_string(&jrpc_req).unwrap()).unwrap();
196            let jrpc_req_ptr = jrpc_req_str.as_ptr();
197            // Allocate a buffer for the response
198            let mut out_buf = vec![0; 1024 * 128]; // Adjust size as needed
199            let out_buf_ptr = out_buf.as_mut_ptr();
200
201            let rpc_ret = unsafe { daemon_rpc(jrpc_req_ptr, out_buf_ptr, out_buf.len() as _) };
202            println!("daemon_rpc retcode = {rpc_ret}");
203            assert!(rpc_ret >= 0);
204            let output = unsafe { CStr::from_ptr(out_buf_ptr) }.to_str().unwrap();
205            println!("daemon_rpc output = {output}");
206            smolscale::block_on(async { Timer::after(std::time::Duration::from_secs(1)).await });
207        }
208    }
209}