mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-09 12: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
149
src/config.rs
149
src/config.rs
|
@ -9,9 +9,12 @@ use anyhow::Context;
|
|||
use boringtun::crypto::{X25519PublicKey, X25519SecretKey};
|
||||
use clap::{App, Arg};
|
||||
|
||||
const DEFAULT_PORT_FORWARD_SOURCE: &str = "127.0.0.1";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
pub(crate) port_forwards: Vec<PortForwardConfig>,
|
||||
pub(crate) remote_port_forwards: Vec<PortForwardConfig>,
|
||||
pub(crate) private_key: Arc<X25519SecretKey>,
|
||||
pub(crate) endpoint_public_key: Arc<X25519PublicKey>,
|
||||
pub(crate) endpoint_addr: SocketAddr,
|
||||
|
@ -103,7 +106,23 @@ impl Config {
|
|||
.takes_value(true)
|
||||
.long("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();
|
||||
|
||||
// Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs
|
||||
|
@ -120,14 +139,11 @@ impl Config {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if port_forward_strings.is_empty() {
|
||||
return Err(anyhow::anyhow!("No port forward configurations given."));
|
||||
}
|
||||
|
||||
// Parse `PORT_FORWARD` strings into `PortForwardConfig`
|
||||
let port_forwards: anyhow::Result<Vec<Vec<PortForwardConfig>>> = port_forward_strings
|
||||
.into_iter()
|
||||
.map(|s| PortForwardConfig::from_notation(&s))
|
||||
.map(|s| PortForwardConfig::from_notation(&s, DEFAULT_PORT_FORWARD_SOURCE))
|
||||
.collect();
|
||||
let port_forwards: Vec<PortForwardConfig> = port_forwards
|
||||
.with_context(|| "Failed to parse port forward config")?
|
||||
|
@ -135,6 +151,52 @@ impl Config {
|
|||
.flatten()
|
||||
.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
|
||||
let (group_readable, world_readable) = matches
|
||||
.value_of("private-key-file")
|
||||
|
@ -165,6 +227,7 @@ impl Config {
|
|||
|
||||
Ok(Self {
|
||||
port_forwards,
|
||||
remote_port_forwards,
|
||||
private_key: Arc::new(
|
||||
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"))
|
||||
.with_context(|| "Invalid endpoint address")?,
|
||||
source_peer_ip: parse_ip(matches.value_of("source-peer-ip"))
|
||||
.with_context(|| "Invalid source peer IP")?,
|
||||
source_peer_ip,
|
||||
keepalive_seconds: parse_keep_alive(matches.value_of("keep-alive"))
|
||||
.with_context(|| "Invalid keep-alive value")?,
|
||||
max_transmission_unit: parse_mtu(matches.value_of("max-transmission-unit"))
|
||||
|
@ -256,6 +318,8 @@ pub struct PortForwardConfig {
|
|||
pub destination: SocketAddr,
|
||||
/// The transport protocol to use for the port (Layer 4).
|
||||
pub protocol: PortProtocol,
|
||||
/// Whether this is a remote port forward.
|
||||
pub remote: bool,
|
||||
}
|
||||
|
||||
impl PortForwardConfig {
|
||||
|
@ -278,7 +342,7 @@ impl PortForwardConfig {
|
|||
/// - IPv6 addresses must be prefixed with `[` and suffixed with `]`. Example: `[::1]`.
|
||||
/// - 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.
|
||||
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 {
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_not;
|
||||
|
@ -356,7 +420,7 @@ impl PortForwardConfig {
|
|||
.1;
|
||||
|
||||
let source = (
|
||||
src_addr.0.unwrap_or("127.0.0.1"),
|
||||
src_addr.0.unwrap_or(default_source),
|
||||
src_addr
|
||||
.1
|
||||
.parse::<u16>()
|
||||
|
@ -396,6 +460,7 @@ impl PortForwardConfig {
|
|||
source,
|
||||
destination,
|
||||
protocol,
|
||||
remote: false,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
@ -451,18 +516,23 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_port_forward_config_1() {
|
||||
assert_eq!(
|
||||
PortForwardConfig::from_notation("192.168.0.1:8080:192.168.4.1:8081:TCP,UDP")
|
||||
PortForwardConfig::from_notation(
|
||||
"192.168.0.1:8080:192.168.4.1:8081:TCP,UDP",
|
||||
DEFAULT_PORT_FORWARD_SOURCE
|
||||
)
|
||||
.expect("Failed to parse"),
|
||||
vec![
|
||||
PortForwardConfig {
|
||||
source: SocketAddr::from_str("192.168.0.1:8080").unwrap(),
|
||||
destination: SocketAddr::from_str("192.168.4.1:8081").unwrap(),
|
||||
protocol: PortProtocol::Tcp
|
||||
protocol: PortProtocol::Tcp,
|
||||
remote: false,
|
||||
},
|
||||
PortForwardConfig {
|
||||
source: SocketAddr::from_str("192.168.0.1:8080").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]
|
||||
fn test_parse_port_forward_config_2() {
|
||||
assert_eq!(
|
||||
PortForwardConfig::from_notation("192.168.0.1:8080:192.168.4.1:8081:TCP")
|
||||
PortForwardConfig::from_notation(
|
||||
"192.168.0.1:8080:192.168.4.1:8081:TCP",
|
||||
DEFAULT_PORT_FORWARD_SOURCE
|
||||
)
|
||||
.expect("Failed to parse"),
|
||||
vec![PortForwardConfig {
|
||||
source: SocketAddr::from_str("192.168.0.1:8080").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]
|
||||
fn test_parse_port_forward_config_3() {
|
||||
assert_eq!(
|
||||
PortForwardConfig::from_notation("0.0.0.0:8080:192.168.4.1:8081")
|
||||
PortForwardConfig::from_notation(
|
||||
"0.0.0.0:8080:192.168.4.1:8081",
|
||||
DEFAULT_PORT_FORWARD_SOURCE
|
||||
)
|
||||
.expect("Failed to parse"),
|
||||
vec![PortForwardConfig {
|
||||
source: SocketAddr::from_str("0.0.0.0:8080").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]
|
||||
fn test_parse_port_forward_config_4() {
|
||||
assert_eq!(
|
||||
PortForwardConfig::from_notation("[::1]:8080:192.168.4.1:8081")
|
||||
PortForwardConfig::from_notation(
|
||||
"[::1]:8080:192.168.4.1:8081",
|
||||
DEFAULT_PORT_FORWARD_SOURCE
|
||||
)
|
||||
.expect("Failed to parse"),
|
||||
vec![PortForwardConfig {
|
||||
source: SocketAddr::from_str("[::1]:8080").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]
|
||||
fn test_parse_port_forward_config_5() {
|
||||
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 {
|
||||
source: SocketAddr::from_str("127.0.0.1:8080").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]
|
||||
fn test_parse_port_forward_config_6() {
|
||||
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 {
|
||||
source: SocketAddr::from_str("127.0.0.1:8080").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]
|
||||
fn test_parse_port_forward_config_7() {
|
||||
assert_eq!(
|
||||
PortForwardConfig::from_notation("localhost:8080:192.168.4.1:8081")
|
||||
PortForwardConfig::from_notation(
|
||||
"localhost:8080:192.168.4.1:8081",
|
||||
DEFAULT_PORT_FORWARD_SOURCE
|
||||
)
|
||||
.expect("Failed to parse"),
|
||||
vec![PortForwardConfig {
|
||||
source: "localhost:8080".to_socket_addrs().unwrap().next().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]
|
||||
fn test_parse_port_forward_config_8() {
|
||||
assert_eq!(
|
||||
PortForwardConfig::from_notation("localhost:8080:localhost:8081:TCP")
|
||||
PortForwardConfig::from_notation(
|
||||
"localhost:8080:localhost:8081:TCP",
|
||||
DEFAULT_PORT_FORWARD_SOURCE
|
||||
)
|
||||
.expect("Failed to parse"),
|
||||
vec![PortForwardConfig {
|
||||
source: "localhost:8080".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
|
||||
.iter()
|
||||
.any(|pf| pf.protocol == PortProtocol::Udp)
|
||||
|| config
|
||||
.remote_port_forwards
|
||||
.iter()
|
||||
.any(|pf| pf.protocol == PortProtocol::Udp)
|
||||
{
|
||||
// UDP device
|
||||
let bus = bus.clone();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue