mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-09 18:18:31 -04:00
WIP: Remote port forwarding
This commit is contained in:
parent
def5f22d3c
commit
c19af07342
2 changed files with 133 additions and 32 deletions
161
src/config.rs
161
src/config.rs
|
@ -9,9 +9,12 @@ use anyhow::Context;
|
||||||
use boringtun::crypto::{X25519PublicKey, X25519SecretKey};
|
use boringtun::crypto::{X25519PublicKey, X25519SecretKey};
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
|
|
||||||
|
const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1";
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub(crate) port_forwards: Vec<PortForwardConfig>,
|
pub(crate) port_forwards: Vec<PortForwardConfig>,
|
||||||
|
pub(crate) remote_port_forwards: Vec<PortForwardConfig>,
|
||||||
pub(crate) private_key: Arc<X25519SecretKey>,
|
pub(crate) private_key: Arc<X25519SecretKey>,
|
||||||
pub(crate) endpoint_public_key: Arc<X25519PublicKey>,
|
pub(crate) endpoint_public_key: Arc<X25519PublicKey>,
|
||||||
pub(crate) endpoint_addr: SocketAddr,
|
pub(crate) endpoint_addr: SocketAddr,
|
||||||
|
@ -103,7 +106,23 @@ impl Config {
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.long("pcap")
|
.long("pcap")
|
||||||
.env("ONETUN_PCAP")
|
.env("ONETUN_PCAP")
|
||||||
.help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file.")
|
.help("Decrypts and captures IP packets on the WireGuard tunnel to a given output file."),
|
||||||
|
Arg::with_name("remote")
|
||||||
|
.required(false)
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true)
|
||||||
|
.long("remote")
|
||||||
|
.short("r")
|
||||||
|
.help("Remote port forward configurations. The format of each argument is <src_port>:<dst_host>:<dst_port>[:TCP,UDP,...], \
|
||||||
|
where <src_port> is the port the other peers will reach the server with, <dst_host> is the IP to forward to, and <dst_port> is the port to forward to. \
|
||||||
|
The <src_port> will be bound on onetun's peer IP, as specified by --source-peer-ip. If you pass a different value for <src_host> here, it will be rejected.\n\
|
||||||
|
Note: <dst_host>:<dst_port> must be reachable by onetun. If referring to another WireGuard peer, use --bridge instead (not supported yet).\n\
|
||||||
|
Environment variables of the form 'ONETUN_REMOTE_PORT_FORWARD_[#]' are also accepted, where [#] starts at 1.\n\
|
||||||
|
Examples:\n\
|
||||||
|
\t--remote 8080:localhost:8081:TCP,UDP\n\
|
||||||
|
\t--remote 8080:[::1]:8081:TCP\n\
|
||||||
|
\t--remote 8080:google.com:80\
|
||||||
|
"),
|
||||||
]).get_matches();
|
]).get_matches();
|
||||||
|
|
||||||
// Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs
|
// Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs
|
||||||
|
@ -120,14 +139,11 @@ impl Config {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if port_forward_strings.is_empty() {
|
|
||||||
return Err(anyhow::anyhow!("No port forward configurations given."));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse `PORT_FORWARD` strings into `PortForwardConfig`
|
// Parse `PORT_FORWARD` strings into `PortForwardConfig`
|
||||||
let port_forwards: anyhow::Result<Vec<Vec<PortForwardConfig>>> = port_forward_strings
|
let port_forwards: anyhow::Result<Vec<Vec<PortForwardConfig>>> = port_forward_strings
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| PortForwardConfig::from_notation(&s))
|
.map(|s| PortForwardConfig::from_notation(&s, DEFAULT_PORT_FORWARD_SOURCE))
|
||||||
.collect();
|
.collect();
|
||||||
let port_forwards: Vec<PortForwardConfig> = port_forwards
|
let port_forwards: Vec<PortForwardConfig> = port_forwards
|
||||||
.with_context(|| "Failed to parse port forward config")?
|
.with_context(|| "Failed to parse port forward config")?
|
||||||
|
@ -135,6 +151,52 @@ impl Config {
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Read source-peer-ip
|
||||||
|
let source_peer_ip = parse_ip(matches.value_of("source-peer-ip"))
|
||||||
|
.with_context(|| "Invalid source peer IP")?;
|
||||||
|
|
||||||
|
// Combined `remote` arg and `ONETUN_REMOTE_PORT_FORWARD_#` envs
|
||||||
|
let mut port_forward_strings = HashSet::new();
|
||||||
|
if let Some(values) = matches.values_of("remote") {
|
||||||
|
for value in values {
|
||||||
|
port_forward_strings.insert(value.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for n in 1.. {
|
||||||
|
if let Ok(env) = std::env::var(format!("ONETUN_REMOTE_PORT_FORWARD_{}", n)) {
|
||||||
|
port_forward_strings.insert(env);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Parse `PORT_FORWARD` strings into `PortForwardConfig`
|
||||||
|
let remote_port_forwards: anyhow::Result<Vec<Vec<PortForwardConfig>>> =
|
||||||
|
port_forward_strings
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| {
|
||||||
|
PortForwardConfig::from_notation(
|
||||||
|
&s,
|
||||||
|
matches.value_of("source-peer-ip").unwrap(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut remote_port_forwards: Vec<PortForwardConfig> = remote_port_forwards
|
||||||
|
.with_context(|| "Failed to parse remote port forward config")?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
for port_forward in remote_port_forwards.iter_mut() {
|
||||||
|
if port_forward.source.ip() != source_peer_ip {
|
||||||
|
return Err(anyhow::anyhow!("Remote port forward config <src_host> must match --source-peer-ip ({}), or be omitted.", source_peer_ip));
|
||||||
|
}
|
||||||
|
port_forward.source = SocketAddr::from((source_peer_ip, port_forward.source.port()));
|
||||||
|
port_forward.remote = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if port_forwards.is_empty() && remote_port_forwards.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("No port forward configurations given."));
|
||||||
|
}
|
||||||
|
|
||||||
// Read private key from file or CLI argument
|
// Read private key from file or CLI argument
|
||||||
let (group_readable, world_readable) = matches
|
let (group_readable, world_readable) = matches
|
||||||
.value_of("private-key-file")
|
.value_of("private-key-file")
|
||||||
|
@ -165,6 +227,7 @@ impl Config {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
port_forwards,
|
port_forwards,
|
||||||
|
remote_port_forwards,
|
||||||
private_key: Arc::new(
|
private_key: Arc::new(
|
||||||
parse_private_key(&private_key).with_context(|| "Invalid private key")?,
|
parse_private_key(&private_key).with_context(|| "Invalid private key")?,
|
||||||
),
|
),
|
||||||
|
@ -174,8 +237,7 @@ impl Config {
|
||||||
),
|
),
|
||||||
endpoint_addr: parse_addr(matches.value_of("endpoint-addr"))
|
endpoint_addr: parse_addr(matches.value_of("endpoint-addr"))
|
||||||
.with_context(|| "Invalid endpoint address")?,
|
.with_context(|| "Invalid endpoint address")?,
|
||||||
source_peer_ip: parse_ip(matches.value_of("source-peer-ip"))
|
source_peer_ip,
|
||||||
.with_context(|| "Invalid source peer IP")?,
|
|
||||||
keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive"))
|
keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive"))
|
||||||
.with_context(|| "Invalid keep-alive value")?,
|
.with_context(|| "Invalid keep-alive value")?,
|
||||||
max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit"))
|
max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit"))
|
||||||
|
@ -256,6 +318,8 @@ pub struct PortForwardConfig {
|
||||||
pub destination: SocketAddr,
|
pub destination: SocketAddr,
|
||||||
/// The transport protocol to use for the port (Layer 4).
|
/// The transport protocol to use for the port (Layer 4).
|
||||||
pub protocol: PortProtocol,
|
pub protocol: PortProtocol,
|
||||||
|
/// Whether this is a remote port forward.
|
||||||
|
pub remote: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PortForwardConfig {
|
impl PortForwardConfig {
|
||||||
|
@ -278,7 +342,7 @@ impl PortForwardConfig {
|
||||||
/// - IPv6 addresses must be prefixed with `[` and suffixed with `]`. Example: `[::1]`.
|
/// - IPv6 addresses must be prefixed with `[` and suffixed with `]`. Example: `[::1]`.
|
||||||
/// - Any `u16` is accepted as `src_port` and `dst_port`
|
/// - Any `u16` is accepted as `src_port` and `dst_port`
|
||||||
/// - Specifying protocols (`PROTO1,PROTO2,...`) is optional and defaults to `TCP`. Values must be separated by commas.
|
/// - Specifying protocols (`PROTO1,PROTO2,...`) is optional and defaults to `TCP`. Values must be separated by commas.
|
||||||
pub fn from_notation(s: &str) -> anyhow::Result<Vec<PortForwardConfig>> {
|
pub fn from_notation(s: &str, default_source: &str) -> anyhow::Result<Vec<PortForwardConfig>> {
|
||||||
mod parsers {
|
mod parsers {
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::is_not;
|
use nom::bytes::complete::is_not;
|
||||||
|
@ -356,7 +420,7 @@ impl PortForwardConfig {
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
let source = (
|
let source = (
|
||||||
src_addr.0.unwrap_or("127.0.0.1"),
|
src_addr.0.unwrap_or(default_source),
|
||||||
src_addr
|
src_addr
|
||||||
.1
|
.1
|
||||||
.parse::<u16>()
|
.parse::<u16>()
|
||||||
|
@ -396,6 +460,7 @@ impl PortForwardConfig {
|
||||||
source,
|
source,
|
||||||
destination,
|
destination,
|
||||||
protocol,
|
protocol,
|
||||||
|
remote: false,
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
@ -451,18 +516,23 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_port_forward_config_1() {
|
fn test_parse_port_forward_config_1() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PortForwardConfig::from_notation("192.168.0.1:8080:192.168.4.1:8081:TCP,UDP")
|
PortForwardConfig::from_notation(
|
||||||
.expect("Failed to parse"),
|
"192.168.0.1:8080:192.168.4.1:8081:TCP,UDP",
|
||||||
|
DEFAULT_PORT_FORWARD_SOURCE
|
||||||
|
)
|
||||||
|
.expect("Failed to parse"),
|
||||||
vec![
|
vec![
|
||||||
PortForwardConfig {
|
PortForwardConfig {
|
||||||
source: SocketAddr::from_str("192.168.0.1:8080").unwrap(),
|
source: SocketAddr::from_str("192.168.0.1:8080").unwrap(),
|
||||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||||
protocol: PortProtocol::Tcp
|
protocol: PortProtocol::Tcp,
|
||||||
|
remote: false,
|
||||||
},
|
},
|
||||||
PortForwardConfig {
|
PortForwardConfig {
|
||||||
source: SocketAddr::from_str("192.168.0.1:8080").unwrap(),
|
source: SocketAddr::from_str("192.168.0.1:8080").unwrap(),
|
||||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||||
protocol: PortProtocol::Udp
|
protocol: PortProtocol::Udp,
|
||||||
|
remote: false,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -471,12 +541,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_port_forward_config_2() {
|
fn test_parse_port_forward_config_2() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PortForwardConfig::from_notation("192.168.0.1:8080:192.168.4.1:8081:TCP")
|
PortForwardConfig::from_notation(
|
||||||
.expect("Failed to parse"),
|
"192.168.0.1:8080:192.168.4.1:8081:TCP",
|
||||||
|
DEFAULT_PORT_FORWARD_SOURCE
|
||||||
|
)
|
||||||
|
.expect("Failed to parse"),
|
||||||
vec![PortForwardConfig {
|
vec![PortForwardConfig {
|
||||||
source: SocketAddr::from_str("192.168.0.1:8080").unwrap(),
|
source: SocketAddr::from_str("192.168.0.1:8080").unwrap(),
|
||||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||||
protocol: PortProtocol::Tcp
|
protocol: PortProtocol::Tcp,
|
||||||
|
remote: false,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -484,12 +558,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_port_forward_config_3() {
|
fn test_parse_port_forward_config_3() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PortForwardConfig::from_notation("0.0.0.0:8080:192.168.4.1:8081")
|
PortForwardConfig::from_notation(
|
||||||
.expect("Failed to parse"),
|
"0.0.0.0:8080:192.168.4.1:8081",
|
||||||
|
DEFAULT_PORT_FORWARD_SOURCE
|
||||||
|
)
|
||||||
|
.expect("Failed to parse"),
|
||||||
vec![PortForwardConfig {
|
vec![PortForwardConfig {
|
||||||
source: SocketAddr::from_str("0.0.0.0:8080").unwrap(),
|
source: SocketAddr::from_str("0.0.0.0:8080").unwrap(),
|
||||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||||
protocol: PortProtocol::Tcp
|
protocol: PortProtocol::Tcp,
|
||||||
|
remote: false,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -497,12 +575,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_port_forward_config_4() {
|
fn test_parse_port_forward_config_4() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PortForwardConfig::from_notation("[::1]:8080:192.168.4.1:8081")
|
PortForwardConfig::from_notation(
|
||||||
.expect("Failed to parse"),
|
"[::1]:8080:192.168.4.1:8081",
|
||||||
|
DEFAULT_PORT_FORWARD_SOURCE
|
||||||
|
)
|
||||||
|
.expect("Failed to parse"),
|
||||||
vec![PortForwardConfig {
|
vec![PortForwardConfig {
|
||||||
source: SocketAddr::from_str("[::1]:8080").unwrap(),
|
source: SocketAddr::from_str("[::1]:8080").unwrap(),
|
||||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||||
protocol: PortProtocol::Tcp
|
protocol: PortProtocol::Tcp,
|
||||||
|
remote: false,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -510,11 +592,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_port_forward_config_5() {
|
fn test_parse_port_forward_config_5() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PortForwardConfig::from_notation("8080:192.168.4.1:8081").expect("Failed to parse"),
|
PortForwardConfig::from_notation("8080:192.168.4.1:8081", DEFAULT_PORT_FORWARD_SOURCE)
|
||||||
|
.expect("Failed to parse"),
|
||||||
vec![PortForwardConfig {
|
vec![PortForwardConfig {
|
||||||
source: SocketAddr::from_str("127.0.0.1:8080").unwrap(),
|
source: SocketAddr::from_str("127.0.0.1:8080").unwrap(),
|
||||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||||
protocol: PortProtocol::Tcp
|
protocol: PortProtocol::Tcp,
|
||||||
|
remote: false,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -522,11 +606,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_port_forward_config_6() {
|
fn test_parse_port_forward_config_6() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PortForwardConfig::from_notation("8080:192.168.4.1:8081:TCP").expect("Failed to parse"),
|
PortForwardConfig::from_notation(
|
||||||
|
"8080:192.168.4.1:8081:TCP",
|
||||||
|
DEFAULT_PORT_FORWARD_SOURCE
|
||||||
|
)
|
||||||
|
.expect("Failed to parse"),
|
||||||
vec![PortForwardConfig {
|
vec![PortForwardConfig {
|
||||||
source: SocketAddr::from_str("127.0.0.1:8080").unwrap(),
|
source: SocketAddr::from_str("127.0.0.1:8080").unwrap(),
|
||||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||||
protocol: PortProtocol::Tcp
|
protocol: PortProtocol::Tcp,
|
||||||
|
remote: false,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -534,12 +623,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_port_forward_config_7() {
|
fn test_parse_port_forward_config_7() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PortForwardConfig::from_notation("localhost:8080:192.168.4.1:8081")
|
PortForwardConfig::from_notation(
|
||||||
.expect("Failed to parse"),
|
"localhost:8080:192.168.4.1:8081",
|
||||||
|
DEFAULT_PORT_FORWARD_SOURCE
|
||||||
|
)
|
||||||
|
.expect("Failed to parse"),
|
||||||
vec![PortForwardConfig {
|
vec![PortForwardConfig {
|
||||||
source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(),
|
source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(),
|
||||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||||
protocol: PortProtocol::Tcp
|
protocol: PortProtocol::Tcp,
|
||||||
|
remote: false,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -547,12 +640,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_port_forward_config_8() {
|
fn test_parse_port_forward_config_8() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PortForwardConfig::from_notation("localhost:8080:localhost:8081:TCP")
|
PortForwardConfig::from_notation(
|
||||||
.expect("Failed to parse"),
|
"localhost:8080:localhost:8081:TCP",
|
||||||
|
DEFAULT_PORT_FORWARD_SOURCE
|
||||||
|
)
|
||||||
|
.expect("Failed to parse"),
|
||||||
vec![PortForwardConfig {
|
vec![PortForwardConfig {
|
||||||
source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(),
|
source: "localhost:8080".to_socket_addrs().unwrap().next().unwrap(),
|
||||||
destination: "localhost:8081".to_socket_addrs().unwrap().next().unwrap(),
|
destination: "localhost:8081".to_socket_addrs().unwrap().next().unwrap(),
|
||||||
protocol: PortProtocol::Tcp
|
protocol: PortProtocol::Tcp,
|
||||||
|
remote: false,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,10 @@ async fn main() -> anyhow::Result<()> {
|
||||||
.port_forwards
|
.port_forwards
|
||||||
.iter()
|
.iter()
|
||||||
.any(|pf| pf.protocol == PortProtocol::Udp)
|
.any(|pf| pf.protocol == PortProtocol::Udp)
|
||||||
|
|| config
|
||||||
|
.remote_port_forwards
|
||||||
|
.iter()
|
||||||
|
.any(|pf| pf.protocol == PortProtocol::Udp)
|
||||||
{
|
{
|
||||||
// UDP device
|
// UDP device
|
||||||
let bus = bus.clone();
|
let bus = bus.clone();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue