1use crate::proto::{
16 Command, ProtocolError, ReplyKind, SocksMethod, client,
17 server::{Header, Reply, UsernamePasswordResponse},
18};
19use rama_core::{
20 Service,
21 error::BoxError,
22 extensions::{Extensions, ExtensionsRef},
23 io::Io,
24 rt::Executor,
25 telemetry::tracing,
26};
27use rama_net::{
28 address::SocketAddress,
29 extensions::StreamTransformed,
30 user::{self, authority::Authorizer},
31};
32use rama_tcp::{TcpStream, server::TcpListener};
33use std::{fmt, sync::Arc};
34
35mod peek;
36#[doc(inline)]
37pub use peek::{NoSocks5RejectError, Socks5PeekRouter, Socks5PrefixedIo};
38
39mod connect;
40pub use connect::{Connector, DefaultConnector, LazyConnector, Socks5Connector};
41
42pub mod bind;
43pub use bind::{Binder, DefaultBinder, Socks5Binder};
44
45pub mod udp;
46pub use udp::{DefaultUdpRelay, Socks5UdpAssociator, UdpRelay};
47
48#[derive(Debug, Clone)]
63pub struct Socks5Acceptor<C = DefaultConnector, B = (), U = (), A = ()> {
64 connector: C,
65 binder: B,
66 udp_associator: U,
67
68 auth: AuthKind<A>,
69
70 auth_opt: bool,
76
77 exec: Executor,
78}
79
80#[derive(Debug, Clone)]
81enum AuthKind<A> {
82 NoAuth(A),
83 WithAuth(A),
84}
85
86impl Socks5Acceptor<(), (), (), ()> {
87 #[must_use]
92 pub fn new(exec: Executor) -> Self {
93 Self {
94 connector: (),
95 binder: (),
96 udp_associator: (),
97 auth: AuthKind::NoAuth(()),
98 auth_opt: false,
99 exec,
100 }
101 }
102}
103
104impl<C, B, U> Socks5Acceptor<C, B, U> {
105 pub fn with_authorizer<A>(self, authorizer: A) -> Socks5Acceptor<C, B, U, A> {
106 Socks5Acceptor {
107 connector: self.connector,
108 binder: self.binder,
109 udp_associator: self.udp_associator,
110 auth: AuthKind::WithAuth(authorizer),
111 auth_opt: self.auth_opt,
112 exec: self.exec,
113 }
114 }
115
116 rama_utils::macros::generate_set_and_with! {
117 pub fn auth_optional(mut self, optional: bool) -> Self {
123 self.auth_opt = optional;
124 self
125 }
126 }
127}
128
129impl<B, U, A> Socks5Acceptor<(), B, U, A> {
130 pub fn with_connector<C>(self, connector: C) -> Socks5Acceptor<C, B, U, A> {
136 Socks5Acceptor {
137 connector,
138 binder: self.binder,
139 udp_associator: self.udp_associator,
140 auth: self.auth,
141 auth_opt: self.auth_opt,
142 exec: self.exec,
143 }
144 }
145
146 #[inline]
152 pub fn with_default_connector(self) -> Socks5Acceptor<DefaultConnector, B, U, A> {
153 self.with_connector(DefaultConnector::default())
154 }
155}
156
157impl<C, U, A> Socks5Acceptor<C, (), U, A> {
158 pub fn with_binder<B>(self, binder: B) -> Socks5Acceptor<C, B, U, A> {
164 Socks5Acceptor {
165 connector: self.connector,
166 binder,
167 udp_associator: self.udp_associator,
168 auth: self.auth,
169 auth_opt: self.auth_opt,
170 exec: self.exec,
171 }
172 }
173
174 #[inline]
180 pub fn with_default_binder(self) -> Socks5Acceptor<C, DefaultBinder, U, A> {
181 self.with_binder(DefaultBinder::default())
182 }
183}
184
185impl<C, B, A> Socks5Acceptor<C, B, (), A> {
186 pub fn with_udp_associator<U>(self, udp_associator: U) -> Socks5Acceptor<C, B, U, A> {
192 Socks5Acceptor {
193 connector: self.connector,
194 binder: self.binder,
195 udp_associator,
196 auth: self.auth,
197 auth_opt: self.auth_opt,
198 exec: self.exec,
199 }
200 }
201
202 #[inline]
208 pub fn with_default_udp_associator(self) -> Socks5Acceptor<C, B, DefaultUdpRelay, A> {
209 self.with_udp_associator(DefaultUdpRelay::default())
210 }
211}
212
213impl Socks5Acceptor {
214 #[inline]
215 pub fn default_with_executor(exec: Executor) -> Self {
216 Socks5Acceptor::new(exec).with_default_connector()
217 }
218}
219
220impl Default for Socks5Acceptor {
221 #[inline]
222 fn default() -> Self {
223 Self::default_with_executor(Executor::default())
224 }
225}
226
227#[derive(Debug)]
228pub struct Error {
230 kind: ErrorKind,
231 context: ErrorContext,
232 source: Option<BoxError>,
233}
234
235#[derive(Debug)]
236enum ErrorContext {
237 None,
238 Message(&'static str),
239 ReplyKind(ReplyKind),
240}
241
242impl From<&'static str> for ErrorContext {
243 fn from(value: &'static str) -> Self {
244 Self::Message(value)
245 }
246}
247
248impl From<ReplyKind> for ErrorContext {
249 fn from(value: ReplyKind) -> Self {
250 Self::ReplyKind(value)
251 }
252}
253
254impl Error {
255 fn io(err: std::io::Error) -> Self {
256 Self {
257 kind: ErrorKind::IO,
258 context: ErrorContext::None,
259 source: Some(err.into()),
260 }
261 }
262
263 fn protocol(err: ProtocolError) -> Self {
264 Self {
265 kind: ErrorKind::Protocol,
266 context: ErrorContext::None,
267 source: Some(err.into()),
268 }
269 }
270
271 fn aborted(reason: &'static str) -> Self {
272 Self {
273 kind: ErrorKind::Aborted(reason),
274 context: ErrorContext::None,
275 source: None,
276 }
277 }
278
279 fn service(error: impl Into<BoxError>) -> Self {
280 Self {
281 kind: ErrorKind::Service,
282 context: ErrorContext::None,
283 source: Some(error.into()),
284 }
285 }
286
287 fn with_context(mut self, context: impl Into<ErrorContext>) -> Self {
288 self.context = context.into();
289 self
290 }
291
292 fn with_source(mut self, err: impl Into<BoxError>) -> Self {
293 self.source = Some(err.into());
294 self
295 }
296}
297
298#[derive(Debug)]
299enum ErrorKind {
300 IO,
301 Protocol,
302 Aborted(&'static str),
303 Service,
304}
305
306impl fmt::Display for ErrorContext {
307 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308 match self {
309 Self::Message(message) => write!(f, "{message}"),
310 Self::ReplyKind(kind) => write!(f, "reply: {kind}"),
311 Self::None => write!(f, "no context"),
312 }
313 }
314}
315
316impl fmt::Display for Error {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 let context = &self.context;
319 match &self.kind {
320 ErrorKind::IO => {
321 write!(f, "server: handshake error: I/O ({context})")
322 }
323 ErrorKind::Protocol => {
324 write!(f, "server: handshake error: protocol error ({context})")
325 }
326 ErrorKind::Aborted(reason) => {
327 write!(f, "server: handshake error: aborted: {reason} ({context})")
328 }
329 ErrorKind::Service => {
330 write!(f, "server: service error ({context})")
331 }
332 }
333 }
334}
335
336impl std::error::Error for Error {
337 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
338 self.source.as_ref().and_then(|e| e.source())
339 }
340}
341
342impl<C, B, U, A> Socks5Acceptor<C, B, U, A> {
343 pub async fn accept<S>(&self, mut stream: S) -> Result<(), Error>
344 where
345 C: Socks5Connector<S>,
346 U: Socks5UdpAssociator<S>,
347 A: Authorizer<user::Basic, Error: fmt::Debug>,
348 B: Socks5Binder<S>,
349 S: Io + Unpin + ExtensionsRef,
350 {
351 let client_header = client::Header::read_from(&mut stream)
352 .await
353 .map_err(|err| Error::protocol(err).with_context("read client header"))?;
354
355 let (negotiated_method, maybe_ext) = self
356 .handle_method(&client_header.methods, &mut stream)
357 .await?;
358
359 if let Some(ext) = maybe_ext {
360 stream.extensions().extend(&ext);
361 }
362
363 tracing::trace!(
364 "socks5 server: headers exchanged negotiated method = {negotiated_method:?} (for client methods: {:?}",
365 client_header.methods,
366 );
367
368 let client_request = client::Request::read_from(&mut stream)
369 .await
370 .map_err(|err| Error::protocol(err).with_context("read client request"))?;
371 tracing::trace!(
372 "socks5 server w/ destination {} and negotiated method {:?} (for client methods: {:?}): client request received cmd {:?}",
373 client_request.destination,
374 negotiated_method,
375 client_header.methods,
376 client_request.command,
377 );
378
379 stream.extensions().insert(StreamTransformed {
380 by: "rama-socks5::Socks5Acceptor",
381 });
382
383 match client_request.command {
384 Command::Connect => {
385 self.connector
386 .accept_connect(stream, client_request.destination)
387 .await
388 }
389 Command::Bind => {
390 self.binder
391 .accept_bind(stream, client_request.destination)
392 .await
393 }
394 Command::UdpAssociate => {
395 self.udp_associator
396 .accept_udp_associate(stream, client_request.destination)
397 .await
398 }
399 Command::Unknown(_) => {
400 tracing::debug!(
401 "socks5 server w/ destination {} for negotiated method: {:?} (for client methods: {:?}): abort: unknown command {:?} not supported",
402 client_request.destination,
403 negotiated_method,
404 client_header.methods,
405 client_request.command,
406 );
407
408 Reply::error_reply(ReplyKind::CommandNotSupported)
409 .write_to(&mut stream)
410 .await
411 .map_err(|err| {
412 Error::io(err)
413 .with_context("write server reply: unknown command not supported")
414 })?;
415 Err(Error::aborted("unknown command not supported")
416 .with_context(ReplyKind::CommandNotSupported))
417 }
418 }
419 }
420}
421
422impl<C, B, U, A: Authorizer<user::Basic, Error: fmt::Debug>> Socks5Acceptor<C, B, U, A> {
423 async fn handle_method<S: Io + Unpin>(
424 &self,
425 methods: &[SocksMethod],
426 stream: &mut S,
427 ) -> Result<(SocksMethod, Option<Extensions>), Error> {
428 match &self.auth {
429 AuthKind::WithAuth(authorizer) => {
430 if methods.contains(&SocksMethod::UsernamePassword) {
431 Header::new(SocksMethod::UsernamePassword)
432 .write_to(stream)
433 .await
434 .map_err(|err| {
435 Error::io(err)
436 .with_context("write server reply: auth (username-password)")
437 })?;
438
439 let client_auth_req = client::UsernamePasswordRequest::read_from(stream)
440 .await
441 .map_err(|err| {
442 Error::protocol(err).with_context(
443 "read client auth sub-negotiation request: username-password",
444 )
445 })?;
446 let user::authority::AuthorizeResult { result, .. } =
447 authorizer.authorize(client_auth_req.basic).await;
448 match result {
449 Ok(maybe_ext) => {
450 UsernamePasswordResponse::new_success()
451 .write_to(stream)
452 .await
453 .map_err(|err| {
454 Error::io(err).with_context(
455 "write server auth sub-negotiation success response",
456 )
457 })?;
458 Ok((SocksMethod::UsernamePassword, maybe_ext))
459 }
460 Err(err) => {
461 tracing::trace!(
462 "socks5 acceptor's authorizer stopped inc request: {err:?}"
463 );
464 UsernamePasswordResponse::new_invalid_credentails()
465 .write_to(stream)
466 .await
467 .map_err(|err| {
468 Error::io(err).with_context(
469 "write server auth sub-negotiation error response: unauthorized",
470 )
471 })?;
472 Err(Error::aborted("username-password: client unauthorized"))
473 }
474 }
475 } else if self.auth_opt && methods.contains(&SocksMethod::NoAuthenticationRequired)
476 {
477 tracing::trace!(
478 "socks5 server: auth supported but optional: skipping auth as client does not support username-passowrd auth",
479 );
480
481 Header::new(SocksMethod::NoAuthenticationRequired)
482 .write_to(stream)
483 .await
484 .map_err(|err| {
485 Error::io(err).with_context("write server reply: no auth required")
486 })?;
487
488 Ok((SocksMethod::NoAuthenticationRequired, None))
489 } else {
490 Header::new(SocksMethod::NoAcceptableMethods)
491 .write_to(stream)
492 .await
493 .map_err(|err| {
494 Error::io(err).with_context(
495 "write server auth sub-negotiation error response: no acceptable methods",
496 )
497 })?;
498 Err(Error::aborted(
499 "username-password required but client doesn't support the method (auth == required)",
500 ))
501 }
502 }
503 AuthKind::NoAuth(_) => {
504 if methods.contains(&SocksMethod::NoAuthenticationRequired) {
505 Header::new(SocksMethod::NoAuthenticationRequired)
506 .write_to(stream)
507 .await
508 .map_err(|err| {
509 Error::io(err).with_context("write server reply: no auth required")
510 })?;
511
512 return Ok((SocksMethod::NoAuthenticationRequired, None));
513 }
514
515 Header::new(SocksMethod::NoAcceptableMethods)
516 .write_to(stream)
517 .await
518 .map_err(|err| {
519 Error::io(err).with_context(
520 "write server auth sub-negotiation error response: no acceptable methods",
521 )
522 })?;
523 Err(Error::aborted("no acceptable methods"))
524 }
525 }
526 }
527}
528
529impl<C, B, U, A, S> Service<S> for Socks5Acceptor<C, B, U, A>
530where
531 C: Socks5Connector<S>,
532 U: Socks5UdpAssociator<S>,
533 A: Authorizer<user::Basic, Error: fmt::Debug>,
534 B: Socks5Binder<S>,
535 S: Io + Unpin + ExtensionsRef,
536{
537 type Output = ();
538 type Error = Error;
539
540 #[inline]
541 fn serve(
542 &self,
543 stream: S,
544 ) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send + '_ {
545 self.accept(stream)
546 }
547}
548
549impl<C, B, U, A> Socks5Acceptor<C, B, U, A>
550where
551 C: Socks5Connector<TcpStream>,
552 U: Socks5UdpAssociator<TcpStream>,
553 A: Authorizer<user::Basic, Error: fmt::Debug>,
554 B: Socks5Binder<TcpStream>,
555{
556 pub async fn listen<Address>(self, address: Address) -> Result<(), BoxError>
560 where
561 Address: TryInto<SocketAddress, Error: Into<BoxError>>,
562 {
563 let tcp = TcpListener::bind_address(address, self.exec.clone()).await?;
564 tcp.serve(Arc::new(self)).await;
565 Ok(())
566 }
567}
568
569#[cfg(test)]
570mod test;