Compare commits
10 Commits
9d232bed81
...
c1091b56b9
Author | SHA1 | Date | |
---|---|---|---|
|
c1091b56b9 | ||
|
e911350d04 | ||
|
cb9473cf67 | ||
|
5c9320b136 | ||
|
be113f59a8 | ||
|
731c3e4fa5 | ||
|
67fab76476 | ||
|
4c7bca4571 | ||
|
b695d2fa1c | ||
|
e461aa962f |
5
.github/workflows/build.yaml
vendored
5
.github/workflows/build.yaml
vendored
@ -25,13 +25,16 @@ jobs:
|
|||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: taiki-e/upload-rust-binary-action@v1
|
- uses: taiki-e/upload-rust-binary-action@v1
|
||||||
with:
|
with:
|
||||||
bin: leightbox
|
bin: contego
|
||||||
tar: unix
|
tar: unix
|
||||||
include: LICENSE
|
include: LICENSE
|
||||||
checksum: sha256
|
checksum: sha256
|
||||||
|
target: ${{ matrix.target }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
645
Cargo.lock
generated
Executable file → Normal file
645
Cargo.lock
generated
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
Executable file → Normal file
11
Cargo.toml
Executable file → Normal file
@ -1,16 +1,17 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "contego"
|
name = "contego"
|
||||||
description = "CLI tool for file transfer"
|
version = "0.4.0"
|
||||||
version = "0.3.0"
|
authors = ["17ms"]
|
||||||
|
description = "A small CLI tool for quick file transfers"
|
||||||
|
repository = "https://github.com/17ms/contego"
|
||||||
|
license = "MIT"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.28.1", features = ["full"] }
|
tokio = { version = "1.28.1", features = ["full"] }
|
||||||
rand = "0.7.0"
|
rand = "0.7.0"
|
||||||
x25519-dalek = "1.2.0"
|
x25519-dalek = "1.2.0"
|
||||||
aes-gcm = "0.10.2"
|
aes-gcm = "0.10.3"
|
||||||
base64 = "0.21.2"
|
base64 = "0.21.2"
|
||||||
sha256 = "1.1.3"
|
sha256 = "1.1.3"
|
||||||
ureq = "2.6.2"
|
ureq = "2.6.2"
|
||||||
|
36
README.md
36
README.md
@ -15,15 +15,35 @@
|
|||||||
|
|
||||||
The initial key exchange is performed with elliptic-curve Diffie-Hellman. General data exchange is encrypted with AES-GCM. During regular communication payloads are Base64 encoded before being encrypted to prevent delimiter conflicts. SHA-256 hashes of files are compared to ensure data integrity.
|
The initial key exchange is performed with elliptic-curve Diffie-Hellman. General data exchange is encrypted with AES-GCM. During regular communication payloads are Base64 encoded before being encrypted to prevent delimiter conflicts. SHA-256 hashes of files are compared to ensure data integrity.
|
||||||
|
|
||||||
## Cellular networks
|
|
||||||
|
|
||||||
Most cellular ISP's tend to block port forwarding on CGNAT level, which makes it impossible to create inbound connections to such network without a VPN. Luckily many consumer VPNs and self-hosted solutions make port forwarding a trivial task. This is the main reason why the client must fetch information about the public IP from an external service (https://ipinfo.io/ip for IPv4 and https://ipv6.icanhazip.com for IPv6).
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Work in progress. Will be completed when the current release is finished.
|
Check [releases](https://github.com/17ms/contego/releases) for an up-to-date executables or build from source with `cargo build --release`.
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
```shell
|
```
|
||||||
cargo build --release
|
Usage: contego host [OPTIONS] --key <KEY> <--source <SOURCE>|--files <FILES>...>
|
||||||
./target/release/contego
|
|
||||||
|
Options:
|
||||||
|
-k, --key <KEY> Access key
|
||||||
|
-s, --source <SOURCE> Path to a source file (alternative to --files)
|
||||||
|
-f, --files <FILES>... Paths to shareable files (alternative to --source)
|
||||||
|
-p, --port <PORT> Host port [default: 8080]
|
||||||
|
-6, --ipv6 IPv6 instead of IPv4
|
||||||
|
-c, --chunksize <CHUNKSIZE> Transmit chunksize in bytes [default: 8192]
|
||||||
|
-l, --local Host locally
|
||||||
|
-h, --help Print help
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: contego connect --addr <ADDR> --out <OUT> --key <KEY>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-a, --addr <ADDR> IP address of the instance
|
||||||
|
-o, --out <OUT> Path to an output folder
|
||||||
|
-k, --key <KEY> Access key
|
||||||
|
-h, --help Print help
|
||||||
```
|
```
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
22
src/main.rs
22
src/main.rs
@ -6,11 +6,11 @@ use contego::{
|
|||||||
client::Client,
|
client::Client,
|
||||||
parser::{addr_parser, dirpath_parser, filepath_parser},
|
parser::{addr_parser, dirpath_parser, filepath_parser},
|
||||||
server::Server,
|
server::Server,
|
||||||
util::{filepaths, handle_exit, metadata, Ip},
|
util::{ascii, filepaths, metadata, Ip},
|
||||||
};
|
};
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
use log::error;
|
use log::{error, info};
|
||||||
use tokio::sync::mpsc;
|
use tokio::{signal, sync::mpsc};
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(about, version)]
|
#[command(about, version)]
|
||||||
@ -60,6 +60,8 @@ enum Commands {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
ascii();
|
||||||
|
|
||||||
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
@ -77,22 +79,28 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
let paths = filepaths(source, files)?;
|
let paths = filepaths(source, files)?;
|
||||||
let (metadata, index) = metadata(&paths).await?;
|
let (metadata, index) = metadata(&paths).await?;
|
||||||
let addr = match (local, ipv6) {
|
let (display_addr, bind_addr) = match (local, ipv6) {
|
||||||
(true, _) => Ip::Local.fetch(port)?,
|
(true, _) => Ip::Local.fetch(port)?,
|
||||||
(false, true) => Ip::V6.fetch(port)?,
|
(false, true) => Ip::V6.fetch(port)?,
|
||||||
(false, false) => Ip::V4.fetch(port)?,
|
(false, false) => Ip::V4.fetch(port)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let server = Server::new(addr, key, chunksize, metadata, index);
|
let server = Server::new(display_addr, key, chunksize, metadata, index);
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
match server.start(rx).await {
|
match server.start(rx, &bind_addr).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => error!("Error during server execution: {}", e),
|
Err(e) => error!("Error during server execution: {}", e),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
handle_exit(tx).await?;
|
match signal::ctrl_c().await {
|
||||||
|
Ok(_) => {
|
||||||
|
tx.send(()).await?;
|
||||||
|
info!("Captured Ctrl+C, shutting down");
|
||||||
|
}
|
||||||
|
Err(_) => error!("Failed to listen for a Ctrl+C event"),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Commands::Connect { addr, out, key } => {
|
Commands::Connect { addr, out, key } => {
|
||||||
let client = Client::new(addr, key, out);
|
let client = Client::new(addr, key, out);
|
||||||
|
@ -39,15 +39,19 @@ impl Server {
|
|||||||
pub async fn start(
|
pub async fn start(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
mut kill: mpsc::Receiver<()>,
|
mut kill: mpsc::Receiver<()>,
|
||||||
|
bind_addr: &SocketAddr,
|
||||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = self.listen() => Ok(()),
|
_ = self.listen(bind_addr) => Ok(()),
|
||||||
_ = kill.recv() => Ok(()),
|
_ = kill.recv() => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn listen(self: Arc<Self>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
async fn listen(
|
||||||
let listener = TcpListener::bind(self.addr).await?;
|
self: Arc<Self>,
|
||||||
|
bind_addr: &SocketAddr,
|
||||||
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
let listener = TcpListener::bind(bind_addr).await?;
|
||||||
|
|
||||||
info!("Listening on {} - Access key: {}", self.addr, self.key);
|
info!("Listening on {} - Access key: {}", self.addr, self.key);
|
||||||
|
|
||||||
|
46
src/util.rs
46
src/util.rs
@ -1,15 +1,7 @@
|
|||||||
use std::{
|
use std::{collections::HashMap, env, error::Error, fs, net::SocketAddr, path::PathBuf};
|
||||||
collections::HashMap,
|
|
||||||
env,
|
|
||||||
error::Error,
|
|
||||||
fs,
|
|
||||||
io::{stdin, Read},
|
|
||||||
net::SocketAddr,
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use tokio::{fs::File, io::BufWriter, sync::mpsc::Sender};
|
use tokio::{fs::File, io::BufWriter};
|
||||||
|
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
|
|
||||||
@ -24,24 +16,26 @@ pub enum Ip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Ip {
|
impl Ip {
|
||||||
pub fn fetch(self, port: u16) -> Result<SocketAddr, Box<dyn Error>> {
|
pub fn fetch(self, port: u16) -> Result<(SocketAddr, SocketAddr), Box<dyn Error>> {
|
||||||
let addr = match self {
|
let addr = match self {
|
||||||
Ip::V4 => PUBLIC_IPV4,
|
Ip::V4 => PUBLIC_IPV4,
|
||||||
Ip::V6 => PUBLIC_IPV6,
|
Ip::V6 => PUBLIC_IPV6,
|
||||||
Ip::Local => {
|
Ip::Local => {
|
||||||
let addr_str = format!("127.0.0.1:{}", port);
|
let addr_str = format!("127.0.0.1:{}", port);
|
||||||
return Ok(addr_str.parse::<SocketAddr>()?);
|
let addr = addr_str.parse::<SocketAddr>()?;
|
||||||
|
return Ok((addr, addr));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Fetching IP information from {}", addr);
|
info!("Fetching IP information from {}", addr);
|
||||||
|
|
||||||
let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port);
|
let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port);
|
||||||
let addr = res.parse::<SocketAddr>()?;
|
let display_addr = res.parse::<SocketAddr>()?;
|
||||||
|
let bind_addr = format!("0.0.0.0:{}", port).parse::<SocketAddr>()?;
|
||||||
|
|
||||||
debug!("IP: {}", res);
|
debug!("IP: {}", res);
|
||||||
|
|
||||||
Ok(addr)
|
Ok((display_addr, bind_addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,20 +121,12 @@ pub async fn new_file(
|
|||||||
Ok((BufWriter::new(handle), path))
|
Ok((BufWriter::new(handle), path))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_exit(tx: Sender<()>) -> Result<(), Box<dyn Error>> {
|
pub fn ascii() {
|
||||||
let mut stdin = stdin().lock().bytes();
|
let ascii = " __
|
||||||
|
_________ ____ / /____ ____ _____
|
||||||
loop {
|
/ ___/ __ \\/ __ \\/ __/ _ \\/ __ `/ __ \\
|
||||||
let k = match stdin.next() {
|
/ /__/ /_/ / / / / /_/ __/ /_/ / /_/ /
|
||||||
None => continue,
|
\\___/\\____/_/ /_/\\__/\\___/\\__, /\\____/
|
||||||
Some(k) => k?,
|
/____/ ";
|
||||||
};
|
println!("{}\n", ascii);
|
||||||
|
|
||||||
if k == b'q' {
|
|
||||||
tx.send(()).await?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
@ -24,14 +24,13 @@ async fn sockets_integration() {
|
|||||||
.is_test(true)
|
.is_test(true)
|
||||||
.try_init()
|
.try_init()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
//env_logger::builder().is_test(true).try_init().unwrap();
|
|
||||||
|
|
||||||
debug!("Initializing and starting the test");
|
debug!("Initializing and starting the test");
|
||||||
|
|
||||||
let (testdata, paths) = testdata();
|
let (testdata, paths) = testdata();
|
||||||
let (metadata, index) = metadata(&paths).await.unwrap();
|
let (metadata, index) = metadata(&paths).await.unwrap();
|
||||||
|
|
||||||
let addr = Ip::Local.fetch(8080).unwrap();
|
let (display_addr, bind_addr) = Ip::Local.fetch(8080).unwrap();
|
||||||
let outdir = PathBuf::from("./tests/output/");
|
let outdir = PathBuf::from("./tests/output/");
|
||||||
let key = String::from("testkey");
|
let key = String::from("testkey");
|
||||||
let c_key = key.clone();
|
let c_key = key.clone();
|
||||||
@ -40,14 +39,14 @@ async fn sockets_integration() {
|
|||||||
|
|
||||||
let server_handle = tokio::spawn(async move {
|
let server_handle = tokio::spawn(async move {
|
||||||
debug!("Initializing the asynchronous server task");
|
debug!("Initializing the asynchronous server task");
|
||||||
let server = Server::new(addr, key, 8192, metadata, index);
|
let server = Server::new(display_addr, key, 8192, metadata, index);
|
||||||
debug!("Starting to listen to incoming connections");
|
debug!("Starting to listen to incoming connections");
|
||||||
server.start(rx).await.unwrap();
|
server.start(rx, &bind_addr).await.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
let client_handle = tokio::spawn(async move {
|
let client_handle = tokio::spawn(async move {
|
||||||
debug!("Initializing the asynchronous client task");
|
debug!("Initializing the asynchronous client task");
|
||||||
let client = Client::new(addr, c_key, outdir);
|
let client = Client::new(display_addr, c_key, outdir);
|
||||||
debug!("Connecting to the server");
|
debug!("Connecting to the server");
|
||||||
client.connection().await.unwrap();
|
client.connection().await.unwrap();
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user