Checkpoint, or something.

This commit is contained in:
2022-11-22 20:13:14 -08:00
parent 277125e1a0
commit 1d182a150f
9 changed files with 467 additions and 97 deletions

177
server/config.rs Normal file
View File

@@ -0,0 +1,177 @@
mod cmdline;
mod config_file;
use self::cmdline::Arguments;
use self::config_file::ConfigFile;
use clap::Parser;
use if_addrs::IfAddr;
use std::io;
use std::net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
use std::str::FromStr;
use thiserror::Error;
use tracing::metadata::LevelFilter;
use xdg::BaseDirectoriesError;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error(transparent)]
CommandLineError(#[from] clap::Error),
#[error("Error querying XDG base directories: {0}")]
XdgError(#[from] BaseDirectoriesError),
#[error(transparent)]
IOError(#[from] io::Error),
#[error("TOML processing error: {0}")]
TomlError(#[from] toml::de::Error),
#[error("Server '{0}' specifies an interface ({1}) with no addresses")]
NoAddressForInterface(String, String),
#[error("Server '{0}' specifies an address we couldn't parse: {1}")]
AddressParseError(String, AddrParseError),
}
#[derive(Debug)]
pub struct Config {
pub log_level: LevelFilter,
pub server_definitions: Vec<ServerDefinition>,
}
#[derive(Debug)]
pub struct ServerDefinition {
pub name: String,
pub start: bool,
pub interface: Option<String>,
pub address: SocketAddr,
pub log_level: LevelFilter,
}
impl Config {
/// Generate a configuration by reading the command line arguments and any
/// defined config file, generating the actual arguments that we'll use for
/// operating the daemon.
pub fn derive() -> Result<Self, ConfigError> {
let command_line = Arguments::try_parse()?;
let mut config_file = ConfigFile::read(command_line.config_file)?;
let nic_addresses = if_addrs::get_if_addrs()?;
let log_level = command_line
.log_level
.or(config_file.log_level)
.unwrap_or(LevelFilter::ERROR);
let mut server_definitions = Vec::new();
let servers_to_start: Vec<String> = config_file
.start_servers
.map(|x| x.split(',').map(|v| v.to_string()).collect())
.unwrap_or_default();
for (name, config_info) in config_file.servers.drain() {
let start = servers_to_start.contains(&name);
let log_level = config_info.log_level.unwrap_or(log_level);
let port = config_info.port.unwrap_or(1080);
let mut interface = None;
let mut address = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port));
match (config_info.interface, config_info.address) {
// if the user provides us nothing, we'll just use a blank address and
// no interface association
(None, None) => {}
// if the user provides us an interface but no address, we'll see if we can
// find the interface and pull a reasonable address from it.
(Some(given_interface), None) => {
let mut found_it = false;
for card_interface in nic_addresses.iter() {
if card_interface.name == given_interface {
interface = Some(given_interface.clone());
address = SocketAddr::new(addr_convert(&card_interface.addr), port);
found_it = true;
break;
}
}
if !found_it {
return Err(ConfigError::NoAddressForInterface(name, given_interface));
}
}
// if the user provides us an address but no interface, we'll quickly see if
// we can find that address in our interface list ... but we won't insist on
// it.
(None, Some(address_string)) => {
let read_address = IpAddr::from_str(&address_string)
.map_err(|x| ConfigError::AddressParseError(name.clone(), x))?;
interface = None;
address = SocketAddr::new(read_address, port);
for card_interface in nic_addresses.iter() {
if addrs_match(&card_interface.addr, &read_address) {
interface = Some(card_interface.name.clone());
break;
}
}
}
// if the user provides both, we'll check to make sure that they match.
(Some(given_interface), Some(address_string)) => {
let read_address = IpAddr::from_str(&address_string)
.map_err(|x| ConfigError::AddressParseError(name.clone(), x))?;
let mut inferred_interface = None;
let mut good_to_go = false;
address = SocketAddr::new(read_address, port);
for card_interface in nic_addresses.iter() {
if addrs_match(&card_interface.addr, &read_address) {
if card_interface.name == given_interface {
interface = Some(given_interface.clone());
good_to_go = true;
break;
} else {
inferred_interface = Some(card_interface.name.clone());
}
}
}
if !good_to_go {
if let Some(inferred_interface) = inferred_interface {
tracing::warn!("Address {} is associated with interface {}, not {}; using it instead", read_address, inferred_interface, given_interface);
} else {
tracing::warn!(
"Address {} is not associated with interface {}, or any interface.",
read_address,
given_interface
);
}
}
}
}
server_definitions.push(ServerDefinition {
name,
start,
interface,
address,
log_level,
});
}
Ok(Config {
log_level,
server_definitions,
})
}
}
fn addr_convert(x: &if_addrs::IfAddr) -> IpAddr {
match x {
if_addrs::IfAddr::V4(x) => IpAddr::V4(x.ip),
if_addrs::IfAddr::V6(x) => IpAddr::V6(x.ip),
}
}
fn addrs_match(x: &if_addrs::IfAddr, y: &IpAddr) -> bool {
match (x, y) {
(IfAddr::V4(x), IpAddr::V4(y)) => &x.ip == y,
(IfAddr::V6(x), IpAddr::V6(y)) => &x.ip == y,
_ => false,
}
}

35
server/config/cmdline.rs Normal file
View File

@@ -0,0 +1,35 @@
use clap::Parser;
use std::path::PathBuf;
use tracing::metadata::LevelFilter;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
pub struct Arguments {
#[clap(
short,
long,
help = "Use the given config file, rather than $XDG_CONFIG_DIR/socks5.toml"
)]
pub config_file: Option<PathBuf>,
#[clap(
short,
long,
help = "Default logging to the given level. (Defaults to ERROR if not given)"
)]
pub log_level: Option<LevelFilter>,
#[clap(
short,
long,
help = "Start only the named server(s) from the config file. For more than one, use comma-separated values or multiple instances of --start"
)]
pub start: Vec<String>,
#[clap(
short,
long = "validate",
help = "Do not actually start any servers; just validate the config file."
)]
pub validate_only: bool,
}

