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 tokio::io::{AsyncReadExt, AsyncWriteExt};
15
16use crate::{
17 auth::{auth_loop, get_auth_token},
18 broker::broker_client,
19 bw_token::bw_token_refresh_loop,
20 control_prot::{ControlProtocolImpl, DummyControlProtocolTransport},
21 http_proxy::http_proxy_serve,
22 logging,
23 pac::pac_serve,
24 port_forward::port_forward,
25 session::{open_conn, run_session},
26 socks5::socks5_loop,
27 vpn::{recv_vpn_packet, send_vpn_packet, vpn_loop},
28};
29
30pub use geph5_misc_rpc::client_config::{BrokerKeys, Config};
36
37#[derive(Clone)]
38pub struct Client {
39 task: Shared<geph5_rt::Task<Result<(), Arc<anyhow::Error>>>>,
40 ctx: AnyCtx<Config>,
41}
42
43impl Client {
44 pub fn start(cfg: Config) -> Self {
46 Self::start_with_vpn_fd(cfg, None)
47 }
48
49 #[cfg(unix)]
52 pub fn start_with_vpn_fd(cfg: Config, vpn_fd: Option<i32>) -> Self {
53 let ctx = AnyCtx::new(cfg.clone());
54 let _ = logging::init_logging(&ctx);
56 let ((fd_limit, _), _) = binary_search::binary_search((1, ()), (65536, ()), |lim| {
57 if rlimit::increase_nofile_limit(lim).unwrap_or_default() >= lim {
58 binary_search::Direction::Low(())
59 } else {
60 binary_search::Direction::High(())
61 }
62 });
63 tracing::info!("raised file descriptor limit to {}", fd_limit);
64
65 if let Some(fd) = vpn_fd {
66 let ctx_clone = ctx.clone();
67 geph5_rt::spawn(async move {
68 let file: std::fs::File = unsafe { std::os::fd::FromRawFd::from_raw_fd(fd) };
70 let async_fd = geph5_rt::asyncfd::AsyncFdStream::new(file)
71 .expect("could not wrap VPN fd in AsyncFdStream");
72
73 let (mut reader, mut writer) = tokio::io::split(async_fd);
75
76 let read_task = async {
78 let mut buf = vec![0u8; 65535]; loop {
80 match reader.read(&mut buf).await {
81 Ok(n) if n > 0 => {
82 #[cfg(target_os = "macos")]
86 let pkt = {
87 if n <= 4 {
88 continue;
89 }
90 bytes::Bytes::copy_from_slice(&buf[4..n])
91 };
92 #[cfg(not(target_os = "macos"))]
93 let pkt = bytes::Bytes::copy_from_slice(&buf[..n]);
94 send_vpn_packet(&ctx_clone, pkt).await;
96 }
97 Ok(0) => {
98 tracing::warn!("VPN fd reached EOF");
100 break;
101 }
102 Err(e) => {
103 tracing::error!("Error reading from VPN fd: {}", e);
104 break;
105 }
106 _ => break,
107 }
108 }
109 anyhow::Ok(())
110 };
111
112 let write_task = async {
114 loop {
115 let packet = recv_vpn_packet(&ctx_clone).await;
117
118 #[cfg(target_os = "macos")]
122 let packet = {
123 let af: u32 = if packet.first().map(|b| b >> 4) == Some(6) {
124 30
125 } else {
126 2
127 };
128 let mut framed = Vec::with_capacity(4 + packet.len());
129 framed.extend_from_slice(&af.to_be_bytes());
130 framed.extend_from_slice(&packet);
131 bytes::Bytes::from(framed)
132 };
133
134 if let Err(e) = writer.write_all(&packet).await {
136 tracing::error!("Error writing to VPN fd: {}", e);
137 break;
138 }
139
140 if let Err(e) = writer.flush().await {
141 tracing::error!("Error flushing VPN fd: {}", e);
142 break;
143 }
144 }
145 anyhow::Ok(())
146 };
147
148 let _ = (read_task, write_task).race().await;
150 tracing::warn!("VPN fd handler exited");
151 })
152 .detach();
153 }
154 let task = geph5_rt::spawn(client_main(ctx.clone()).map_err(Arc::new));
155 Client {
156 task: task.shared(),
157 ctx,
158 }
159 }
160
161 #[cfg(not(unix))]
163 pub fn start_with_vpn_fd(cfg: Config, _vpn_fd: Option<i32>) -> Self {
164 let ctx = AnyCtx::new(cfg.clone());
165 let _ = logging::init_logging(&ctx);
166 let ((fd_limit, _), _) = binary_search::binary_search((1, ()), (65536, ()), |lim| {
167 if rlimit::increase_nofile_limit(lim).unwrap_or_default() >= lim {
168 binary_search::Direction::Low(())
169 } else {
170 binary_search::Direction::High(())
171 }
172 });
173 tracing::info!("raised file descriptor limit to {}", fd_limit);
174
175 let task = geph5_rt::spawn(client_main(ctx.clone()).map_err(Arc::new));
176 Client {
177 task: task.shared(),
178 ctx,
179 }
180 }
181
182 pub async fn open_conn(&self, remote: &str) -> anyhow::Result<Box<dyn Pipe>> {
184 open_conn(&self.ctx, "tcp", remote).await
185 }
186
187 pub async fn wait_until_dead(self) -> anyhow::Result<()> {
189 self.task.await.map_err(|e| anyhow::anyhow!(e))
190 }
191
192 pub fn check_dead(&self) -> anyhow::Result<()> {
194 match self
195 .task
196 .clone()
197 .poll_unpin(&mut std::task::Context::from_waker(&noop_waker()))
198 {
199 std::task::Poll::Ready(val) => val.map_err(|e| anyhow::anyhow!(e))?,
200 std::task::Poll::Pending => {}
201 }
202
203 Ok(())
204 }
205
206 pub fn control_client(&self) -> ControlClient {
208 ControlClient(DynRpcTransport::new(DummyControlProtocolTransport(
209 ControlService(ControlProtocolImpl {
210 ctx: self.ctx.clone(),
211 }),
212 )))
213 }
214
215 pub async fn user_info(&self) -> anyhow::Result<UserInfo> {
217 let auth_token = get_auth_token(&self.ctx).await?;
218 let user_info = broker_client(&self.ctx)?
219 .get_user_info(auth_token)
220 .await??
221 .context("no such user")?;
222 Ok(user_info)
223 }
224
225 pub async fn send_vpn_packet(&self, bts: Bytes) -> anyhow::Result<()> {
227 send_vpn_packet(&self.ctx, bts).await;
228 Ok(())
229 }
230
231 pub async fn recv_vpn_packet(&self) -> anyhow::Result<Bytes> {
233 let packet = recv_vpn_packet(&self.ctx).await;
234 Ok(packet)
235 }
236}
237
238pub type CtxField<T> = fn(&AnyCtx<Config>) -> T;
239
240async fn client_main(ctx: AnyCtx<Config>) -> anyhow::Result<()> {
241 let tcp_rpc_serve = async {
242 if let Some(control_listen) = ctx.init().control_listen {
243 nanorpc_sillad::rpc_serve(
244 sillad::tcp::TcpListener::bind(control_listen).await?,
245 ControlService(ControlProtocolImpl { ctx: ctx.clone() }),
246 )
247 .await?;
248 anyhow::Ok(())
249 } else {
250 std::future::pending().await
251 }
252 };
253 let unix_rpc_serve = async {
254 #[cfg(unix)]
255 if let Some(path) = ctx.init().control_listen_unix.as_ref() {
256 nanorpc_sillad::rpc_serve(
257 sillad::unix::UnixListener::bind(path).await?,
258 ControlService(ControlProtocolImpl { ctx: ctx.clone() }),
259 )
260 .await?;
261 return anyhow::Ok(());
262 }
263 std::future::pending().await
264 };
265 let pipe_rpc_serve = async {
266 #[cfg(windows)]
267 if let Some(name) = ctx.init().control_listen_pipe.as_ref() {
268 nanorpc_sillad::rpc_serve(
269 sillad::windows_pipe::NamedPipeListener::bind(name, None)?,
270 ControlService(ControlProtocolImpl { ctx: ctx.clone() }),
271 )
272 .await?;
273 return anyhow::Ok(());
274 }
275 std::future::pending().await
276 };
277 let rpc_serve = (tcp_rpc_serve, unix_rpc_serve, pipe_rpc_serve).race();
278 if ctx.init().dry_run {
279 rpc_serve.await
280 } else {
281 let vpn_loop = vpn_loop(&ctx);
282
283 let _client_loop = Immortal::spawn(run_session(ctx.clone()));
284
285 (
286 socks5_loop(&ctx)
287 .inspect_err(|e| tracing::error!(err = debug(e), "socks5 loop stopped")),
288 vpn_loop.inspect_err(|e| tracing::error!(err = debug(e), "vpn loop stopped")),
289 http_proxy_serve(&ctx)
290 .inspect_err(|e| tracing::error!(err = debug(e), "http proxy stopped")),
291 auth_loop(&ctx).inspect_err(|e| tracing::error!(err = debug(e), "auth loop stopped")),
292 bw_token_refresh_loop(&ctx)
293 .inspect_err(|e| tracing::error!(err = debug(e), "bw token loop stopped")),
294 rpc_serve,
295 pac_serve(&ctx),
296 port_forward(&ctx),
297 )
298 .race()
299 .await
300 }
301}