├── .gitignore ├── src ├── lib.rs ├── traits.rs ├── session.rs ├── pipe.rs ├── server.rs └── client.rs ├── examples ├── client │ ├── Cargo.toml │ └── src │ │ └── main.rs └── server │ ├── Cargo.toml │ └── src │ └── main.rs ├── Cargo.toml ├── LICENCE.md ├── .github └── workflows │ └── rust.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | Cargo.lock 4 | *.pem 5 | *.crt 6 | *.csr 7 | *.ext 8 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod pipe; 3 | mod server; 4 | mod session; 5 | mod traits; 6 | 7 | pub use tokio; 8 | pub use tokio_rustls::rustls; 9 | pub use tokio_rustls::webpki; 10 | 11 | pub use client::Client; 12 | pub use server::Server; 13 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use tokio::io::{AsyncRead, AsyncWrite}; 2 | 3 | pub trait Readable: 'static + AsyncRead + Send {} 4 | pub trait Writable: 'static + AsyncWrite + Send {} 5 | 6 | impl Readable for T {} 7 | impl Writable for T {} 8 | -------------------------------------------------------------------------------- /examples/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sltunnel_client" 3 | version = "0.1.0" 4 | authors = ["Naoki Ikeguchi "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "0.2", features = ["macros"] } 11 | sltunnel = { path = "../.." } 12 | -------------------------------------------------------------------------------- /examples/server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sltunnel_server" 3 | version = "0.1.0" 4 | authors = ["Naoki Ikeguchi "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tokio = { version = "0.2", features = ["macros"] } 11 | sltunnel = { path = "../.." } 12 | -------------------------------------------------------------------------------- /src/session.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use crate::pipe::{pipes, Pipes}; 4 | use crate::traits::{Readable, Writable}; 5 | 6 | pub struct SessionMeta { 7 | peer_addr: SocketAddr, 8 | } 9 | 10 | impl SessionMeta { 11 | pub fn get_peer_addr(&self) -> &SocketAddr { 12 | &self.peer_addr 13 | } 14 | } 15 | 16 | pub(crate) type Session = (SessionMeta, Pipes); 17 | 18 | pub(crate) fn create_session( 19 | peer_addr: SocketAddr, 20 | upstream: U, 21 | downstream: D, 22 | ) -> Session 23 | where 24 | U: Readable + Writable, 25 | D: Readable + Writable, 26 | { 27 | (SessionMeta { peer_addr }, pipes(upstream, downstream)) 28 | } 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sltunnel" 3 | description = "A simple TLS tunneling implementation, written in Rust." 4 | version = "0.1.0" 5 | license = "MIT" 6 | license-file = "LICENCE.md" 7 | readme = "README.md" 8 | homepage = "https://github.com/siketyan/sltunnel" 9 | repository = "https://github.com/siketyan/sltunnel" 10 | documentation = "https://github.com/siketyan/sltunnel/#readme" 11 | keywords = ["rust-library", "tls-tunnel", "firewall-bypass"] 12 | authors = ["Naoki Ikeguchi "] 13 | edition = "2018" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | tokio = { version = "0.2", features = ["io-util", "macros", "tcp", "rt-core"] } 19 | tokio-rustls = "0.14" 20 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Naoki Ikeguchi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | - push 5 | 6 | env: 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - uses: actions/cache@v2 17 | with: 18 | path: | 19 | ~/.cargo/registry 20 | ~/.cargo/git 21 | target 22 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} 23 | 24 | - name: Install latest nightly 25 | uses: actions-rs/toolchain@v1 26 | with: 27 | toolchain: nightly 28 | override: true 29 | components: rustfmt,clippy 30 | 31 | - name: Build 32 | uses: actions-rs/cargo@v1 33 | with: 34 | command: build 35 | args: --verbose 36 | 37 | - name: Run clippy 38 | uses: actions-rs/clippy-check@v1 39 | with: 40 | token: ${{ secrets.GITHUB_TOKEN }} 41 | 42 | - name: Run rustfmt 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: fmt 46 | args: -- --check 47 | 48 | - name: Run tests 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: test 52 | args: --verbose 53 | -------------------------------------------------------------------------------- /src/pipe.rs: -------------------------------------------------------------------------------- 1 | use tokio::io::{copy, split, Copy, ReadHalf, WriteHalf}; 2 | 3 | use crate::traits::{Readable, Writable}; 4 | 5 | pub struct Pipe 6 | where 7 | R: Readable, 8 | W: Writable, 9 | { 10 | reader: R, 11 | writer: W, 12 | } 13 | 14 | impl Pipe, WriteHalf> 15 | where 16 | U: Readable + Writable, 17 | D: Readable + Writable, 18 | { 19 | pub fn run(&mut self) -> Copy<'_, ReadHalf, WriteHalf> { 20 | copy::, WriteHalf>(&mut self.reader, &mut self.writer) 21 | } 22 | } 23 | 24 | pub(crate) type Pipes = ( 25 | Pipe, WriteHalf>, 26 | Pipe, WriteHalf>, 27 | ); 28 | 29 | pub(crate) fn pipes( 30 | upstream: U, 31 | downstream: D, 32 | ) -> ( 33 | Pipe, WriteHalf>, 34 | Pipe, WriteHalf>, 35 | ) 36 | where 37 | U: Readable + Writable, 38 | D: Readable + Writable, 39 | { 40 | let (upstream_read, upstream_write) = split(upstream); 41 | let (downstream_read, downstream_write) = split(downstream); 42 | 43 | ( 44 | Pipe { 45 | reader: upstream_read, 46 | writer: downstream_write, 47 | }, 48 | Pipe { 49 | reader: downstream_read, 50 | writer: upstream_write, 51 | }, 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::net::SocketAddr; 3 | use std::sync::Arc; 4 | use tokio::net::{TcpListener, TcpStream}; 5 | use tokio_rustls::rustls::ServerConfig; 6 | use tokio_rustls::server::TlsStream; 7 | use tokio_rustls::TlsAcceptor; 8 | 9 | use crate::session::{create_session, Session}; 10 | 11 | type Upstream = TcpStream; 12 | type Downstream = TlsStream; 13 | type ServerSession = Session; 14 | 15 | pub struct Server { 16 | upstream: SocketAddr, 17 | tcp_listener: TcpListener, 18 | tls_acceptor: TlsAcceptor, 19 | } 20 | 21 | impl Server { 22 | pub async fn start( 23 | bind_to: SocketAddr, 24 | upstream: SocketAddr, 25 | server_config: ServerConfig, 26 | ) -> Result> { 27 | Ok(Self { 28 | upstream, 29 | tcp_listener: TcpListener::bind(bind_to).await?, 30 | tls_acceptor: TlsAcceptor::from(Arc::new(server_config)), 31 | }) 32 | } 33 | 34 | pub async fn wait_for_session(&mut self) -> Result> { 35 | let (downstream, peer_addr) = self.tcp_listener.accept().await?; 36 | let downstream = self.tls_acceptor.clone().accept(downstream).await?; 37 | let upstream = TcpStream::connect(self.upstream).await?; 38 | 39 | Ok(create_session(peer_addr, upstream, downstream)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::net::SocketAddr; 3 | use std::sync::Arc; 4 | use tokio::net::{TcpListener, TcpStream}; 5 | use tokio_rustls::client::TlsStream; 6 | use tokio_rustls::rustls::ClientConfig; 7 | use tokio_rustls::webpki::{DNSName, DNSNameRef}; 8 | use tokio_rustls::TlsConnector; 9 | 10 | use crate::session::{create_session, Session}; 11 | 12 | type Upstream = TlsStream; 13 | type Downstream = TcpStream; 14 | type ClientSession = Session; 15 | 16 | pub struct Client { 17 | hostname: DNSName, 18 | upstream: SocketAddr, 19 | tcp_listener: TcpListener, 20 | tls_connector: TlsConnector, 21 | } 22 | 23 | impl Client { 24 | pub async fn start( 25 | hostname: &str, 26 | bind_to: SocketAddr, 27 | upstream: SocketAddr, 28 | client_config: ClientConfig, 29 | ) -> Result> { 30 | Ok(Self { 31 | upstream, 32 | hostname: DNSName::from(DNSNameRef::try_from_ascii_str(hostname)?), 33 | tcp_listener: TcpListener::bind(bind_to).await?, 34 | tls_connector: TlsConnector::from(Arc::new(client_config)), 35 | }) 36 | } 37 | 38 | pub async fn wait_for_session(&mut self) -> Result> { 39 | let (downstream, peer_addr) = self.tcp_listener.accept().await?; 40 | let upstream = TcpStream::connect(self.upstream).await?; 41 | let upstream = self 42 | .tls_connector 43 | .connect(self.hostname.as_ref(), upstream) 44 | .await?; 45 | 46 | Ok(create_session(peer_addr, upstream, downstream)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚇 sltunnel 2 | ![Rust](https://github.com/siketyan/sltunnel/workflows/Rust/badge.svg) 3 | A simple TLS tunneling implementation, written in Rust. 4 | 5 | ## What is TLS tunneling? 6 | TLS tunneling is the way to transport any TCP packets via a TLS tunnel. 7 | Thanks to TLS, we can communicate with remote server more securely, through general firewalls. 8 | 9 | ### What about sslh? 10 | sslh is a multiplexer of packets using their header bytes. 11 | It is not a problem in general home networks, but some network (e.g. schools or works) restricts to transport without correct TLS negotiation even in port 443. 12 | 13 | TLS tunneling is different. 14 | The transportation through a TLS tunnel is completely negotiated as a TLS connection. 15 | Usually, the firewall accepts the connection even in schools or works! 16 | 17 | ## Installation 18 | ```toml 19 | [dependencies] 20 | sltunnel = "0.1" 21 | ``` 22 | 23 | ## Examples 24 | In this case, let them to communicate between `[::]:11234` and `[::]:22334` via `[::]:33445`. 25 | 26 | ### Server 27 | The server listens on `[::]:33445` with TLS and relays connections to `[::]:11234`. 28 | 29 | ```console 30 | $ cd ./examples/server 31 | $ cargo build --release 32 | $ ./target/release/sltunnel_server [::]:33445 [::]:11234 33 | ``` 34 | 35 | ### Client 36 | The client listens on `[::]:22334` and relays connections to `[::]:33445` with TLS. 37 | 38 | ```console 39 | $ cd ./examples/client 40 | $ cargo build --release 41 | $ ./target/release/sltunnel_client [::]:22334 [::]:33445 42 | ``` 43 | 44 | ### Testing 45 | When both server and client is ready, run the command to check the connection: 46 | 47 | ```console 48 | $ nc -k -l 11223 & 49 | $ echo "OK" > /dev/tcp/localhost/22334 50 | ``` 51 | 52 | If the console outputs "OK", it works! 53 | -------------------------------------------------------------------------------- /examples/client/src/main.rs: -------------------------------------------------------------------------------- 1 | use sltunnel::rustls::ClientConfig; 2 | use sltunnel::Client; 3 | use std::env::args; 4 | use std::error::Error; 5 | use std::fs::File; 6 | use std::io::BufReader; 7 | use std::net::SocketAddr; 8 | use std::path::Path; 9 | use std::str::FromStr; 10 | 11 | fn load_ca(config: &mut ClientConfig, path: &Path) -> std::io::Result<(usize, usize)> { 12 | config 13 | .root_store 14 | .add_pem_file(&mut BufReader::new(File::open(path)?)) 15 | .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid key")) 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<(), Box> { 20 | let args = args().into_iter().collect::>(); 21 | if args.len() < 3 { 22 | eprintln!("Usage: {} [bind_to] [upstream] [hostname]", &args[0]); 23 | return Ok(()); 24 | } 25 | 26 | let mut client_config = ClientConfig::new(); 27 | let (num_certificates, _) = load_ca(&mut client_config, Path::new("./ca.pem"))?; 28 | 29 | println!("Loaded {} certificates.", num_certificates); 30 | 31 | let hostname = &args[3]; 32 | let bind_to = SocketAddr::from_str(&args[1])?; 33 | let upstream = SocketAddr::from_str(&args[2])?; 34 | let mut client = Client::start(hostname, bind_to, upstream, client_config).await?; 35 | 36 | println!( 37 | "Listening on {} with upstream {} ({}).", 38 | bind_to, hostname, upstream, 39 | ); 40 | 41 | loop { 42 | let (meta, pipes) = client.wait_for_session().await?; 43 | let (mut inbound, mut outbound) = pipes; 44 | 45 | tokio::spawn(async move { inbound.run().await }); 46 | tokio::spawn(async move { outbound.run().await }); 47 | 48 | println!( 49 | "Connection established: {} (TLS) <-> {}.", 50 | upstream, 51 | meta.get_peer_addr(), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/server/src/main.rs: -------------------------------------------------------------------------------- 1 | use sltunnel::rustls::internal::pemfile::{certs, rsa_private_keys}; 2 | use sltunnel::rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig}; 3 | use sltunnel::Server; 4 | use std::env::args; 5 | use std::error::Error; 6 | use std::fs::File; 7 | use std::io::BufReader; 8 | use std::net::SocketAddr; 9 | use std::path::Path; 10 | use std::str::FromStr; 11 | 12 | fn load_certificates(path: &Path) -> std::io::Result> { 13 | certs(&mut BufReader::new(File::open(path)?)) 14 | .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid cert")) 15 | } 16 | 17 | fn load_private_keys(path: &Path) -> std::io::Result> { 18 | rsa_private_keys(&mut BufReader::new(File::open(path)?)) 19 | .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid key")) 20 | } 21 | 22 | #[tokio::main] 23 | async fn main() -> Result<(), Box> { 24 | let args = args().into_iter().collect::>(); 25 | if args.len() < 2 { 26 | eprintln!("Usage: {} [bind_to] [upstream]", &args[0]); 27 | return Ok(()); 28 | } 29 | 30 | let certificates = load_certificates(Path::new("./cert.pem"))?; 31 | let mut private_keys = load_private_keys(Path::new("./privkey.pem"))?; 32 | let mut server_config = ServerConfig::new(NoClientAuth::new()); 33 | 34 | let num_certificates = certificates.len(); 35 | let num_private_keys = private_keys.len(); 36 | 37 | server_config.set_single_cert(certificates, private_keys.remove(0))?; 38 | 39 | println!( 40 | "Loaded {} certificates and {} private keys.", 41 | num_certificates, num_private_keys, 42 | ); 43 | 44 | let bind_to = SocketAddr::from_str(&args[1])?; 45 | let upstream = SocketAddr::from_str(&args[2])?; 46 | let mut server = Server::start(bind_to, upstream, server_config).await?; 47 | 48 | println!("Listening on {} with upstream {}.", bind_to, upstream); 49 | 50 | loop { 51 | let (meta, pipes) = server.wait_for_session().await?; 52 | let (mut inbound, mut outbound) = pipes; 53 | 54 | tokio::spawn(async move { inbound.run().await }); 55 | tokio::spawn(async move { outbound.run().await }); 56 | 57 | println!( 58 | "Connection established: {} <-> {} (TLS).", 59 | upstream, 60 | meta.get_peer_addr(), 61 | ); 62 | } 63 | } 64 | --------------------------------------------------------------------------------