View File

@@ -0,0 +1,62 @@
use super::ConfigError;
use serde::{Deserialize, Deserializer};
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use tracing::metadata::LevelFilter;
use xdg::BaseDirectories;
#[derive(serde_derive::Deserialize, Default)]
pub struct ConfigFile {
#[serde(deserialize_with = "parse_log_level")]
pub log_level: Option<LevelFilter>,
pub start_servers: Option<String>,
#[serde(flatten)]
pub servers: HashMap<String, ServerDefinition>,
}
#[derive(serde_derive::Deserialize)]
pub struct ServerDefinition {
pub interface: Option<String>,
#[serde(deserialize_with = "parse_log_level")]
pub log_level: Option<LevelFilter>,
pub address: Option<String>,
pub port: Option<u16>,
}
fn parse_log_level<'de, D>(deserializer: D) -> Result<Option<LevelFilter>, D::Error>
where
D: Deserializer<'de>,
{
let possible_string: Option<&str> = Deserialize::deserialize(deserializer)?;
if let Some(s) = possible_string {
Ok(Some(s.parse().map_err(|e| {
serde::de::Error::custom(format!("Couldn't parse log level '{}': {}", s, e))
})?))
} else {
Ok(None)
}
}
impl ConfigFile {
pub fn read(mut config_file_path: Option<PathBuf>) -> Result<ConfigFile, ConfigError> {
if config_file_path.is_none() {
let base_dirs = BaseDirectories::with_prefix("socks5")?;
let proposed_path = base_dirs.get_config_home();
if let Ok(attributes) = fs::metadata(proposed_path.clone()) {
if attributes.is_file() {
config_file_path = Some(proposed_path);
}
}
}
match config_file_path {
None => Ok(ConfigFile::default()),
Some(path) => {
let content = fs::read(path)?;
Ok(toml::from_slice(&content)?)
}
}
}
}

74
server/main.rs Normal file
View File

@@ -0,0 +1,74 @@
mod config;
use async_socks5::server::{SOCKSv5Server, SecurityParameters};
use config::Config;
use tracing::Instrument;
use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::fmt;
use tracing_subscriber::prelude::*;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let config = Config::derive()?;
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::builder()
.with_default_directive(config.log_level.into())
.from_env()?;
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.init();
tracing::trace!("Parsed configuration: {:?}", config);
let core_server = SOCKSv5Server::new(SecurityParameters {
allow_unauthenticated: true,
allow_connection: None,
check_password: None,
connect_tls: None,
});
let mut running_servers = vec![];
for server_def in config.server_definitions {
let span = tracing::trace_span!(
"",
server_name = %server_def.name,
interface = ?server_def.interface,
address = %server_def.address,
);
let result = core_server
.start(server_def.address.ip(), server_def.address.port())
.instrument(span)
.await;
match result {
Ok(x) => running_servers.push(x),
Err(e) => tracing::error!(
server = %server_def.name,
interface = ?server_def.interface,
address = %server_def.address,
"Failure in launching server: {}",
e
),
}
}
while !running_servers.is_empty() {
let (initial_result, _idx, next_runners) =
futures::future::select_all(running_servers).await;
match initial_result {
Ok(Ok(())) => tracing::info!("server completed successfully"),
Ok(Err(e)) => tracing::error!("error in running server: {}", e),
Err(e) => tracing::error!("error joining server: {}", e),
}
running_servers = next_runners;
}
Ok(())
}