Skip to content

Commit

Permalink
upgrade to v0.3.0-pre2
Browse files Browse the repository at this point in the history
- add socks5 reverse proxy
- code clean
  • Loading branch information
wlh320 committed May 30, 2022
1 parent 33a023f commit 2daf952
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 80 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "portguard"
version = "0.3.0-pre"
version = "0.3.0-pre2"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# portguard

An encrypted port forwarding tool that just works like ssh tunnel, but **Zero Config** for client.
A port forwarding tool with encryption and authentication that just works like ssh tunnel, but **Zero Config** for client.

It is currently a simple project and the author is not familiar with security, we take no responsibility for any security flaws.

Expand All @@ -26,11 +26,11 @@ remote1 <-> client <-> server <-> remote2
```

1. Server listens on public IP and a public port.
2. Remote can be a remote port (google.com:443), a local port (127.0.0.1:xxxx), or dynamic.
2. Remote can be a remote port (google.com:443), a local port (127.0.0.1:xxxx), or dynamic (socks5).
3. Client works in any of the following modes:
- `ssh -L` mode: visit static port of remote2 through server.
- `ssh -D` mode: visit dynamic remote2 through server's builtin socks5 server.
- `ssh -R` mode: expose port of remote1 to server and register a _service id_.
- `ssh -R` mode: expose remote1 (port or dynamic) to server and register a _service id_.
- `ssh -R visitor` mode: only clients in this mode with same _service id_ can visit the exposed port.
4. Client and server handshake using `Noise_IK_25519_ChaChaPoly_BLAKE2s`.
5. Data transferred with encryption between client and server.
Expand Down Expand Up @@ -119,15 +119,17 @@ remote1 <-> client <-> server <-> remote2

## TODO

- [ ] I'm not familar with Noise protocol, now in my code every connection between client and server needs to handshake (except reverse proxy mode).
- [x] ~~I'm not familar with Noise protocol, now in my code every connection between client and server needs to handshake (except reverse proxy mode).~~ Now I think it is a feature.
- [x] Set remote address per client.
- [ ] Benchmark and improve performance.
- [ ] When will a connection be closed? Put it in logs.
- [ ] Test.

## Changelog

### v0.3.0-pre
### v0.3.0-pre2
- add `ssh -R` feature using yamux (It just works, recommend to use existing projects like frp or rathole with `-L` mode)
- add `ssh -R` + `ssh -D` feature (socks5 reverse proxy)
- more tests needed

### v0.2.1
Expand Down
16 changes: 9 additions & 7 deletions README_zh.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# portguard

一个功能类似 ssh 隧道 的加密端口转发工具,但客户端**零配置**.
一个功能类似 ssh 隧道 的端口加密转发工具,但客户端**零配置**.

作者还是个新手,不为代码中可能出现的安全缺陷负责.

Expand All @@ -24,11 +24,11 @@
```

1. 服务端绑定公网IP的一个端口.
2. 远端可以是其他的公网端口(google.com:443), 本地端口(127.0.0.1:port), 或者动态端口.
2. 远端可以是其他的公网端口(google.com:443), 本地端口(127.0.0.1:port), 或者动态端口(socks5).
3. 客户端可以工作于以下任一模式:
- `ssh -L`模式:通过服务端访问远端2的静态端口。
- `ssh -D`模式:通过服务端内置的socks5服务访问动态的远端2。
- `ssh -R`模式:将远端1的端口暴露给服务端并注册一个 _service id_。
- `ssh -R`模式:将远端1(固定端口或内建socks5)暴露给服务端并注册一个 _service id_。
- `ssh -R visitor` 模式:只有在此模式下具有相同 _service id_ 的客户端才能访问暴露的端口。
4. 客户端与服务端通过`Noise_IK_25519_ChaChaPoly_BLAKE2s`协议握手.
5. 随后客户端与服务端之间的流量加密传输.
Expand Down Expand Up @@ -121,15 +121,17 @@

