mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 07:11:36 +02:00
TOR bridge + TOR Proxy + migration config_file_version (#617)
* tor bridge config and args * migration `config_file_version=2` * small fixes typo, comment etc.. * support: snowflake, meek_lite, obsf4 and tor proxy * remove useless serde * improve migrate function * few fixes * add bridge flags to pay and receive + few fixes * some improvements
This commit is contained in:
+3
-1
@@ -26,7 +26,7 @@ lazy_static = "1"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
reqwest = { version = "0.10", features = ["rustls-tls", "socks"] }
|
||||
|
||||
#Socks/Tor
|
||||
#Socks/Tor/Bridge/Proxy
|
||||
byteorder = "1"
|
||||
ed25519-dalek = "1.0.0-pre.4"
|
||||
x25519-dalek = "0.6"
|
||||
@@ -34,6 +34,8 @@ data-encoding = "2"
|
||||
regex = "1.3"
|
||||
timer = "0.2"
|
||||
sysinfo = "0.14"
|
||||
base64 = "0.12.0"
|
||||
url = "2.1"
|
||||
|
||||
grin_wallet_util = { path = "../util", version = "5.1.0-alpha.1" }
|
||||
grin_wallet_config = { path = "../config", version = "5.1.0-alpha.1" }
|
||||
|
||||
@@ -16,9 +16,14 @@
|
||||
use crate::client_utils::{Client, ClientError, ClientErrorKind};
|
||||
use crate::libwallet::slate_versions::{SlateVersion, VersionedSlate};
|
||||
use crate::libwallet::{Error, ErrorKind, Slate};
|
||||
use crate::tor::bridge::TorBridge;
|
||||
use crate::tor::proxy::TorProxy;
|
||||
use crate::SlateSender;
|
||||
use grin_wallet_config::types::{TorBridgeConfig, TorProxyConfig};
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use std::sync::Arc;
|
||||
@@ -35,6 +40,8 @@ pub struct HttpSlateSender {
|
||||
socks_proxy_addr: Option<SocketAddr>,
|
||||
tor_config_dir: String,
|
||||
process: Option<Arc<tor_process::TorProcess>>,
|
||||
bridge: TorBridgeConfig,
|
||||
proxy: TorProxyConfig,
|
||||
}
|
||||
|
||||
impl HttpSlateSender {
|
||||
@@ -49,6 +56,8 @@ impl HttpSlateSender {
|
||||
socks_proxy_addr: None,
|
||||
tor_config_dir: String::from(""),
|
||||
process: None,
|
||||
bridge: TorBridgeConfig::default(),
|
||||
proxy: TorProxyConfig::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -58,12 +67,16 @@ impl HttpSlateSender {
|
||||
base_url: &str,
|
||||
proxy_addr: &str,
|
||||
tor_config_dir: &str,
|
||||
tor_bridge: TorBridgeConfig,
|
||||
tor_proxy: TorProxyConfig,
|
||||
) -> Result<HttpSlateSender, SchemeNotHttp> {
|
||||
let mut ret = Self::new(base_url)?;
|
||||
ret.use_socks = true;
|
||||
//TODO: Unwrap
|
||||
ret.socks_proxy_addr = Some(SocketAddr::V4(proxy_addr.parse().unwrap()));
|
||||
ret.tor_config_dir = tor_config_dir.into();
|
||||
ret.bridge = tor_bridge;
|
||||
ret.proxy = tor_proxy;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
@@ -80,9 +93,30 @@ impl HttpSlateSender {
|
||||
"Starting TOR Process for send at {:?}",
|
||||
self.socks_proxy_addr
|
||||
);
|
||||
|
||||
let mut hm_tor_bridge: HashMap<String, String> = HashMap::new();
|
||||
if self.bridge.bridge_line.is_some() {
|
||||
let bridge_struct = TorBridge::try_from(self.bridge.clone())
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
hm_tor_bridge = bridge_struct
|
||||
.to_hashmap()
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
}
|
||||
|
||||
let mut hm_tor_proxy: HashMap<String, String> = HashMap::new();
|
||||
if self.proxy.transport.is_some() || self.proxy.allowed_port.is_some() {
|
||||
let proxy = TorProxy::try_from(self.proxy.clone())
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
hm_tor_proxy = proxy
|
||||
.to_hashmap()
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?;
|
||||
}
|
||||
|
||||
tor_config::output_tor_sender_config(
|
||||
&tor_dir,
|
||||
&self.socks_proxy_addr.unwrap().to_string(),
|
||||
hm_tor_bridge,
|
||||
hm_tor_proxy,
|
||||
)
|
||||
.map_err(|e| ErrorKind::TorConfig(format!("{:?}", e)))?;
|
||||
// Start TOR process
|
||||
|
||||
@@ -47,6 +47,14 @@ pub enum ErrorKind {
|
||||
#[fail(display = "Onion V3 Address Error")]
|
||||
OnionV3Address(OnionV3AddressError),
|
||||
|
||||
/// Error when obfs4proxy is not in the user path if TOR brigde is enabled
|
||||
#[fail(display = "Unable to find obfs4proxy binary in your path; {}", _0)]
|
||||
Obfs4proxyBin(String),
|
||||
|
||||
/// Error the bridge input is in bad format
|
||||
#[fail(display = "Bridge line is in bad format; {}", _0)]
|
||||
BridgeLine(String),
|
||||
|
||||
/// Error when formatting json
|
||||
#[fail(display = "IO error")]
|
||||
IO,
|
||||
@@ -83,6 +91,14 @@ pub enum ErrorKind {
|
||||
#[fail(display = "{}", _0)]
|
||||
ArgumentError(String),
|
||||
|
||||
/// Tor Bridge error
|
||||
#[fail(display = "Tor Bridge Error: {}", _0)]
|
||||
TorBridge(String),
|
||||
|
||||
/// Tor Proxy error
|
||||
#[fail(display = "Tor Proxy Error: {}", _0)]
|
||||
TorProxy(String),
|
||||
|
||||
/// Generating ED25519 Public Key
|
||||
#[fail(display = "Error generating ed25519 secret key: {}", _0)]
|
||||
ED25519Key(String),
|
||||
|
||||
@@ -79,6 +79,10 @@ where
|
||||
tor_config: Option<TorConfig>,
|
||||
) -> Result<(), Error> {
|
||||
let mut default_config = GlobalWalletConfig::for_chain(&chain_type);
|
||||
let config_file_version = match default_config.members.as_ref() {
|
||||
Some(m) => m.clone().config_file_version,
|
||||
None => None,
|
||||
};
|
||||
let logging = match logging_config {
|
||||
Some(l) => Some(l),
|
||||
None => match default_config.members.as_ref() {
|
||||
@@ -102,6 +106,7 @@ where
|
||||
};
|
||||
default_config = GlobalWalletConfig {
|
||||
members: Some(GlobalWalletConfigMembers {
|
||||
config_file_version,
|
||||
wallet,
|
||||
tor,
|
||||
logging,
|
||||
@@ -139,7 +144,8 @@ where
|
||||
abs_path.push(self.data_dir.clone());
|
||||
|
||||
default_config.update_paths(&abs_path);
|
||||
let res = default_config.write_to_file(config_file_name.to_str().unwrap());
|
||||
let res =
|
||||
default_config.write_to_file(config_file_name.to_str().unwrap(), false, None, None);
|
||||
if let Err(e) = res {
|
||||
let msg = format!(
|
||||
"Error creating config file as ({}): {}",
|
||||
|
||||
@@ -0,0 +1,661 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{Error, ErrorKind};
|
||||
use base64;
|
||||
use grin_wallet_config::types::TorBridgeConfig;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::{env, str};
|
||||
use url::{Host, Url};
|
||||
|
||||
use crate::tor::proxy::TorProxy;
|
||||
|
||||
#[cfg(windows)]
|
||||
const OBFS4_EXE_NAME: &str = "obfs4proxy.exe";
|
||||
#[cfg(not(windows))]
|
||||
const OBFS4_EXE_NAME: &str = "obfs4proxy";
|
||||
|
||||
#[cfg(windows)]
|
||||
const SNOWFLAKE_EXE_NAME: &str = "snowflake-client.exe";
|
||||
#[cfg(not(windows))]
|
||||
const SNOWFLAKE_EXE_NAME: &str = "snowflake-client";
|
||||
|
||||
pub struct FlagParser<'a> {
|
||||
/// line left to be parsed
|
||||
line: &'a str,
|
||||
/// all flags, bool flags and flags that takes a value
|
||||
flags: Vec<&'a str>,
|
||||
/// bool flags, present in the client line
|
||||
bool_flags: Vec<&'a str>,
|
||||
/// is current parsed flag is a bool
|
||||
is_bool_flag: bool,
|
||||
// parsing client or bridge line
|
||||
client: bool,
|
||||
}
|
||||
|
||||
/// Flag parser, help to retrieve flags and it's value whether on the bridge or client option line
|
||||
impl<'a> FlagParser<'a> {
|
||||
pub fn new(line: &'a str, flags: Vec<&'a str>, bool_flags: Vec<&'a str>, client: bool) -> Self {
|
||||
Self {
|
||||
line,
|
||||
flags,
|
||||
bool_flags,
|
||||
is_bool_flag: false,
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
/// Used only on the client option line parsing, help to retrieve a known flags
|
||||
fn is_flag(&mut self) -> usize {
|
||||
let mut split_index = 0;
|
||||
let line = self.line.split_whitespace();
|
||||
self.is_bool_flag = false;
|
||||
for is_flag in line {
|
||||
let index = self.flags.iter().position(|&flag| flag == is_flag);
|
||||
if let Some(m) = index {
|
||||
let i = self.line.find(is_flag).unwrap();
|
||||
split_index = i + is_flag.len() + 1;
|
||||
let idx_b_flag = self
|
||||
.bool_flags
|
||||
.iter()
|
||||
.position(|&bool_flag| bool_flag == is_flag);
|
||||
if let Some(i) = idx_b_flag {
|
||||
self.is_bool_flag = true;
|
||||
self.bool_flags.remove(i);
|
||||
}
|
||||
self.flags.remove(m);
|
||||
return split_index;
|
||||
}
|
||||
}
|
||||
split_index
|
||||
}
|
||||
|
||||
/// Determine at which index we should take the value linked to its flags
|
||||
fn end(&mut self, is_bool_flag: bool, right: &str) -> usize {
|
||||
if is_bool_flag {
|
||||
0
|
||||
} else if right.starts_with('"') {
|
||||
right[1..].find('"').unwrap_or(0) + 2
|
||||
} else {
|
||||
right.find(' ').unwrap_or(right.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FlagParser<'a> {
|
||||
type Item = (&'a str, &'a str);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (left, right) = if self.client {
|
||||
// Client parser
|
||||
let split_index = self.is_flag();
|
||||
let (l, r) = self.line.split_at(split_index);
|
||||
(l, r)
|
||||
} else {
|
||||
// Bridge parser
|
||||
let split_index = self.line.find("=")?;
|
||||
let (l, r) = self.line.split_at(split_index + 1);
|
||||
(l, r)
|
||||
};
|
||||
let end = self.end(self.is_bool_flag, right);
|
||||
let key = left.split_whitespace().last()?;
|
||||
let val = &right[..end];
|
||||
self.line = &right[end..].trim();
|
||||
Some((key, val))
|
||||
}
|
||||
}
|
||||
|
||||
/// Every args field that could be in the bridge line
|
||||
/// obfs4 args : https://github.com/Yawning/obfs4/blob/40245c4a1cf221395c59d1f4bf274127045352f9/transports/obfs4/obfs4.go#L86-L91
|
||||
/// meek_lite args : https://github.com/Yawning/obfs4/blob/40245c4a1cf221395c59d1f4bf274127045352f9/transports/meeklite/meek.go#L93-L127
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Transport {
|
||||
/// transport type: obfs4, meek_lite, meek, snowflake
|
||||
pub transport: Option<String>,
|
||||
/// server address
|
||||
pub server: Option<String>,
|
||||
/// fingerprint
|
||||
pub fingerprint: Option<String>,
|
||||
/// certificate (obfs4)
|
||||
pub cert: Option<String>,
|
||||
/// IAT obfuscation: 0 disabled, 1 enabled, 2 paranoid (obfs4)
|
||||
pub iatmode: Option<String>,
|
||||
/// URL of signaling broker (meek)
|
||||
pub url: Option<String>,
|
||||
/// optional - front domain (meek)
|
||||
pub front: Option<String>,
|
||||
/// optional - URL of AMP cache to use as a proxy for signaling (meek)
|
||||
pub utls: Option<String>,
|
||||
/// optional - HPKP disable argument. (meek)
|
||||
pub disablehpkp: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Transport {
|
||||
fn default() -> Transport {
|
||||
Transport {
|
||||
transport: None,
|
||||
server: None,
|
||||
fingerprint: None,
|
||||
cert: None,
|
||||
iatmode: None,
|
||||
url: None,
|
||||
front: None,
|
||||
utls: None,
|
||||
disablehpkp: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
/// Parse the server address of the bridge line
|
||||
fn parse_socketaddr_arg(arg: Option<&&str>) -> Result<String, Error> {
|
||||
match arg {
|
||||
Some(addr) => {
|
||||
let address = addr.parse::<SocketAddr>().map_err(|_e| {
|
||||
ErrorKind::TorBridge(format!("Invalid bridge server address: {}", addr).into())
|
||||
})?;
|
||||
Ok(address.to_string())
|
||||
}
|
||||
None => {
|
||||
let msg = format!("Missing bridge server address");
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the fingerprint of the bridge line (obfs4/snowflake/meek)
|
||||
fn parse_fingerprint_arg(arg: Option<&&str>) -> Result<Option<String>, Error> {
|
||||
match arg {
|
||||
Some(f) => {
|
||||
let fgp = f.to_owned();
|
||||
let is_hex = fgp.chars().all(|c| c.is_ascii_hexdigit());
|
||||
let fingerprint = fgp.to_uppercase();
|
||||
if !(is_hex && fingerprint.len() == 40) {
|
||||
let msg = format!("Invalid fingerprint: {}", fingerprint);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
Ok(Some(fingerprint))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
/// Parse the certificate of the bridge line (obfs4)
|
||||
pub fn parse_cert_arg(arg: &str) -> Result<String, Error> {
|
||||
let cert_vec = base64::decode(arg).map_err(|_e| {
|
||||
ErrorKind::TorBridge(format!(
|
||||
"Invalid certificate, error decoding bridge certificate: {}",
|
||||
arg
|
||||
))
|
||||
})?;
|
||||
if cert_vec.len() != 52 {
|
||||
let msg = format!("Invalid certificate: {}", arg);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
Ok(arg.to_string())
|
||||
}
|
||||
/// Parse the iatmode of the bridge line (obfs4)
|
||||
pub fn parse_iatmode_arg(arg: &str) -> Result<String, Error> {
|
||||
let iatmode = arg.parse::<u8>().unwrap_or(0);
|
||||
if !((0..3).contains(&iatmode)) {
|
||||
let msg = format!("Invalid iatmode: {}, must be between 0 and 2", iatmode);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
Ok(iatmode.to_string())
|
||||
}
|
||||
|
||||
/// Parse the max value for the arg -max in the client line option (snowflake)
|
||||
fn parse_hpkp_arg(arg: &str) -> Result<String, Error> {
|
||||
let max = arg.parse::<bool>().map_err(|_e| {
|
||||
ErrorKind::TorBridge(
|
||||
format!("Invalid -max value: {}, must be \"true\" or \"false\"", arg).into(),
|
||||
)
|
||||
})?;
|
||||
Ok(max.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// Client Plugin such as snowflake or obfs4proxy
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PluginClient {
|
||||
// Path plugin client
|
||||
pub path: Option<String>,
|
||||
// Plugin client option
|
||||
pub option: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for PluginClient {
|
||||
fn default() -> PluginClient {
|
||||
PluginClient {
|
||||
path: None,
|
||||
option: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginClient {
|
||||
/// Get the hashmap key(argument) and attached value of the client option line.
|
||||
pub fn get_flags(s: &str) -> HashMap<&str, &str> {
|
||||
let flags = vec![
|
||||
"-url",
|
||||
"-front",
|
||||
"-ice",
|
||||
"-log",
|
||||
"-log-to-state-dir",
|
||||
"-keep-local-addresses",
|
||||
"-unsafe-logging",
|
||||
"-max",
|
||||
"-loglevel",
|
||||
"-enableLogging",
|
||||
"-unsafeLogging",
|
||||
];
|
||||
let bool_flags = vec![
|
||||
"-log-to-state-dir",
|
||||
"-keep-local-addresses",
|
||||
"-unsafe-logging",
|
||||
"-enableLogging",
|
||||
"-unsafeLogging",
|
||||
];
|
||||
FlagParser::new(s, flags, bool_flags, true).collect()
|
||||
}
|
||||
|
||||
/// Try to find the plugin client path
|
||||
pub fn get_client_path(plugin: &str) -> Result<String, Error> {
|
||||
let plugin_path = env::var_os("PATH").and_then(|path| {
|
||||
env::split_paths(&path)
|
||||
.filter_map(|dir| {
|
||||
let full_path = dir.join(plugin);
|
||||
if full_path.is_file() {
|
||||
Some(full_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
});
|
||||
match plugin_path {
|
||||
Some(path) => Ok(path.into_os_string().into_string().unwrap()),
|
||||
None => {
|
||||
let msg = format!("Transport client \"{}\" is missing, make sure it's installed and on your path.", plugin);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the URL value for the arg -url in the client line option (snowflake)
|
||||
fn parse_url_arg(arg: &str) -> Result<String, Error> {
|
||||
let url = arg
|
||||
.parse::<Url>()
|
||||
.map_err(|_e| ErrorKind::TorBridge(format!("Invalid -url value: {}", arg).into()))?;
|
||||
Ok(url.to_string())
|
||||
}
|
||||
|
||||
/// Parse the DNS domain value for the arg -front in the client line option (snowflake)
|
||||
fn parse_front_arg(arg: &str) -> Result<String, Error> {
|
||||
let front = Host::parse(arg).map_err(|_e| {
|
||||
ErrorKind::TorBridge(format!("Invalid -front hostname value: {}", arg).into())
|
||||
})?;
|
||||
match front {
|
||||
Host::Domain(_) => Ok(front.to_string()),
|
||||
Host::Ipv4(_) | Host::Ipv6(_) => {
|
||||
let msg = format!(
|
||||
"Invalid front argument: {}, in the client option. Must be a DNS Domain",
|
||||
front
|
||||
);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the ICE address value for the arg -ice in the client line option (snowflake)
|
||||
fn parse_ice_arg(arg: &str) -> Result<String, Error> {
|
||||
let ice_addr = arg.trim();
|
||||
let vec_ice_addr = ice_addr.split(",");
|
||||
for addr in vec_ice_addr {
|
||||
let addr = addr.to_lowercase();
|
||||
if addr.starts_with("stun:") || addr.starts_with("turn:") {
|
||||
let address = addr.replace("stun:", "").replace("turn:", "");
|
||||
let _p_address = TorProxy::parse_address(&address)
|
||||
.map_err(|e| ErrorKind::TorBridge(format!("{}", e.kind()).into()))?;
|
||||
} else {
|
||||
let msg = format!(
|
||||
"Invalid ICE address: {}. Must be a stun or turn address",
|
||||
addr
|
||||
);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
}
|
||||
Ok(ice_addr.to_string())
|
||||
}
|
||||
|
||||
/// Parse the max value for the arg -max in the client line option (snowflake)
|
||||
fn parse_max_arg(arg: &str) -> Result<String, Error> {
|
||||
match arg.parse::<u16>() {
|
||||
Ok(max) => Ok(max.to_string()),
|
||||
Err(_e) => {
|
||||
let msg = format!("Invalid -max argument: {} in the client option.", arg);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the loglevel value for the arg -loglevel in the client line option (obfs4)
|
||||
fn parse_loglevel_arg(arg: &str) -> Result<String, Error> {
|
||||
let log_level = arg.to_uppercase();
|
||||
match log_level.as_str() {
|
||||
"ERROR" | "WARN" | "INFO" | "DEBUG" => Ok(log_level.to_string()),
|
||||
_ => {
|
||||
let msg = format!("Invalid log level argurment: {}, in the client option. Must be: ERROR, WARN, INFO or DEBUG", log_level);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and verify if the client option line of obfs4proxy or snowflake are correct
|
||||
/// Obfs4proxy client args : https://github.com/Yawning/obfs4/blob/40245c4a1cf221395c59d1f4bf274127045352f9/obfs4proxy/obfs4proxy.go#L313-L316
|
||||
/// Snowflake client args : https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/blob/main/client/snowflake.go#L123-132
|
||||
pub fn parse_client(option: &str, snowflake: bool) -> Result<String, Error> {
|
||||
let hm_flags = PluginClient::get_flags(option);
|
||||
let mut string = String::from("");
|
||||
if snowflake {
|
||||
let (ck_url, ck_ice) = (hm_flags.contains_key("-url"), hm_flags.contains_key("-ice"));
|
||||
if !(ck_url || ck_ice) {
|
||||
let msg = if !ck_url {
|
||||
format!("Missing URL argurment for snowflake transport, specify \"-url\"")
|
||||
} else {
|
||||
format!("Missing ICE argurment for snowflake transport, specify \"-ice\"")
|
||||
};
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
for (key, value) in hm_flags {
|
||||
let p_value = match key {
|
||||
"-url" => PluginClient::parse_url_arg(value)?,
|
||||
"-front" => PluginClient::parse_front_arg(value)?,
|
||||
"-ice" => PluginClient::parse_ice_arg(value)?,
|
||||
"-ampcache" => value.to_string(),
|
||||
"-log" => value.to_string(),
|
||||
"-log-to-state-dir" => String::from(""),
|
||||
"-keep-local-addresses" => String::from(""),
|
||||
"-unsafe-logging" => String::from(""),
|
||||
"-max" => PluginClient::parse_max_arg(value)?,
|
||||
_ => continue,
|
||||
};
|
||||
string.push_str(format!(" {} {}", key, p_value).trim_end())
|
||||
}
|
||||
} else {
|
||||
for (key, value) in hm_flags {
|
||||
let p_value = match key {
|
||||
"-loglevel" => PluginClient::parse_loglevel_arg(value)?,
|
||||
"-enableLogging" => String::from(""),
|
||||
"-unsafeLogging" => String::from(""),
|
||||
_ => continue,
|
||||
};
|
||||
string.push_str(format!(" {} {}", key, p_value).trim_end())
|
||||
}
|
||||
}
|
||||
let p_string = string.trim_start().to_string();
|
||||
Ok(p_string)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tor Bridge Field
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TorBridge {
|
||||
/// tor bridge (transport field)
|
||||
pub bridge: Transport,
|
||||
// tor bridge plugin client (path and option)
|
||||
pub client: PluginClient,
|
||||
}
|
||||
|
||||
impl Default for TorBridge {
|
||||
fn default() -> TorBridge {
|
||||
TorBridge {
|
||||
bridge: Transport::default(),
|
||||
client: PluginClient::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TorBridge {
|
||||
/// Get the hashmap key(argument) and attached value of the bridge line. r
|
||||
pub fn get_flags(s: &str) -> HashMap<&str, &str> {
|
||||
FlagParser::new(s, vec![], vec![], false).collect()
|
||||
}
|
||||
|
||||
/// Bridge and client option convertion to hashmap, facility for the writing of the torrc config
|
||||
pub fn to_hashmap(&self) -> Result<HashMap<String, String>, Error> {
|
||||
let bridge = self.bridge.clone();
|
||||
let client = self.client.clone();
|
||||
let transport = bridge.transport.as_ref().unwrap().as_str();
|
||||
let mut ret_val = HashMap::new();
|
||||
match transport {
|
||||
"obfs4" => {
|
||||
let string_un = &String::from("");
|
||||
let chskey = "ClientTransportPlugin".to_string();
|
||||
let chsvalue = format!(
|
||||
"{} exec {} {}",
|
||||
transport,
|
||||
client.path.as_ref().unwrap(),
|
||||
client.option.as_ref().unwrap_or(string_un)
|
||||
);
|
||||
ret_val.insert(chskey, chsvalue);
|
||||
|
||||
let hskey = "Bridge".to_string();
|
||||
let mut hsvalue = format!("{} {}", transport, bridge.server.as_ref().unwrap());
|
||||
if let Some(fingerprint) = bridge.fingerprint {
|
||||
hsvalue.push_str(format!(" {}", fingerprint).as_str())
|
||||
}
|
||||
hsvalue.push_str(format!(" cert={}", bridge.cert.unwrap()).as_str());
|
||||
hsvalue.push_str(format!(" iat-mode={}", bridge.iatmode.unwrap()).as_str());
|
||||
ret_val.insert(hskey, hsvalue);
|
||||
|
||||
Ok(ret_val)
|
||||
}
|
||||
|
||||
"meek_lite" => {
|
||||
let chskey = "ClientTransportPlugin".to_string();
|
||||
let mut chsvalue = format!("{} exec {}", transport, client.path.as_ref().unwrap());
|
||||
if let Some(option) = client.option {
|
||||
chsvalue.push_str(format!(" {}", option).as_str())
|
||||
}
|
||||
ret_val.insert(chskey, chsvalue);
|
||||
|
||||
let hskey = "Bridge".to_string();
|
||||
let mut hsvalue = format!("{} {}", transport, bridge.server.as_ref().unwrap());
|
||||
if let Some(fingerprint) = bridge.fingerprint {
|
||||
hsvalue.push_str(format!(" {}", fingerprint).as_str())
|
||||
}
|
||||
|
||||
hsvalue.push_str(format!(" url={}", bridge.url.as_ref().unwrap()).as_str());
|
||||
|
||||
if let Some(front) = bridge.front {
|
||||
hsvalue.push_str(format!(" front={}", front).as_str())
|
||||
}
|
||||
if let Some(utls) = bridge.utls {
|
||||
hsvalue.push_str(format!(" utls={}", utls).as_str())
|
||||
}
|
||||
if let Some(disablehpkp) = bridge.disablehpkp {
|
||||
hsvalue.push_str(format!(" disableHPKP={}", disablehpkp).as_str())
|
||||
}
|
||||
ret_val.insert(hskey, hsvalue);
|
||||
Ok(ret_val)
|
||||
}
|
||||
|
||||
"snowflake" => {
|
||||
let chskey = "ClientTransportPlugin".to_string();
|
||||
let chsvalue = format!(
|
||||
"{} exec {} {}",
|
||||
transport,
|
||||
client.path.as_ref().unwrap(),
|
||||
client.option.as_ref().unwrap()
|
||||
);
|
||||
ret_val.insert(chskey, chsvalue);
|
||||
|
||||
let hskey = "Bridge".to_string();
|
||||
let mut hsvalue = format!("{} {}", transport, bridge.server.as_ref().unwrap());
|
||||
if let Some(fingerprint) = bridge.fingerprint {
|
||||
hsvalue.push_str(format!(" {}", fingerprint).as_str())
|
||||
}
|
||||
ret_val.insert(hskey, hsvalue);
|
||||
Ok(ret_val)
|
||||
}
|
||||
|
||||
_ => {
|
||||
let msg = format!(
|
||||
"Invalid transport method: {} - must be obfs4/meek_lite/meek/snowflake",
|
||||
transport
|
||||
);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TorBridgeConfig> for TorBridge {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(tbc: TorBridgeConfig) -> Result<Self, Self::Error> {
|
||||
let bridge = match tbc.bridge_line {
|
||||
Some(b) => b,
|
||||
None => return Ok(TorBridge::default()),
|
||||
};
|
||||
let flags = TorBridge::get_flags(&bridge);
|
||||
let split = bridge.split_whitespace().collect::<Vec<&str>>();
|
||||
let mut iter = split.iter();
|
||||
let transport = iter.next().unwrap().to_lowercase();
|
||||
match transport.as_str() {
|
||||
"obfs4" => {
|
||||
let socketaddr = Transport::parse_socketaddr_arg(iter.next())?;
|
||||
let fingerprint = Transport::parse_fingerprint_arg(iter.next())?;
|
||||
let cert = match flags.get_key_value("cert=") {
|
||||
Some(hm) => Transport::parse_cert_arg(hm.1)?,
|
||||
None => {
|
||||
let msg =
|
||||
format!("Missing cert argurment in obfs4 transport, specify \"cert=\"");
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
};
|
||||
let iatmode = match flags.get_key_value("iat-mode=") {
|
||||
Some(hm) => Transport::parse_iatmode_arg(hm.1)?,
|
||||
None => String::from("0"),
|
||||
};
|
||||
let path = PluginClient::get_client_path(OBFS4_EXE_NAME)?;
|
||||
let option = match tbc.client_option {
|
||||
Some(o) => Some(PluginClient::parse_client(&o, false)?),
|
||||
None => None,
|
||||
};
|
||||
let tbpc = TorBridge {
|
||||
bridge: Transport {
|
||||
transport: Some("obfs4".into()),
|
||||
server: Some(socketaddr.to_string()),
|
||||
fingerprint: fingerprint,
|
||||
cert: Some(cert.into()),
|
||||
iatmode: Some(iatmode),
|
||||
..Transport::default()
|
||||
},
|
||||
client: PluginClient {
|
||||
path: Some(path),
|
||||
option: option,
|
||||
},
|
||||
};
|
||||
Ok(tbpc)
|
||||
}
|
||||
|
||||
"meek_lite" | "meek" => {
|
||||
let socketaddr = Transport::parse_socketaddr_arg(iter.next())?;
|
||||
let fingerprint = Transport::parse_fingerprint_arg(iter.next())?;
|
||||
let url = match flags.get_key_value("url=") {
|
||||
Some(hm) => PluginClient::parse_url_arg(hm.1)?,
|
||||
None => {
|
||||
let msg = format!(
|
||||
"Missing url argurment in meek_lite transport, specify \"url=\""
|
||||
);
|
||||
return Err(ErrorKind::TorBridge(msg).into());
|
||||
}
|
||||
};
|
||||
let front = match flags.get_key_value("front=") {
|
||||
Some(hm) => Some(PluginClient::parse_front_arg(hm.1)?),
|
||||
None => None,
|
||||
};
|
||||
let utls = match flags.get_key_value("utls=") {
|
||||
Some(hm) => Some(hm.1.to_string()),
|
||||
None => None,
|
||||
};
|
||||
let disablehpkp = match flags.get_key_value("disablehpkp=") {
|
||||
Some(hm) => Some(Transport::parse_hpkp_arg(hm.1)?),
|
||||
None => None,
|
||||
};
|
||||
let path = PluginClient::get_client_path(OBFS4_EXE_NAME)?;
|
||||
let option = match tbc.client_option {
|
||||
Some(o) => Some(PluginClient::parse_client(&o, false)?),
|
||||
None => None,
|
||||
};
|
||||
let tbpc = TorBridge {
|
||||
bridge: Transport {
|
||||
transport: Some("meek_lite".into()),
|
||||
server: Some(socketaddr.to_string()),
|
||||
fingerprint: fingerprint,
|
||||
url: Some(url),
|
||||
front: front,
|
||||
utls: utls,
|
||||
disablehpkp: disablehpkp,
|
||||
..Transport::default()
|
||||
},
|
||||
client: PluginClient {
|
||||
path: Some(path),
|
||||
option: option,
|
||||
},
|
||||
};
|
||||
Ok(tbpc)
|
||||
}
|
||||
|
||||
"snowflake" => {
|
||||
let socketaddr = Transport::parse_socketaddr_arg(iter.next())?;
|
||||
let fingerprint = Transport::parse_fingerprint_arg(iter.next())?;
|
||||
let path = PluginClient::get_client_path(SNOWFLAKE_EXE_NAME)?;
|
||||
let option = match tbc.client_option {
|
||||
Some(o) => PluginClient::parse_client(&o, true)?,
|
||||
None => {
|
||||
let url =
|
||||
"-url https://snowflake-broker.torproject.net.global.prod.fastly.net/";
|
||||
let front = "-front cdn.sstatic.net";
|
||||
let ice = "-ice stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478";
|
||||
format!("{} {} {}", url, front, ice)
|
||||
}
|
||||
};
|
||||
let tbpc = TorBridge {
|
||||
bridge: Transport {
|
||||
transport: Some("snowflake".into()),
|
||||
server: Some(socketaddr.to_string()),
|
||||
fingerprint: fingerprint,
|
||||
..Transport::default()
|
||||
},
|
||||
client: PluginClient {
|
||||
path: Some(path),
|
||||
option: Some(option),
|
||||
},
|
||||
};
|
||||
Ok(tbpc)
|
||||
}
|
||||
_ => {
|
||||
let msg = format!(
|
||||
"Invalid transport method: {} - must be obfs4/meek_lite/meek/snowflake",
|
||||
transport
|
||||
);
|
||||
Err(ErrorKind::TorBridge(msg).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
-3
@@ -20,11 +20,12 @@ use grin_wallet_util::OnionV3Address;
|
||||
use ed25519_dalek::ExpandedSecretKey;
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, MAIN_SEPARATOR};
|
||||
use std::string::String;
|
||||
|
||||
use failure::ResultExt;
|
||||
|
||||
@@ -171,6 +172,8 @@ pub fn output_torrc(
|
||||
wallet_listener_addr: &str,
|
||||
socks_port: &str,
|
||||
service_dirs: &[String],
|
||||
hm_tor_bridge: HashMap<String, String>,
|
||||
hm_tor_proxy: HashMap<String, String>,
|
||||
) -> Result<(), Error> {
|
||||
let torrc_file_path = format!("{}{}{}", tor_config_directory, MAIN_SEPARATOR, TORRC_FILE);
|
||||
|
||||
@@ -186,6 +189,19 @@ pub fn output_torrc(
|
||||
props.add_item("HiddenServicePort", &format!("80 {}", wallet_listener_addr));
|
||||
}
|
||||
|
||||
if !hm_tor_bridge.is_empty() {
|
||||
props.add_item("UseBridges", "1");
|
||||
for (key, value) in hm_tor_bridge {
|
||||
props.add_item(&key, &value);
|
||||
}
|
||||
}
|
||||
|
||||
if !hm_tor_proxy.is_empty() {
|
||||
for (key, value) in hm_tor_proxy {
|
||||
props.add_item(&key, &value);
|
||||
}
|
||||
}
|
||||
|
||||
props.write_to_file(&torrc_file_path)?;
|
||||
|
||||
Ok(())
|
||||
@@ -196,6 +212,8 @@ pub fn output_tor_listener_config(
|
||||
tor_config_directory: &str,
|
||||
wallet_listener_addr: &str,
|
||||
listener_keys: &[SecretKey],
|
||||
hm_tor_bridge: HashMap<String, String>,
|
||||
hm_tor_proxy: HashMap<String, String>,
|
||||
) -> Result<(), Error> {
|
||||
let tor_data_dir = format!("{}{}{}", tor_config_directory, MAIN_SEPARATOR, TOR_DATA_DIR);
|
||||
|
||||
@@ -215,6 +233,8 @@ pub fn output_tor_listener_config(
|
||||
wallet_listener_addr,
|
||||
"0",
|
||||
&service_dirs,
|
||||
hm_tor_bridge,
|
||||
hm_tor_proxy,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -224,11 +244,20 @@ pub fn output_tor_listener_config(
|
||||
pub fn output_tor_sender_config(
|
||||
tor_config_dir: &str,
|
||||
socks_listener_addr: &str,
|
||||
hm_tor_bridge: HashMap<String, String>,
|
||||
hm_tor_proxy: HashMap<String, String>,
|
||||
) -> Result<(), Error> {
|
||||
// create data directory if it doesn't exist
|
||||
fs::create_dir_all(&tor_config_dir).context(ErrorKind::IO)?;
|
||||
|
||||
output_torrc(tor_config_dir, "", socks_listener_addr, &[])?;
|
||||
output_torrc(
|
||||
tor_config_dir,
|
||||
"",
|
||||
socks_listener_addr,
|
||||
&[],
|
||||
hm_tor_bridge,
|
||||
hm_tor_proxy,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -293,7 +322,8 @@ mod tests {
|
||||
let secp = secp_inst.lock();
|
||||
let mut test_rng = StepRng::new(1_234_567_890_u64, 1);
|
||||
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
||||
output_tor_listener_config(test_dir, "127.0.0.1:3415", &[sec_key])?;
|
||||
let hm = HashMap::new();
|
||||
output_tor_listener_config(test_dir, "127.0.0.1:3415", &[sec_key], hm.clone(), hm)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -12,5 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod bridge;
|
||||
pub mod config;
|
||||
pub mod process;
|
||||
pub mod proxy;
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{Error, ErrorKind};
|
||||
use grin_wallet_config::types::TorProxyConfig;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::str;
|
||||
use url::Host;
|
||||
|
||||
/// Tor Proxy
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorProxy {
|
||||
/// proxy type used for the proxy, eg "socks4", "socks5", "http", "https"
|
||||
pub transport: Option<String>,
|
||||
/// Proxy address for the proxy, eg IP:PORT or Hostname
|
||||
pub address: Option<String>,
|
||||
/// Username for the proxy authentification
|
||||
pub username: Option<String>,
|
||||
/// Password for the proxy authentification
|
||||
pub password: Option<String>,
|
||||
/// computer goes through a firewall that only allows connections to certain ports
|
||||
pub allowed_port: Option<Vec<u16>>,
|
||||
}
|
||||
|
||||
impl Default for TorProxy {
|
||||
fn default() -> TorProxy {
|
||||
TorProxy {
|
||||
transport: None,
|
||||
address: None,
|
||||
username: None,
|
||||
password: None,
|
||||
allowed_port: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TorProxy {
|
||||
fn parse_host_port(addr: &str) -> Result<(String, Option<String>), Error> {
|
||||
let host: String;
|
||||
let str_port: Option<String>;
|
||||
let address = addr
|
||||
.chars()
|
||||
.filter(|c| !c.is_whitespace())
|
||||
.collect::<String>();
|
||||
if address.starts_with('[') {
|
||||
let split = address.split_once("]:").unwrap();
|
||||
host = split.0.to_string();
|
||||
str_port = Some(split.1.to_string());
|
||||
} else if address.contains(":") && !address.ends_with(":") {
|
||||
let split = address.split_once(":").unwrap();
|
||||
host = split.0.to_string();
|
||||
str_port = Some(split.1.to_string());
|
||||
} else {
|
||||
host = address.to_string();
|
||||
str_port = None;
|
||||
};
|
||||
Ok((host, str_port))
|
||||
}
|
||||
|
||||
pub fn parse_address(addr: &str) -> Result<(String, Option<u16>), Error> {
|
||||
let (host, str_port) = TorProxy::parse_host_port(&addr)?;
|
||||
let host = Host::parse(&host)
|
||||
.map_err(|_e| ErrorKind::TorProxy(format!("Invalid host address: {}", host)))?;
|
||||
let port = if let Some(p) = str_port {
|
||||
let res = p
|
||||
.parse::<u16>()
|
||||
.map_err(|_e| ErrorKind::TorProxy(format!("Invalid port number: {}", p)))?;
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((host.to_string(), port))
|
||||
}
|
||||
|
||||
pub fn to_hashmap(self) -> Result<HashMap<String, String>, Error> {
|
||||
let mut hm = HashMap::new();
|
||||
if let Some(ports) = self.allowed_port {
|
||||
let mut allowed_ports = "".to_string();
|
||||
let last_port = ports.last().unwrap().to_owned();
|
||||
for port in ports.clone() {
|
||||
allowed_ports.push_str(format!("*:{}", port).as_str());
|
||||
if port != last_port {
|
||||
allowed_ports.push_str(",");
|
||||
}
|
||||
}
|
||||
hm.insert(
|
||||
"ReachableAddresses".to_string(),
|
||||
format!("{}", allowed_ports.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
let transport = match self.transport {
|
||||
Some(t) => t,
|
||||
None => return Ok(hm),
|
||||
};
|
||||
match transport.as_str() {
|
||||
"socks4" => {
|
||||
hm.insert("Socks4Proxy".to_string(), self.address.unwrap());
|
||||
Ok(hm)
|
||||
}
|
||||
"socks5" => {
|
||||
hm.insert("Socks5Proxy".to_string(), self.address.unwrap());
|
||||
|
||||
if let Some(s) = self.username {
|
||||
hm.insert("Socks5ProxyUsername".to_string(), s);
|
||||
}
|
||||
if let Some(s) = self.password {
|
||||
hm.insert("Socks5ProxyPassword".to_string(), s);
|
||||
}
|
||||
Ok(hm)
|
||||
}
|
||||
"http" | "https" | "http(s)" => {
|
||||
hm.insert("HTTPSProxy".to_string(), self.address.unwrap());
|
||||
|
||||
if let Some(user) = self.username {
|
||||
let pass = self.password.unwrap_or("".to_string());
|
||||
hm.insert(
|
||||
"HTTPSProxyAuthenticator".to_string(),
|
||||
format!("{}:{}", user, pass),
|
||||
);
|
||||
}
|
||||
Ok(hm)
|
||||
}
|
||||
_ => Ok(hm),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TorProxyConfig> for TorProxy {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(tb: TorProxyConfig) -> Result<Self, Self::Error> {
|
||||
if let Some(t) = tb.transport {
|
||||
let transport = t.to_lowercase();
|
||||
match transport.as_str() {
|
||||
"socks4" | "socks5" | "http" | "https" | "http(s)" => {
|
||||
// Can't parse socket address --> trying to parse a domain name
|
||||
if let Some(address) = tb.address {
|
||||
let address_addr: String;
|
||||
let (host, port) = TorProxy::parse_address(&address)?;
|
||||
if let Some(p) = port {
|
||||
address_addr = format!("{}:{}", host, p);
|
||||
} else {
|
||||
address_addr = host
|
||||
}
|
||||
Ok(TorProxy {
|
||||
transport: Some(transport.into()),
|
||||
address: Some(address_addr),
|
||||
username: tb.username,
|
||||
password: tb.password,
|
||||
allowed_port: tb.allowed_port,
|
||||
})
|
||||
} else {
|
||||
let msg = format!(
|
||||
"Missing proxy address: {} - must be <IP:PORT> or <Hostname>",
|
||||
transport
|
||||
);
|
||||
return Err(ErrorKind::TorProxy(msg).into());
|
||||
}
|
||||
}
|
||||
// Missing transport type
|
||||
_ => {
|
||||
let msg = format!(
|
||||
"Invalid proxy transport: {} - must be socks4/socks5/http(s)",
|
||||
transport
|
||||
);
|
||||
Err(ErrorKind::TorProxy(msg).into())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In case the user want to allow only some ports
|
||||
let ports = tb.allowed_port.unwrap();
|
||||
Ok(TorProxy {
|
||||
allowed_port: Some(ports),
|
||||
..TorProxy::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user