5 Commits

Author SHA1 Message Date
Eric Zhang
e25f021505 Bump version to 0.3.0 2022-04-14 15:24:09 -04:00
Orhun Parmaksız
cae08bb3c2 Support reading client/server secret from an environment variable (#18) 2022-04-14 14:40:52 -04:00
Basti Ortiz
b045d8028e Deps: minimize Tokio features (#25)
* Deps: use specific Tokio features

* Deps: run `cargo update`

* Refactor: move CLI-parsing outside Tokio runtime

* Fix: use `parse` over `try_parse`

Clap does special things behind the scenes before it exits.

* Refactor: use `tokio::main` macro for convenience
2022-04-14 14:39:46 -04:00
Eric Zhang
36a56c0d4a Run CI workflow on both push and pull_request 2022-04-14 14:33:31 -04:00
Antonio Mika
99fc4f7ddb Add a configuration to allow forward to a different local host (#15)
* Add a configuration to allow forward to a different local host

* Minor documentation / formatting change

Co-authored-by: Eric Zhang <ekzhang1@gmail.com>
2022-04-11 23:45:07 -04:00
7 changed files with 64 additions and 54 deletions

View File

@@ -1,6 +1,6 @@
name: CI name: CI
on: push on: [push, pull_request]
jobs: jobs:
rust: rust:

42
Cargo.lock generated
View File

@@ -84,7 +84,7 @@ dependencies = [
[[package]] [[package]]
name = "bore-cli" name = "bore-cli"
version = "0.2.3" version = "0.3.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -278,9 +278,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.121" version = "0.2.123"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -438,18 +438,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.36" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -560,15 +560,6 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.8.0" version = "1.8.0"
@@ -599,9 +590,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.90" version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -643,10 +634,7 @@ dependencies = [
"memchr", "memchr",
"mio", "mio",
"num_cpus", "num_cpus",
"once_cell",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"winapi", "winapi",
@@ -665,9 +653,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.32" version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" checksum = "80b9fa4360528139bc96100c160b7ae879f5567f49f1782b0b02035b0358ebf3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"pin-project-lite", "pin-project-lite",
@@ -688,9 +676,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.24" version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90442985ee2f57c9e1b548ee72ae842f4a9a20e3f417cc38dbc5dc684d9bb4ee" checksum = "6dfce9f3241b150f36e8e54bb561a742d5daa1a47b5dd9a5ce369fd4a4db2210"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"valuable", "valuable",
@@ -709,9 +697,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.10" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9df98b037d039d03400d9dd06b0f8ce05486b5f25e9a2d7d36196e142ebbc52" checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"sharded-slab", "sharded-slab",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bore-cli" name = "bore-cli"
version = "0.2.3" version = "0.3.0"
authors = ["Eric Zhang <ekzhang1@gmail.com>"] authors = ["Eric Zhang <ekzhang1@gmail.com>"]
license = "MIT" license = "MIT"
description = "A modern, simple TCP tunnel in Rust that exposes local ports to a remote server, bypassing standard NAT connection firewalls." description = "A modern, simple TCP tunnel in Rust that exposes local ports to a remote server, bypassing standard NAT connection firewalls."
@@ -17,14 +17,14 @@ path = "src/main.rs"
[dependencies] [dependencies]
anyhow = { version = "1.0.56", features = ["backtrace"] } anyhow = { version = "1.0.56", features = ["backtrace"] }
clap = { version = "3.1.8", features = ["derive"] } clap = { version = "3.1.8", features = ["derive", "env"] }
dashmap = "5.2.0" dashmap = "5.2.0"
hex = "0.4.3" hex = "0.4.3"
hmac = "0.12.1" hmac = "0.12.1"
serde = { version = "1.0.136", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79" serde_json = "1.0.79"
sha2 = "0.10.2" sha2 = "0.10.2"
tokio = { version = "1.17.0", features = ["full"] } tokio = { version = "1.17.0", features = ["rt-multi-thread", "io-util", "macros", "net", "time"] }
tracing = "0.1.32" tracing = "0.1.32"
tracing-subscriber = "0.3.10" tracing-subscriber = "0.3.10"
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.2", features = ["serde", "v4"] }
@@ -32,3 +32,4 @@ uuid = { version = "0.8.2", features = ["serde", "v4"] }
[dev-dependencies] [dev-dependencies]
lazy_static = "1.4.0" lazy_static = "1.4.0"
rstest = "0.12.0" rstest = "0.12.0"
tokio = { version = "1.17.0", features = ["sync"] }

View File

@@ -47,26 +47,27 @@ You can forward a port on your local machine by using the `bore local` command.
bore local 5000 --to bore.pub bore local 5000 --to bore.pub
``` ```
You can optionally pass in a `--port` option to pick a specific port on the remote to expose, although the command will fail if this port is not available. You can optionally pass in a `--port` option to pick a specific port on the remote to expose, although the command will fail if this port is not available. Also, passing `--local-host` allows you to expose a different host on your local area network besides the loopback address `localhost`.
The full options are shown below. The full options are shown below.
```shell ```shell
bore-local 0.2.3 bore-local 0.3.0
Starts a local proxy to the remote server Starts a local proxy to the remote server
USAGE: USAGE:
bore local [OPTIONS] --to <TO> <LOCAL_PORT> bore local [OPTIONS] --to <TO> <LOCAL_PORT>
ARGS: ARGS:
<LOCAL_PORT> The local port to listen on <LOCAL_PORT> The local port to expose
OPTIONS: OPTIONS:
-h, --help Print help information -h, --help Print help information
-p, --port <PORT> Optional port on the remote server to select [default: 0] -l, --local-host <HOST> The local host to expose [default: localhost]
-s, --secret <SECRET> Optional secret for authentication -p, --port <PORT> Optional port on the remote server to select [default: 0]
-t, --to <TO> Address of the remote server to expose local ports to -s, --secret <SECRET> Optional secret for authentication [env: BORE_SECRET]
-V, --version Print version information -t, --to <TO> Address of the remote server to expose local ports to
-V, --version Print version information
``` ```
### Self-Hosting ### Self-Hosting
@@ -82,7 +83,7 @@ That's all it takes! After the server starts running at a given address, you can
The full options for the `bore server` command are shown below. The full options for the `bore server` command are shown below.
```shell ```shell
bore-server 0.2.3 bore-server 0.3.0
Runs the remote proxy server Runs the remote proxy server
USAGE: USAGE:
@@ -91,7 +92,7 @@ USAGE:
OPTIONS: OPTIONS:
-h, --help Print help information -h, --help Print help information
--min-port <MIN_PORT> Minimum TCP port number to accept [default: 1024] --min-port <MIN_PORT> Minimum TCP port number to accept [default: 1024]
-s, --secret <SECRET> Optional secret for authentication -s, --secret <SECRET> Optional secret for authentication [env: BORE_SECRET]
-V, --version Print version information -V, --version Print version information
``` ```
@@ -115,6 +116,8 @@ bore server --secret my_secret_string
bore local <LOCAL_PORT> --to <TO> --secret my_secret_string bore local <LOCAL_PORT> --to <TO> --secret my_secret_string
``` ```
If a secret is not present in the arguments, `bore` will also attempt to read from the `BORE_SECRET` environment variable.
## Acknowledgements ## Acknowledgements
Created by Eric Zhang ([@ekzhang1](https://twitter.com/ekzhang1)). Licensed under the [MIT license](LICENSE). Created by Eric Zhang ([@ekzhang1](https://twitter.com/ekzhang1)). Licensed under the [MIT license](LICENSE).

View File

@@ -21,6 +21,9 @@ pub struct Client {
/// Destination address of the server. /// Destination address of the server.
to: String, to: String,
// Local host that is forwarded.
local_host: String,
/// Local port that is forwarded. /// Local port that is forwarded.
local_port: u16, local_port: u16,
@@ -33,7 +36,13 @@ pub struct Client {
impl Client { impl Client {
/// Create a new client. /// Create a new client.
pub async fn new(local_port: u16, to: &str, port: u16, secret: Option<&str>) -> Result<Self> { pub async fn new(
local_host: &str,
local_port: u16,
to: &str,
port: u16,
secret: Option<&str>,
) -> Result<Self> {
let mut stream = BufReader::new(connect_with_timeout(to, CONTROL_PORT).await?); let mut stream = BufReader::new(connect_with_timeout(to, CONTROL_PORT).await?);
let auth = secret.map(Authenticator::new); let auth = secret.map(Authenticator::new);
@@ -57,6 +66,7 @@ impl Client {
Ok(Client { Ok(Client {
conn: Some(stream), conn: Some(stream),
to: to.to_string(), to: to.to_string(),
local_host: local_host.to_string(),
local_port, local_port,
remote_port, remote_port,
auth, auth,
@@ -106,7 +116,7 @@ impl Client {
} }
send_json(&mut remote_conn, ClientMessage::Accept(id)).await?; send_json(&mut remote_conn, ClientMessage::Accept(id)).await?;
let local_conn = connect_with_timeout("localhost", self.local_port).await?; let local_conn = connect_with_timeout(&self.local_host, self.local_port).await?;
proxy(local_conn, remote_conn).await?; proxy(local_conn, remote_conn).await?;
Ok(()) Ok(())
} }

View File

@@ -14,9 +14,13 @@ struct Args {
enum Command { enum Command {
/// Starts a local proxy to the remote server. /// Starts a local proxy to the remote server.
Local { Local {
/// The local port to listen on. /// The local port to expose.
local_port: u16, local_port: u16,
/// The local host to expose.
#[clap(short, long, value_name = "HOST", default_value = "localhost")]
local_host: String,
/// Address of the remote server to expose local ports to. /// Address of the remote server to expose local ports to.
#[clap(short, long)] #[clap(short, long)]
to: String, to: String,
@@ -26,7 +30,7 @@ enum Command {
port: u16, port: u16,
/// Optional secret for authentication. /// Optional secret for authentication.
#[clap(short, long)] #[clap(short, long, env = "BORE_SECRET", hide_env_values = true)]
secret: Option<String>, secret: Option<String>,
}, },
@@ -37,24 +41,22 @@ enum Command {
min_port: u16, min_port: u16,
/// Optional secret for authentication. /// Optional secret for authentication.
#[clap(short, long)] #[clap(short, long, env = "BORE_SECRET", hide_env_values = true)]
secret: Option<String>, secret: Option<String>,
}, },
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn run(command: Command) -> Result<()> {
tracing_subscriber::fmt::init(); match command {
let args = Args::parse();
match args.command {
Command::Local { Command::Local {
local_host,
local_port, local_port,
to, to,
port, port,
secret, secret,
} => { } => {
let client = Client::new(local_port, &to, port, secret.as_deref()).await?; let client = Client::new(&local_host, local_port, &to, port, secret.as_deref()).await?;
client.listen().await?; client.listen().await?;
} }
Command::Server { min_port, secret } => { Command::Server { min_port, secret } => {
@@ -64,3 +66,8 @@ async fn main() -> Result<()> {
Ok(()) Ok(())
} }
fn main() -> Result<()> {
tracing_subscriber::fmt::init();
run(Args::parse().command)
}

View File

@@ -24,7 +24,8 @@ async fn spawn_server(secret: Option<&str>) {
/// Spawns a client with randomly assigned ports, returning the listener and remote address. /// Spawns a client with randomly assigned ports, returning the listener and remote address.
async fn spawn_client(secret: Option<&str>) -> Result<(TcpListener, SocketAddr)> { async fn spawn_client(secret: Option<&str>) -> Result<(TcpListener, SocketAddr)> {
let listener = TcpListener::bind("localhost:0").await?; let listener = TcpListener::bind("localhost:0").await?;
let client = Client::new(listener.local_addr()?.port(), "localhost", 0, secret).await?; let local_port = listener.local_addr()?.port();
let client = Client::new("localhost", local_port, "localhost", 0, secret).await?;
let remote_addr = ([0, 0, 0, 0], client.remote_port()).into(); let remote_addr = ([0, 0, 0, 0], client.remote_port()).into();
tokio::spawn(client.listen()); tokio::spawn(client.listen());
Ok((listener, remote_addr)) Ok((listener, remote_addr))
@@ -83,7 +84,7 @@ async fn mismatched_secret(
async fn invalid_address() -> Result<()> { async fn invalid_address() -> Result<()> {
// We don't need the serial guard for this test because it doesn't create a server. // We don't need the serial guard for this test because it doesn't create a server.
async fn check_address(to: &str, use_secret: bool) -> Result<()> { async fn check_address(to: &str, use_secret: bool) -> Result<()> {
match Client::new(5000, to, 0, use_secret.then(|| "a secret")).await { match Client::new("localhost", 5000, to, 0, use_secret.then(|| "a secret")).await {
Ok(_) => Err(anyhow!("expected error for {to}, use_secret={use_secret}")), Ok(_) => Err(anyhow!("expected error for {to}, use_secret={use_secret}")),
Err(_) => Ok(()), Err(_) => Ok(()),
} }