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
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: taiki-e/upload-rust-binary-action@v1
|
||||
with:
|
||||
bin: leightbox
|
||||
bin: contego
|
||||
tar: unix
|
||||
include: LICENSE
|
||||
checksum: sha256
|
||||
target: ${{ matrix.target }}
|
||||
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]
|
||||
name = "contego"
|
||||
description = "CLI tool for file transfer"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
authors = ["17ms"]
|
||||
description = "A small CLI tool for quick file transfers"
|
||||
repository = "https://github.com/17ms/contego"
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
rand = "0.7.0"
|
||||
x25519-dalek = "1.2.0"
|
||||
aes-gcm = "0.10.2"
|
||||
aes-gcm = "0.10.3"
|
||||
base64 = "0.21.2"
|
||||
sha256 = "1.1.3"
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
./target/release/contego
|
||||
```
|
||||
Usage: contego host [OPTIONS] --key <KEY> <--source <SOURCE>|--files <FILES>...>
|
||||
|
||||
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,
|
||||
parser::{addr_parser, dirpath_parser, filepath_parser},
|
||||
server::Server,
|
||||
util::{filepaths, handle_exit, metadata, Ip},
|
||||
util::{ascii, filepaths, metadata, Ip},
|
||||
};
|
||||
use env_logger::Env;
|
||||
use log::error;
|
||||
use tokio::sync::mpsc;
|
||||
use log::{error, info};
|
||||
use tokio::{signal, sync::mpsc};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(about, version)]
|
||||
@ -60,6 +60,8 @@ enum Commands {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
ascii();
|
||||
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||
let cli = Cli::parse();
|
||||
|
||||
@ -77,22 +79,28 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let paths = filepaths(source, files)?;
|
||||
let (metadata, index) = metadata(&paths).await?;
|
||||
let addr = match (local, ipv6) {
|
||||
let (display_addr, bind_addr) = match (local, ipv6) {
|
||||
(true, _) => Ip::Local.fetch(port)?,
|
||||
(false, true) => Ip::V6.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 {
|
||||
match server.start(rx).await {
|
||||
match server.start(rx, &bind_addr).await {
|
||||
Ok(_) => {}
|
||||
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 } => {
|
||||
let client = Client::new(addr, key, out);
|
||||
|
@ -39,15 +39,19 @@ impl Server {
|
||||
pub async fn start(
|
||||
self: Arc<Self>,
|
||||
mut kill: mpsc::Receiver<()>,
|
||||
bind_addr: &SocketAddr,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
tokio::select! {
|
||||
_ = self.listen() => Ok(()),
|
||||
_ = self.listen(bind_addr) => Ok(()),
|
||||
_ = kill.recv() => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen(self: Arc<Self>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let listener = TcpListener::bind(self.addr).await?;
|
||||
async fn listen(
|
||||
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);
|
||||
|
||||
|
46
src/util.rs
46
src/util.rs
@ -1,15 +1,7 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
error::Error,
|
||||
fs,
|
||||
io::{stdin, Read},
|
||||
net::SocketAddr,
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::{collections::HashMap, env, error::Error, fs, net::SocketAddr, path::PathBuf};
|
||||
|
||||
use log::{debug, info};
|
||||
use tokio::{fs::File, io::BufWriter, sync::mpsc::Sender};
|
||||
use tokio::{fs::File, io::BufWriter};
|
||||
|
||||
use crate::crypto;
|
||||
|
||||
@ -24,24 +16,26 @@ pub enum 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 {
|
||||
Ip::V4 => PUBLIC_IPV4,
|
||||
Ip::V6 => PUBLIC_IPV6,
|
||||
Ip::Local => {
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
Ok(addr)
|
||||
Ok((display_addr, bind_addr))
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,20 +121,12 @@ pub async fn new_file(
|
||||
Ok((BufWriter::new(handle), path))
|
||||
}
|
||||
|
||||
pub async fn handle_exit(tx: Sender<()>) -> Result<(), Box<dyn Error>> {
|
||||
let mut stdin = stdin().lock().bytes();
|
||||
|
||||
loop {
|
||||
let k = match stdin.next() {
|
||||
None => continue,
|
||||
Some(k) => k?,
|
||||
};
|
||||
|
||||
if k == b'q' {
|
||||
tx.send(()).await?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn ascii() {
|
||||
let ascii = " __
|
||||
_________ ____ / /____ ____ _____
|
||||
/ ___/ __ \\/ __ \\/ __/ _ \\/ __ `/ __ \\
|
||||
/ /__/ /_/ / / / / /_/ __/ /_/ / /_/ /
|
||||
\\___/\\____/_/ /_/\\__/\\___/\\__, /\\____/
|
||||
/____/ ";
|
||||
println!("{}\n", ascii);
|
||||
}
|
||||
|
@ -24,14 +24,13 @@ async fn sockets_integration() {
|
||||
.is_test(true)
|
||||
.try_init()
|
||||
.unwrap();
|
||||
//env_logger::builder().is_test(true).try_init().unwrap();
|
||||
|
||||
debug!("Initializing and starting the test");
|
||||
|
||||
let (testdata, paths) = testdata();
|
||||
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 key = String::from("testkey");
|
||||
let c_key = key.clone();
|
||||
@ -40,14 +39,14 @@ async fn sockets_integration() {
|
||||
|
||||
let server_handle = tokio::spawn(async move {
|
||||
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");
|
||||
server.start(rx).await.unwrap();
|
||||
server.start(rx, &bind_addr).await.unwrap();
|
||||
});
|
||||
|
||||
let client_handle = tokio::spawn(async move {
|
||||
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");
|
||||
client.connection().await.unwrap();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user