1use anyctx::AnyCtx;
2
3use anyhow::Context;
4use bytes::Bytes;
5use futures_concurrency::future::Race as _;
6use futures_util::{FutureExt, TryFutureExt, future::Shared, task::noop_waker};
7use geph5_broker_protocol::UserInfo;
8use geph5_misc_rpc::client_control::{ControlClient, ControlService};
9use geph5_rt::Immortal;
10use nanorpc::DynRpcTransport;
11use sillad::Pipe;
12use std::sync::Arc;
13#[cfg(unix)]
14use std::{
15 io::{Read, Write},
16 os::fd::{AsRawFd, FromRawFd},
17};
18#[cfg(unix)]
19use tokio::io::{Interest, unix::AsyncFd};
20
21use crate::{
22 auth::{auth_loop, get_auth_token},
23 broker::broker_client,
24 bw_token::bw_token_refresh_loop,
25 control_prot::{ControlProtocolImpl, DummyControlProtocolTransport},
26 http_proxy::http_proxy_serve,
27 logging,
28 pac::pac_serve,
29 port_forward::port_forward,
30 session::{open_conn, run_session},
31 socks5::socks5_loop,
32 vpn::{recv_vpn_packet, send_vpn_packet, vpn_loop},
33};
34
35pub use geph5_misc_rpc::client_config::{BrokerKeys, Config};
41
42#[derive(Clone)]
43pub struct Client {
44 task: Shared<geph5_rt::Task<Result<(), Arc<anyhow::Error>>>>,
45 ctx: AnyCtx<Config>,
46}
47
48impl Client {
49 pub fn start(cfg: Config) -> Self {
51 Self::start_with_vpn_fd(cfg, None)
52 }
53
54 #[cfg(unix)]
57 pub fn start_with_vpn_fd(cfg: Config, vpn_fd: Option<i32>) -> Self {
58 let ctx = AnyCtx::new(cfg.clone());
59 let _ = logging::init_logging(&ctx);
61 let ((fd_limit, _), _) = binary_search::binary_search((1, ()), (65536, ()), |lim| {
62 if rlimit::increase_nofile_limit(lim).unwrap_or_default() >= lim {
63 binary_search::Direction::Low(())
64 } else {
65 binary_search::Direction::High(())
66 }
67 });
68 tracing::info!("raised file descriptor limit to {}", fd_limit);
69
70 let client_ctx = ctx.clone();
71 let combined = async move {
77 let main_fut = client_main(ctx.clone());
78 match vpn_fd {
79 Some(fd) => (main_fut, run_vpn_fd_handler(ctx, fd)).race().await,
80 None => main_fut.await,
81 }
82 };
83 let task = geph5_rt::spawn(combined.map_err(Arc::new));
84 Client {
85 task: task.shared(),
86 ctx: client_ctx,
87 }
88 }
89
90 #[cfg(not(unix))]
92 pub fn start_with_vpn_fd(cfg: Config, _vpn_fd: Option<i32>) -> Self {
93 let ctx = AnyCtx::new(cfg.clone());
94 let _ = logging::init_logging(&ctx);
95 let ((fd_limit, _), _) = binary_search::binary_search((1, ()), (65536, ()), |lim| {
96 if rlimit::increase_nofile_limit(lim).unwrap_or_default() >= lim {
97 binary_search::Direction::Low(())
98 } else {
99 binary_search::Direction::High(())
100 }
101 });
102 tracing::info!("raised file descriptor limit to {}", fd_limit);
103
104 let task = geph5_rt::spawn(client_main(ctx.clone()).map_err(Arc::new));
105 Client {
106 task: task.shared(),
107 ctx,
108 }
109 }
110
111 pub async fn open_conn(&self, remote: &str) -> anyhow::Result<Box<dyn Pipe>> {
113 open_conn(&self.ctx, "tcp", remote).await
114 }
115
116 pub async fn wait_until_dead(self) -> anyhow::Result<()> {
118 self.task.await.map_err(|e| anyhow::anyhow!(e))
119 }
120
121 pub fn check_dead(&self) -> anyhow::Result<()> {
123 match self
124 .task
125 .clone()
126 .poll_unpin(&mut std::task::Context::from_waker(&noop_waker()))
127 {
128 std::task::Poll::Ready(val) => val.map_err(|e| anyhow::anyhow!(e))?,
129 std::task::Poll::Pending => {}
130 }
131
132 Ok(())
133 }
134
135 pub fn control_client(&self) -> ControlClient {
137 ControlClient(DynRpcTransport::new(DummyControlProtocolTransport(
138 ControlService(ControlProtocolImpl {
139 ctx: self.ctx.clone(),
140 }),
141 )))
142 }
143
144 pub async fn user_info(&self) -> anyhow::Result<UserInfo> {
146 let auth_token = get_auth_token(&self.ctx).await?;
147 let user_info = broker_client(&self.ctx)?
148 .get_user_info(auth_token)
149 .await??
150 .context("no such user")?;
151 Ok(user_info)
152 }
153
154 pub async fn send_vpn_packet(&self, bts: Bytes) -> anyhow::Result<()> {
156 send_vpn_packet(&self.ctx, bts).await;
157 Ok(())
158 }
159
160 pub async fn recv_vpn_packet(&self) -> anyhow::Result<Bytes> {
162 let packet = recv_vpn_packet(&self.ctx).await;
163 Ok(packet)
164 }
165}
166
167pub type CtxField<T> = fn(&AnyCtx<Config>) -> T;
168
169#[cfg(unix)]
175async fn run_vpn_fd_handler(ctx: AnyCtx<Config>, fd: i32) -> anyhow::Result<()> {
176 let file = unsafe { std::fs::File::from_raw_fd(fd) };
177 let flags = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL) };
178 if flags < 0 {
179 return Err(std::io::Error::last_os_error()).context("could not get VPN fd flags");
180 }
181 if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0 {
182 return Err(std::io::Error::last_os_error()).context("could not make VPN fd nonblocking");
183 }
184 let async_fd = AsyncFd::new(file).context("could not register VPN fd with Tokio")?;
185
186 let read_task = async {
187 let mut buf = vec![0u8; 65535]; loop {
189 match async_fd
190 .async_io(Interest::READABLE, |mut file| file.read(&mut buf))
191 .await
192 {
193 Ok(n) if n > 0 => {
194 #[cfg(target_os = "macos")]
198 let pkt = {
199 if n <= 4 {
200 continue;
201 }
202 bytes::Bytes::copy_from_slice(&buf[4..n])
203 };
204 #[cfg(not(target_os = "macos"))]
205 let pkt = bytes::Bytes::copy_from_slice(&buf[..n]);
206 send_vpn_packet(&ctx, pkt).await;
207 }
208 Ok(0) => {
209 tracing::warn!("VPN fd reached EOF");
210 break;
211 }
212 Err(e) => {
213 tracing::error!("Error reading from VPN fd: {}", e);
214 break;
215 }
216 _ => break,
217 }
218 }
219 anyhow::Ok(())
220 };
221
222 let write_task = async {
223 loop {
224 let packet = recv_vpn_packet(&ctx).await;
225
226 #[cfg(target_os = "macos")]
230 let packet = {
231 let af: u32 = if packet.first().map(|b| b >> 4) == Some(6) {
232 30
233 } else {
234 2
235 };
236 let mut framed = Vec::with_capacity(4 + packet.len());
237 framed.extend_from_slice(&af.to_be_bytes());
238 framed.extend_from_slice(&packet);
239 bytes::Bytes::from(framed)
240 };
241
242 match async_fd
243 .async_io(Interest::WRITABLE, |mut file| file.write(&packet))
244 .await
245 {
246 Ok(written) if written == packet.len() => {}
247 Ok(written) => {
248 tracing::error!(
249 written,
250 expected = packet.len(),
251 "Partial packet write to VPN fd"
252 );
253 break;
254 }
255 Err(e) => {
256 tracing::error!("Error writing to VPN fd: {}", e);
257 break;
258 }
259 }
260 }
261 anyhow::Ok(())
262 };
263
264 let res = (read_task, write_task).race().await;
265 tracing::warn!("VPN fd handler exited");
266 res
267}
268
269async fn client_main(ctx: AnyCtx<Config>) -> anyhow::Result<()> {
270 let tcp_rpc_serve = async {
271 if let Some(control_listen) = ctx.init().control_listen {
272 nanorpc_sillad::rpc_serve(
273 sillad::tcp::TcpListener::bind(control_listen).await?,
274 ControlService(ControlProtocolImpl { ctx: ctx.clone() }),
275 )
276 .await?;
277 anyhow::Ok(())
278 } else {
279 std::future::pending().await
280 }
281 };
282 let unix_rpc_serve = async {
283 #[cfg(unix)]
284 if let Some(path) = ctx.init().control_listen_unix.as_ref() {
285 nanorpc_sillad::rpc_serve(
286 sillad::unix::UnixListener::bind(path).await?,
287 ControlService(ControlProtocolImpl { ctx: ctx.clone() }),
288 )
289 .await?;
290 return anyhow::Ok(());
291 }
292 std::future::pending().await
293 };
294 let pipe_rpc_serve = async {
295 #[cfg(windows)]
296 if let Some(name) = ctx.init().control_listen_pipe.as_ref() {
297 nanorpc_sillad::rpc_serve(
298 sillad::windows_pipe::NamedPipeListener::bind(name, None)?,
299 ControlService(ControlProtocolImpl { ctx: ctx.clone() }),
300 )
301 .await?;
302 return anyhow::Ok(());
303 }
304 std::future::pending().await
305 };
306 let rpc_serve = (tcp_rpc_serve, unix_rpc_serve, pipe_rpc_serve).race();
307 if ctx.init().dry_run {
308 rpc_serve.await
309 } else {
310 let vpn_loop = vpn_loop(&ctx);
311
312 let _client_loop = Immortal::spawn(run_session(ctx.clone()));
313
314 (
315 socks5_loop(&ctx)
316 .inspect_err(|e| tracing::error!(err = debug(e), "socks5 loop stopped")),
317 vpn_loop.inspect_err(|e| tracing::error!(err = debug(e), "vpn loop stopped")),
318 http_proxy_serve(&ctx)
319 .inspect_err(|e| tracing::error!(err = debug(e), "http proxy stopped")),
320 auth_loop(&ctx).inspect_err(|e| tracing::error!(err = debug(e), "auth loop stopped")),
321 bw_token_refresh_loop(&ctx)
322 .inspect_err(|e| tracing::error!(err = debug(e), "bw token loop stopped")),
323 rpc_serve,
324 pac_serve(&ctx),
325 port_forward(&ctx),
326 )
327 .race()
328 .await
329 }
330}