Crate ptrs

Source
Expand description

§Pluggable Transports in Rust (PTRS)

License: MIT/Apache 2.0

PTRS is a library for writing pluggable transports in Rust.

⚠️ 🚧 WARNING This crate is still under construction 🚧 ⚠️

  • interface subject to change at any time
  • Not production ready
    • do not rely on this for any security critical applications

§Library Usage

This library (currently) revolves around the abstraction of connections as anything that implement the traits [tokio::io:AsyncRead] + tokio::io::AsyncWrite + Unpin + Send + Sync. This allows us to define the expected shared behavior of pluggable transports as a transform of these [Stream]s.

/// Future containing a generic result. We use this for functions that take
/// and/or return futures that will produce Read/Write tunnels once awaited.
pub type FutureResult<T, E> = Box<dyn Future<Output = Result<T, E>> + Send>;

/// Future containing a generic result, shorthand for ['FutureResult']. We use
/// this for functions that take and/or return futures that will produce
/// Read/Write tunnels once awaited.
pub(crate) type F<T, E> = FutureResult<T, E>;


/// Client Transport
pub trait ClientTransport<InRW, InErr>
where
    InRW: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
{
    type OutRW: AsyncRead + AsyncWrite + Send + Unpin;
    type OutErr: std::error::Error + Send + Sync;
    type Builder: ClientBuilder<InRW>;

    /// Create a pluggable transport connection given a future that will return
    /// a Read/Write object that can be used as the underlying socket for the
    /// connection.
    fn establish(self, input: Pin<F<InRW, InErr>>) -> Pin<F<Self::OutRW, Self::OutErr>>;

    /// Create a connection for the pluggable transport client using the provided
    /// (pre-existing/pre-connected) Read/Write object as the underlying socket.
    fn wrap(self, io: InRW) -> Pin<F<Self::OutRW, Self::OutErr>>;

    /// Returns a string identifier for this transport
    fn method_name() -> String;
}

/// Server Transport
pub trait ServerTransport<InRW>
where
    InRW: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
{
    type OutRW: AsyncRead + AsyncWrite + Send + Unpin;
    type OutErr: std::error::Error + Send + Sync;
    type Builder: ServerBuilder<InRW>;

    /// Create/accept a connection for the pluggable transport client using the
    /// provided (pre-existing/pre-connected) Read/Write object as the
    /// underlying socket.
    fn reveal(self, io: InRW) -> Pin<F<Self::OutRW, Self::OutErr>>;

    /// Returns a string identifier for this transport
    fn method_name() -> String;
}

§Integrating ptrs Transports

Given this abstraction integrating transports into async rust applications becomes relatively straightforward, for example, integrating the identity transport (which performs a direct copy with no actual transform) could be done similar to:

/// TODO

Integration on the client side is similarly straightforward.

/// TODO

For more in depth integration exapmples see the binary examples in the lyrebird crate.

§Configuration & State

Transports can be configured using their respective builder interface implementations which require options(...) and statedir(...) functions. See the obfs4 transport for and example implementation of the ptrs interfaces.

§Composition

Because the ptrs interface wraps objects implementing connection oriented traits and and returns trait objects implementing the same abstraction is is possible to wrap multiple transports on top of one another. One reason to do this might be to have separate reliability, obfuscation and padding strategies that can be composed interchangeably.

let listener = tokio::net::TcpListener::bind("127.0.0.1:8009")
    .await
    .unwrap();

let (tcp_sock, _) = listener.accept().await.unwrap();

let pb: &BuilderS = &<Passthrough as PluggableTransport<TcpStream>>::server_builder();

let client1 = <BuilderS as ServerBuilder<TcpStream>>::build(pb);
let conn1 = client1.reveal(tcp_sock).await.unwrap();

let client2 = <BuilderS as ServerBuilder<TcpStream>>::build(pb);
let conn2 = client2.reveal(conn1).await.unwrap();

let client3 = <BuilderS as ServerBuilder<TcpStream>>::build(pb);
let mut sock = client3.reveal(conn2).await.unwrap();

let (mut r, mut w) = tokio::io::split(&mut sock);
_ = tokio::io::copy(&mut r, &mut w).await;

In the client:

let pb: &BuilderC = &<Passthrough as PluggableTransport<TcpStream>>::client_builder();
let client = <BuilderC as ClientBuilder<TcpStream>>::build(pb);
let conn_fut1 = client.establish(Box::pin(tcp_dial_fut));
let client = <BuilderC as ClientBuilder<TcpStream>>::build(pb);
let conn_fut2 = client.establish(Box::pin(conn_fut1));
let client = <BuilderC as ClientBuilder<TcpStream>>::build(pb);
let conn_fut3 = client.establish(Box::pin(conn_fut2));
let mut conn = conn_fut3.await?;

let msg = b"a man a plan a canal panama";
_ = conn.write(&msg[..]).await?;

§Implementing a Transport

There are several constructions that can be used to build up a pluggable transport, in part this is because no individual construction has proven demonstrably better than the others.

The obfs4 transport is implemented using the tokio_util::codec model.

§Notes / Resources

While this is related to and motivated by the Tor pluggable transport system, the primary concern of this repository is creating a consistent and useful abstraction for building pluggable transports. For more information about Tor related pluggable transports see the following resources.

§Open Source License

Dual licensing under both MIT and Apache-2.0 is the currently accepted standard by the Rust language community and has been used for both the compiler and many public libraries since (see Why dual MIT/ASL2license?). In order to match the community standards, ptrs is using the dual MIT+Apache-2.0 license.

§Contributing

Contributors, Issues, and Pull Requests are Welcome

Modules§

args
Key–value mappings for the representation of client and server options.
constants
orport

Macros§

args
Create an Args object from a list of key-value pairs
debug
debug_span
error
error_span
info
info_span
trace
trace_span
warn
warn_span

Structs§

Bindaddr
A combination of a method name and an address, as extracted from TOR_PT_SERVER_BINDADDR.
ClientInfo
ServerInfo
Check the server pluggable transports environment, emitting an error message and returning a non-nil error if any error is encountered. Resolves the various requested bind addresses, the server ORPort and extended ORPort, and reads the auth cookie file. Returns a ServerInfo struct.

Enums§

Error

Traits§

ClientBuilder
Client Transport Builder
ClientTransport
Client Transport
Conn
Creator1 defines a stream creator that could be applied to either the input stream feature or the resulting stream future making them composable.
ConnectExt
In concept this trait provides extended functionality that can be appled to the client / server traits for creating connections / pluggable transports. this is still in a TODO state.
PluggableTransport
ServerBuilder
Server Transport builder interface
ServerTransport
Server Transport

Functions§

is_client
Determines if the current program should be running as a client or server by checking the TOR_PT_CLIENT_TRANSPORTS and TOR_PT_SERVER_TRANSPORTS environment variables.
make_state_dir
Return the directory name in the TOR_PT_STATE_LOCATION environment variable, creating it if it doesn’t exist. Returns non-nil error if TOR_PT_STATE_LOCATION is not set or if there is an error creating the directory.
pt_should_exit_on_stdin_close
Feature #15435 adds a new env var for determining if Tor keeps stdin open for use in termination detection.
resolve_addr

Type Aliases§

FutureResult
Future containing a generic result. We use this for functions that take and/or return futures that will produce Read/Write tunnels once awaited.
TcpStreamFut
UdpSocketFut