initial commit
Signed-off-by: Solomon Wagner <solow@solow.xyz>
This commit is contained in:
commit
21a4e01e81
94
.gitignore
vendored
Normal file
94
.gitignore
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# ---> Emacs
|
||||||
|
# -*- mode: gitignore; -*-
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
/.emacs.desktop
|
||||||
|
/.emacs.desktop.lock
|
||||||
|
*.elc
|
||||||
|
auto-save-list
|
||||||
|
tramp
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# Org-mode
|
||||||
|
.org-id-locations
|
||||||
|
*_archive
|
||||||
|
|
||||||
|
# flymake-mode
|
||||||
|
*_flymake.*
|
||||||
|
|
||||||
|
# eshell files
|
||||||
|
/eshell/history
|
||||||
|
/eshell/lastdir
|
||||||
|
|
||||||
|
# elpa packages
|
||||||
|
/elpa/
|
||||||
|
|
||||||
|
# reftex files
|
||||||
|
*.rel
|
||||||
|
|
||||||
|
# AUCTeX auto folder
|
||||||
|
/auto/
|
||||||
|
|
||||||
|
# cask packages
|
||||||
|
.cask/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Flycheck
|
||||||
|
flycheck_*.el
|
||||||
|
|
||||||
|
# server auth directory
|
||||||
|
/server/
|
||||||
|
|
||||||
|
# projectiles files
|
||||||
|
.projectile
|
||||||
|
|
||||||
|
# directory configuration
|
||||||
|
.dir-locals.el
|
||||||
|
|
||||||
|
# network security
|
||||||
|
/network-security.data
|
||||||
|
|
||||||
|
|
||||||
|
# ---> Rust
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# RustRover
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
# ---> Vim
|
||||||
|
# Swap
|
||||||
|
[._]*.s[a-v][a-z]
|
||||||
|
!*.svg # comment out if you don't need vector files
|
||||||
|
[._]*.sw[a-p]
|
||||||
|
[._]s[a-rt-v][a-z]
|
||||||
|
[._]ss[a-gi-z]
|
||||||
|
[._]sw[a-p]
|
||||||
|
|
||||||
|
# Session
|
||||||
|
Session.vim
|
||||||
|
Sessionx.vim
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
.netrwhist
|
||||||
|
*~
|
||||||
|
# Auto-generated tag files
|
||||||
|
tags
|
||||||
|
# Persistent undo
|
||||||
|
[._]*.un~
|
||||||
|
|
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "swim-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.96"
|
||||||
|
clap = { version = "4.5.30", features = ["derive", "env"] }
|
||||||
|
colored = "3.0.0"
|
||||||
|
quinn = "0.11.6"
|
||||||
|
serde = { version = "1.0.218", features = ["derive"] }
|
||||||
|
serde_json = "1.0.139"
|
||||||
|
tokio = { version = "1.43.0", features = ["full"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["fmt", "env-filter"] }
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
zlib License
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied warranty. In
|
||||||
|
no event will the authors be held liable for any damages arising from the use
|
||||||
|
of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose, including
|
||||||
|
commercial applications, and to alter it and redistribute it freely, subject to
|
||||||
|
the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software in a
|
||||||
|
product, an acknowledgment in the product documentation would be appreciated
|
||||||
|
but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
237
src/main.rs
Normal file
237
src/main.rs
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Result},
|
||||||
|
net::{UnixListener, UnixStream},
|
||||||
|
sync::{self, mpsc, RwLock},
|
||||||
|
};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use tracing::{error, info, instrument, warn};
|
||||||
|
|
||||||
|
static LICENSE: &'static str = include_str!("../LICENSE");
|
||||||
|
|
||||||
|
const IPC_SOCKET_PATH: &str = "/tmp/swimd.sock";
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||||
|
enum SockType {
|
||||||
|
Udp,
|
||||||
|
Tcp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||||
|
struct PortMapping {
|
||||||
|
sock_type: SockType,
|
||||||
|
from: u32,
|
||||||
|
to: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
|
struct Config {
|
||||||
|
mappings: BTreeMap<u32, PortMapping>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AppState {
|
||||||
|
active_config: Arc<RwLock<Config>>,
|
||||||
|
config: Arc<RwLock<Config>>,
|
||||||
|
notify: sync::mpsc::Sender<()>, // Used to tell the ipc server to reload
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
enum ClientOp {
|
||||||
|
Export,
|
||||||
|
Import,
|
||||||
|
Add(PortMapping),
|
||||||
|
Del(Vec<u32>),
|
||||||
|
Reload,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct IPCRequest {
|
||||||
|
op: ClientOp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
enum ClientResp<S: AsRef<str>> {
|
||||||
|
Ok(Option<S>),
|
||||||
|
Error(S),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: AsRef<str> + Serialize> ClientResp<S> {
|
||||||
|
async fn write_to<W>(&self, mut writer: W) -> Result<()>
|
||||||
|
where
|
||||||
|
W: AsyncWriteExt + std::marker::Unpin,
|
||||||
|
{
|
||||||
|
writer
|
||||||
|
.write_all(serde_json::to_string(self)?.as_bytes())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
async fn handle_ipc_client(mut conn: UnixStream, app: Arc<AppState>) {
|
||||||
|
let config = &app.config;
|
||||||
|
let (reader, mut writer) = conn.split();
|
||||||
|
let mut reader = BufReader::new(reader);
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
if reader.read_line(&mut buf).await.unwrap() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::to_string(&*config.read().await).unwrap();
|
||||||
|
|
||||||
|
let resp: ClientResp<_> = match serde_json::from_str::<IPCRequest>(&buf) {
|
||||||
|
Ok(req) => match req.op {
|
||||||
|
ClientOp::Del(ni) => {
|
||||||
|
let cfg = config.write().await;
|
||||||
|
let km: Vec<String> = ni
|
||||||
|
.iter()
|
||||||
|
.filter(|i| cfg.mappings.contains_key(i))
|
||||||
|
.map(|n| n.to_string())
|
||||||
|
.collect();
|
||||||
|
let kml = km.join(",");
|
||||||
|
if !km.len() == 0 {
|
||||||
|
error!("Bad rule ids when deleting: {}", kml);
|
||||||
|
|
||||||
|
ClientResp::Error(format!("Some route IDs do not exist: {}", kml))
|
||||||
|
} else {
|
||||||
|
for i in ni {
|
||||||
|
info!("Removed ruleset {i}!");
|
||||||
|
}
|
||||||
|
ClientResp::Ok(Some(format!("Removed routes: {kml}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientOp::Export => {
|
||||||
|
let cfg = serde_json::to_string(&*config.read().await).unwrap();
|
||||||
|
if let Err(e) = writer.write_all(cfg.as_bytes()).await {
|
||||||
|
error!("Error writing to ipc socket! {e}");
|
||||||
|
}
|
||||||
|
ClientResp::Ok(None)
|
||||||
|
}
|
||||||
|
ClientOp::Import => todo!(),
|
||||||
|
ClientOp::Add(port_mapping) => {
|
||||||
|
let mut cfg = config.write().await;
|
||||||
|
let next = cfg.mappings.keys().last().unwrap_or(&0) + 1;
|
||||||
|
cfg.mappings.insert(next, port_mapping);
|
||||||
|
ClientResp::Ok(None)
|
||||||
|
}
|
||||||
|
ClientOp::Reload => {
|
||||||
|
app.notify.send(()).await.unwrap();
|
||||||
|
ClientResp::Ok(None)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("Couldn't parse ipc request: {}", e);
|
||||||
|
ClientResp::Error("Bad Request".to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = resp.write_to(writer).await {
|
||||||
|
error!("Error sending response to client: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
async fn ipc_server(app: Arc<AppState>, sock: UnixListener) {
|
||||||
|
loop {
|
||||||
|
match sock.accept().await {
|
||||||
|
Ok((conn, _)) => {
|
||||||
|
tokio::spawn(handle_ipc_client(conn, app.clone()));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Couldn't accept socket connection: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn quic_server(app: Arc<AppState>, mut notify: mpsc::Receiver<()>) {
|
||||||
|
while notify.recv().await.is_some() {
|
||||||
|
let mut active = app.active_config.write().await;
|
||||||
|
let cfg = app.config.read().await.clone();
|
||||||
|
*active = cfg;
|
||||||
|
info!("Reloaded config!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Args)]
|
||||||
|
struct ConnectionAdd {
|
||||||
|
#[arg(help = "The name of a registered remote")]
|
||||||
|
remote: String,
|
||||||
|
#[arg(short, long, help = "Specify a non-default port number")]
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
#[command(
|
||||||
|
about = "Operations relating to tunnels",
|
||||||
|
long_about = "Multiple port mappings can use the same tunnel. It is recommended to create separate tunnels for high traffic ports"
|
||||||
|
)]
|
||||||
|
enum Connection {
|
||||||
|
Add(ConnectionAdd),
|
||||||
|
List,
|
||||||
|
Rm,
|
||||||
|
Info,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum Commands {
|
||||||
|
#[command(subcommand)]
|
||||||
|
Connection(Connection),
|
||||||
|
Remote,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author = "Solomon W.", version, about = "The tunneling firewall")]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(long, help = "Run as the swim daemon")]
|
||||||
|
daemonize: bool,
|
||||||
|
#[arg(long, help = "Display the license")]
|
||||||
|
license: bool,
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Option<Commands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
if cli.license {
|
||||||
|
println!("{}", LICENSE);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.daemonize {
|
||||||
|
let sock = UnixListener::bind(IPC_SOCKET_PATH).expect("Couldn't bind to unix socket!");
|
||||||
|
let (tx, rx) = mpsc::channel(10);
|
||||||
|
|
||||||
|
let config = Config::default();
|
||||||
|
|
||||||
|
let app = Arc::new(AppState {
|
||||||
|
config: Arc::new(RwLock::new(config)),
|
||||||
|
notify: tx,
|
||||||
|
active_config: Arc::new(RwLock::new(Config::default())),
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Early config init here
|
||||||
|
|
||||||
|
let ipc_handle = tokio::spawn(ipc_server(app.clone(), sock));
|
||||||
|
let quic_handle = tokio::spawn(quic_server(app.clone(), rx));
|
||||||
|
|
||||||
|
ipc_handle.await.unwrap();
|
||||||
|
quic_handle.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cmd) = cli.command {
|
||||||
|
match cmd {
|
||||||
|
Commands::Connection(_connection) => println!("Hello swim!"),
|
||||||
|
_ => todo!("Not yet implemented!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user