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