## TODO

- [ ] I'm not familar with Noise protocol, now in my code every connection between client and server needs to handshake.
- [x] ~~I'm not familar with Noise protocol, now in my code every connection between client and server needs to handshake (except reverse proxy mode).~~ Now I think it is a feature.
- [x] Set remote address per client.
- [ ] Improve performance.
- [ ] Benchmark and improve performance.
- [ ] When will a connection be closed? Put it in logs.
- [ ] Test.

## 更新日志

### v0.3.0-pre
- 添加 `ssh -R` 功能(只是可以工作,建议使用现有项目,如 frp 或 rathole 来配合 `-L` 模式)
### v0.3.0-pre2
- 添加 `ssh -R` 功能(只是可以工作,建议使用现有项目,如 frp 或 rathole, 配合 `-L` 模式使用)
- 添加 `ssh -R` + `ssh -D` 功能(socks5 反向代理)
- 需要更多测试

### v0.2.1
Expand Down
45 changes: 33 additions & 12 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::consts::{CONF_BUF_LEN, PATTERN};
use crate::{proxy};
use crate::proxy;
use bincode::Options;
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
use fast_socks5::server::Socks5Socket;
use futures::FutureExt;
use log;
use serde::{Deserialize, Serialize};
use snowstorm::NoiseStream;
Expand All @@ -16,7 +18,7 @@ use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientConfig {
pub server_addr: SocketAddr,
pub target_addr: String,
pub target_addr: String, // TODO: should be Remote::Target, but it is untagged, cannot be decoded by bincode
pub server_pubkey: Vec<u8>,
pub client_prikey: Vec<u8>,
}
Expand Down Expand Up @@ -103,12 +105,15 @@ impl Client {
if let Some(addr) = server_addr {
conf.server_addr = addr;
}
// must be valid address
assert!(conf.target_addr.parse::<SocketAddr>().is_ok());
// must be valid address: socket addr or "socks5"
assert!(
conf.target_addr.to_lowercase() == "socks5"
|| conf.target_addr.parse::<SocketAddr>().is_ok()
);
let shared_conf = Arc::new(conf);
// log information
log::info!("Client exposing service on: {:?}", shared_conf.target_addr);
log::info!("Portguard server on: {:?}", &shared_conf.server_addr);
log::info!("Client exposing service on: {}", shared_conf.target_addr);
log::info!("Portguard server on: {}", shared_conf.server_addr);
// start reverse proxy
loop {
let conf = shared_conf.clone();
Expand All @@ -120,7 +125,7 @@ impl Client {
}
}

pub async fn make_reverse_proxy_conn(&self, conf: &ClientConfig) -> Result<(), Box<dyn Error>> {
async fn make_reverse_proxy_conn(&self, conf: &ClientConfig) -> Result<(), Box<dyn Error>> {
// make connection with server
let initiator = snowstorm::Builder::new(PATTERN.parse()?)
.remote_public_key(&conf.server_pubkey)
Expand All @@ -143,18 +148,34 @@ impl Client {
}
Ok(())
}

/// handle yamux connection requests
async fn handle_reverse_client_connection(
inbound: yamux::Stream,
conf: &ClientConfig,
) -> Result<(), Box<dyn Error>> {
log::info!("New incoming request, stream id {:?}", inbound.id());
let expose_addr = &conf.target_addr.parse::<SocketAddr>().expect("Invalid target address");
let outbound = TcpStream::connect(expose_addr).await?;
proxy::transfer_and_log_error(inbound.compat(), outbound).await;
if &conf.target_addr.to_lowercase() == "socks5" {
// target is socks5
let config = fast_socks5::server::Config::default();
let socket = Socks5Socket::new(inbound.compat(), Arc::new(config));
let transfer = socket.upgrade_to_socks5().map(|r| {
if let Err(e) = r {
log::warn!("Transfer error occured. error={}", e);
}
});
transfer.await;
} else {
// target is socket addr
let expose_addr = &conf
.target_addr
.parse::<SocketAddr>()
.expect("Invalid target address");
let outbound = TcpStream::connect(expose_addr).await?;
proxy::transfer_and_log_error(inbound.compat(), outbound).await;
}
Ok(())
}

/// list current client public key
pub fn list_pubkey(server: bool) -> Result<(), Box<dyn Error>> {
let conf: ClientConfig = bincode::options()
.with_limit(CONF_BUF_LEN as u64)
Expand Down
55 changes: 37 additions & 18 deletions src/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
};

// untagged for unit variant of Enum
// solution from https://github.com/serde-rs/serde/issues/1560
// solution from <https://github.com/serde-rs/serde/issues/1560>
// TODO: any better solutions ???
macro_rules! named_unit_variant {
($variant:ident) => {
Expand Down Expand Up @@ -43,35 +43,57 @@ macro_rules! named_unit_variant {
mod strings {
named_unit_variant!(socks5);
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Target {
/// target address is a socket address
Addr(SocketAddr),
/// target address is builtin socks5
#[serde(with = "strings::socks5")]
Socks5,
}
impl ToString for Target {
fn to_string(&self) -> String {
match self {
Target::Addr(a) => a.to_string(),
Target::Socks5 => String::from("socks5"),
}
}
}

/// Type for identifying target remote address
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Remote {
/// visitor of remote address, for `ssh -L`
Addr(SocketAddr),
/// visitor of remote address, for `ssh -L` or
/// visitor of builtin socks5 server, for `ssh -D`
#[serde(with = "strings::socks5")]
Socks5,
Proxy(Target),
/// visitor of reverse proxy, need service id, for `ssh -R` visitor
Service(usize),
/// client of reverse proxy, need addr and service id, for ssh -R` client
RProxy(SocketAddr, usize),
RProxy(Target, usize),
}

impl Remote {
/// if input only target, client can be addr or socks5
fn from_target(target: &str) -> Result<Remote, AddrParseError> {
if target.to_lowercase() == "socks5" {
Ok(Remote::Socks5)
Ok(Remote::Proxy(Target::Socks5))
} else {
target.parse::<SocketAddr>().map(Remote::Addr)
target
.parse::<SocketAddr>()
.map(Target::Addr)
.map(Remote::Proxy)
}
}
/// if input both target and id, client is rclient
fn from_target_and_id(target: &str, id: usize) -> Result<Remote, Box<dyn Error>> {
let addr = target.parse::<SocketAddr>()?;
Ok(Remote::RProxy(addr, id))
if target.to_lowercase() == "socks5" {
Ok(Remote::RProxy(Target::Socks5, id))
} else {
let addr = target.parse::<SocketAddr>()?;
Ok(Remote::RProxy(Target::Addr(addr), id))
}
}
/// if input only id, client is rvisitor
fn from_id(id: usize) -> Remote {
Expand All @@ -80,11 +102,9 @@ impl Remote {

pub fn try_parse(target: Option<String>, id: Option<usize>) -> Result<Remote, Box<dyn Error>> {
match target {
None => {
match id {
Some(id) => Ok(Remote::from_id(id)),
None => Err("No target address")?
}
None => match id {
Some(id) => Ok(Remote::from_id(id)),
None => Err("No target address")?,
},
Some(target) => Ok(match id {
None => Remote::from_target(&target)?,
Expand All @@ -97,10 +117,9 @@ impl Remote {
impl ToString for Remote {
fn to_string(&self) -> String {
match self {
Remote::Addr(a) => a.to_string(),
Remote::Socks5 => String::from("socks5"),
Remote::Proxy(t) => t.to_string(),
Remote::Service(id) => format!("(sid {})", id),
Remote::RProxy(a, _id) => a.to_string()
Remote::RProxy(a, _id) => a.to_string(),
}
}
}
Loading

0 comments on commit 2daf952

Please sign in to comment.