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