Skip to main content

geph5_client/
lib.rs

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