Browse Source

I swear, I'll start making proper commits soon

pull/3/head
Magic_RB 11 months ago
parent
commit
bea372c584
  1. 17
      Cargo.toml
  2. 37
      README.org
  3. 5
      config.toml
  4. 3
      config2.toml
  5. 3
      crypto/Cargo.toml
  6. 89
      crypto/src/private_key.rs
  7. 114
      crypto/src/public_key.rs
  8. 113
      crypto/src/shared_secret.rs
  9. BIN
      images/graph.png
  10. BIN
      images/localhost.png
  11. 19
      netstar-error/Cargo.toml
  12. 267
      netstar-error/src/lib.rs
  13. 276
      spec/netstar.org
  14. BIN
      spec/netstar.pdf
  15. 83
      src/cmdline_opts.rs
  16. 40
      src/config.rs
  17. 155
      src/error.rs
  18. 7
      src/lib.rs
  19. 152
      src/main.rs
  20. 150
      src/netstar/central_processor.rs
  21. 227
      src/netstar/central_store.rs
  22. 165
      src/netstar/local_gateway/mod.rs
  23. 0
      src/netstar/local_protocol/mod.rs
  24. 0
      src/netstar/local_protocol/packet/mod.rs
  25. 65
      src/netstar/mod.rs
  26. 70
      src/netstar/network_gateway.rs
  27. 34
      src/netstar/packet/mod.rs
  28. 4
      src/netstar/packet/parser.rs
  29. 26
      src/netstar/peer.rs
  30. 94
      src/netstar/work_orchestrator.rs
  31. 28
      src/netstar/worker.rs
  32. 14
      src/rest/add_peer.rs
  33. 11
      src/rest/attempt_handshake.rs
  34. 11
      src/rest/delete_peer.rs
  35. 16
      src/rest/get_peer.rs
  36. 12
      src/rest/list_peers.rs
  37. 6
      src/rest/mod.rs
  38. 14
      src/rest/set_peer.rs
  39. 14
      test.sh

17
Cargo.toml

@ -10,25 +10,28 @@ build = "build.rs"
[dependencies]
serde = { version = "1.0.*", features = ["derive"] }
toml = "0.5.*"
bytes = "1.0.*"
serde_json = "1.0.*"
bytes = "1.*.*"
rand = "0.7.*"
clap = { version = "3.0.0-beta.2", features = ["derive"] }
nom = { version = "6.1.*", features = ["std", "bitvec", "lexical", "alloc"]}
nom = { version = "6.1.*", features = ["std", "bitvec", "lexical", "alloc"] }
actix = "0.11.0-beta.1" # "0.10.*"
actix-rt = "2.0.0-beta.2" # "1.1.*"
actix = "0.11.0-beta.2" # "0.10.*"
actix-rt = "2.0.0" # "1.1.*"
actix-web = "4.0.0-beta.3"
tokio = { version = "1.1.0", features = ["net", "fs", "io-util"] }
tokio = { version = "1.0.0", features = ["net", "fs", "io-util" ] }
tokio-util = { version = "0.6.*", features = ["codec", "net"] }
futures = "0.3.*"
log = "0.4.*"
simplelog = "0.9.0"
crypto = { path = "crypto" }
netstar-error = { path = "netstar-error" }
anyhow = "1.0.38"
thiserror = "1.0.23"
[dev-dependencies]
criterion = { version = "0.3.*", features = ["async_tokio"] }

37
README.org

@ -1,5 +1,14 @@
* netstar
** TODO Transparent REST
We need a way, probably a lot of traits, to make sure that when netstar communicates over HTTP with it self, the data
is exactly the same on both sides. That is before the adaptation for HTTP transmition. We must not loose Error or Ok
information, but we can drop Internal Errors, (we need a catch all External Error for Internal Errors)
** TODO How to handle keepalives and handshakes
Should a handshake be sent automatically? Is it dangerous? Should the user be able to manually send one? Decide and
implement.
** Peer API
- get by public key GET /peer?publickey=<publickey>
- connection state
@ -17,6 +26,7 @@
- list -> public keys GET /peers
** Description
Outdated!
#+BEGIN_SRC dot :file images/graph.png :exports results
digraph graphname {
w0 [label="Worker 0"]
@ -51,14 +61,14 @@
struct Encrypt {
packet: Packet,
xaed: XChaCha20Poly1305
} -> Result<Packet, crate::Error>
} -> EResult<Packet>
#+END_SRC
+ Decrypt
#+BEGIN_SRC rustic
struct Decrypt {
packet: Packet,
xaed: XChaCha20Poly1305
} -> Result<Packet, crate::Error>
} -> EResult<Packet>
#+END_SRC
*** f2
@ -72,7 +82,7 @@
packet: Packet,
xaed: XChaCha20Poly1305,
buffer: BytesMut
} -> Result<Packet, crate::Error>
} -> EResult<(Packet, BytesMut)>
#+END_SRC
+ Decrypt
#+BEGIN_SRC rustic
@ -80,17 +90,17 @@
packet: Packet,
xaed: XChaCha20Poly1305,
buffer: BytesMut
} -> Result<Packet, crate::Error>
} -> EResult<(Packet, BytesMut)>
#+END_SRC
*** f3
Used to get and return =BytesMut= to hopeful avoid allocations whenever possible.
+ GetBytesMut
#+BEGIN_SRC rustic
struct GetBytesMut {} -> Result<BytesMut, crate::Error>
struct GetBytesMut {} -> EResult<BytesMut>
#+END_SRC
+ ReturnBytesMut
#+BEGIN_SRC rustic
struct ReturnBytesMut(BytesMut) -> Result<(), ()>
struct ReturnBytesMut(BytesMut)
#+END_SRC
*** f4
Just passes the received or to be sent packets from/to the NetworkGateway to/from the CentralProcessor.
@ -103,7 +113,7 @@
struct EditPeer<R: 'static, F: Fn(&mut Peer) -> R> {
public_key: PublicKey,
fun: F,
} -> Result<R, Error>
} -> EResult<R>
#+END_SRC
Calls the provided closure with an immutable reference to the Peer with the provided public key, provided it exists.
@ -111,7 +121,7 @@
struct GetPeer<R: 'static, F: Fn(&Peer) -> R> {
public_key: PublicKey,
fun: F,
} -> Result<R, Error>
} -> EResult<R>
#+END_SRC
Add a new peer, with the provided parameters, fails if a peer with the same public key already exists.
@ -120,19 +130,24 @@
public_key: PublicKey,
endpoint: Option<SocketAddr>,
shared_secret: SharedSecret
} -> Result<(), Error>
} -> EResult<()>
#+END_SRC
Deletes a peer with the provided public key, fails if the peer doesn't exist.
#+BEGIN_SRC rustic
struct DeletePeer {
public_key: PublicKey
} -> Result<(), Error>
} -> EResult<()>
#+END_SRC
Lists all peers.
#+BEGIN_SRC rustic
struct ListPeers() -> EResult<Vec<Peer>>
#+END_SRC
*** f6
Maps an id hash to a public key, if it exists.
#+BEGIN_SRC rustic
struct MapIdHash {
idhash: IdHash
} -> Result<PublicKeym Error>
} -> EResult<PublicKey>
#+END_SRC

5
config.toml

@ -1,2 +1,3 @@
connection_address = "0.0.0.0:6688"
local_address = "127.0.0.1:6677"
connection_address = "0.0.0.0:5555"
local_address = "127.0.0.1:8050"
private_key = "UKt559mHS9etJJKrgkAKHAZKAcDTOBq5-QdTj7PiQmw"

3
config2.toml

@ -0,0 +1,3 @@
connection_address = "0.0.0.0:5556"
local_address = "127.0.0.1:8060"
private_key = "WHuglOznGrB6myIF2uFJGstDeM3PH3DbuWQNKVXkHHA"

3
crypto/Cargo.toml

@ -11,4 +11,5 @@ serde = { version = "1.0.*", features = ["derive"] }
rand = "0.7.*"
x25519-dalek = "1.1.*"
sha3 = "0.9.*"
hex = "0.4.*"
hex = "0.4.*" # TODO maybe yeet and replace with data-encoding
data-encoding = "2.3.*"

89
crypto/src/private_key.rs

@ -1,14 +1,11 @@
use crate::PublicKey;
use serde::{Deserialize, Serialize};
#[derive(Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
Serialize,
Deserialize)]
Hash)]
pub struct PrivateKey(pub(crate) [u8; 32]);
impl PrivateKey {
@ -31,3 +28,87 @@ impl PrivateKey {
.clone()
}
}
use serde::{Deserialize, Serialize, de::Visitor};
use std::fmt;
impl Serialize for PrivateKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer {
serializer.serialize_str(data_encoding::BASE64URL_NOPAD.encode(&self.0).as_str())
}
}
struct PrivateKeyVisitor;
impl<'de> Visitor<'de> for PrivateKeyVisitor {
type Value = PrivateKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a base64 encoded 32-byte public key")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
use serde::de::Unexpected;
use data_encoding::BASE64URL_NOPAD;
let mut public_key = PrivateKey([0; 32]);
let vec = BASE64URL_NOPAD.decode(v.as_ref());
match vec { // TODO do the same for the other types
Ok(res) => {
if res.len() == 32 {
public_key.0.copy_from_slice(&res[0..32]);
Ok(public_key)
} else {
Err(E::invalid_length(res.len(), &"a base64 encoded 32-byte shared secret"))
}
},
Err(err) => Err(E::invalid_value(Unexpected::Str(v), &"a base64 encoded 32-byte shared secret"))
}
// TODO WHAT THE ACTUAL FUCK IS HAPPENING HERE
// let array_len = BASE64URL.decode_len(v.len());
// match array_len {
// Ok(array_len) => {
// if vec.len() == 32 {
// match BASE64URL.decode_mut(v.as_ref(), &mut public_key.0) {
// Ok(res) => {
// public_key.0 = vec[0..32];
// Ok(public_key)
// }
// Err(err) => Err(E::invalid_value(Unexpected::Str(v), &format!("a base64 encoded 32-byte public key A, {:?}", err).as_str()))
// } // YXNkZmdoamtsOydxd2VydHl1aW9wW116eGN2Ym5tYQo-
// } else {
// Err(E::invalid_length(vec.len(), &"a base64 encoded 32-byte public key"))
// }
// }
// Err(err) => Err(E::invalid_value(Unexpected::Str(v), &format!("a base64 encoded 32-byte public key B, {:?}", err).as_str())),
// }
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v.as_str())
}
}
impl<'de> Deserialize<'de> for PrivateKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
deserializer.deserialize_str(PrivateKeyVisitor)
}
}

114
crypto/src/public_key.rs

@ -1,4 +1,4 @@
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize, de::Visitor};
use std::{fmt, convert::{TryFrom, TryInto}};
use crate::PrivateKey;
@ -8,14 +8,118 @@ use crate::PrivateKey;
Debug,
Eq,
PartialEq,
Hash,
Serialize,
Deserialize)]
Hash)]
pub struct PublicKey(pub(crate) [u8; 32]);
impl TryFrom<&str> for PublicKey {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
use data_encoding::BASE64URL_NOPAD;
let mut public_key = PublicKey([0; 32]);
let vec = BASE64URL_NOPAD.decode(value.as_ref());
match vec { // TODO do the same for the other types
Ok(res) => {
if res.len() == 32 {
public_key.0.copy_from_slice(&res[0..32]);
Ok(public_key)
} else {
Err("".to_string()) // TODO error
}
},
Err(_err) => Err("".to_string()) // TODO error
}
}
}
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer {
serializer.serialize_str(data_encoding::BASE64URL_NOPAD.encode(&self.0).as_str())
}
}
struct PublicKeyVisitor;
impl<'de> Visitor<'de> for PublicKeyVisitor {
type Value = PublicKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a base64 encoded 32-byte public key")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
use serde::de::Unexpected;
use data_encoding::BASE64URL_NOPAD;
let mut public_key = PublicKey([0; 32]);
let vec = BASE64URL_NOPAD.decode(v.as_ref());
match vec { // TODO do the same for the other types
Ok(res) => {
if res.len() == 32 {
public_key.0.copy_from_slice(&res[0..32]);
Ok(public_key)
} else {
Err(E::invalid_length(res.len(), &"a base64 encoded 32-byte public key"))
}
},
Err(err) => Err(E::invalid_value(Unexpected::Str(v), &"a base64 encoded 32-byte public key"))
}
// TODO WHAT THE ACTUAL FUCK IS HAPPENING HERE
// let array_len = BASE64URL.decode_len(v.len());
// match array_len {
// Ok(array_len) => {
// if vec.len() == 32 {
// match BASE64URL.decode_mut(v.as_ref(), &mut public_key.0) {
// Ok(res) => {
// public_key.0 = vec[0..32];
// Ok(public_key)
// }
// Err(err) => Err(E::invalid_value(Unexpected::Str(v), &format!("a base64 encoded 32-byte public key A, {:?}", err).as_str()))
// } // YXNkZmdoamtsOydxd2VydHl1aW9wW116eGN2Ym5tYQo-
// } else {
// Err(E::invalid_length(vec.len(), &"a base64 encoded 32-byte public key"))
// }
// }
// Err(err) => Err(E::invalid_value(Unexpected::Str(v), &format!("a base64 encoded 32-byte public key B, {:?}", err).as_str())),
// }
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v.as_str())
}
}
impl<'de> Deserialize<'de> for PublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
deserializer.deserialize_str(PublicKeyVisitor)
}
}
impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r#""{}""#, hex::encode(self.0))
write!(f, r#""{}""#, data_encoding::BASE64.encode(&self.0))
}
}

113
crypto/src/shared_secret.rs

@ -1,13 +1,11 @@
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt};
#[derive(Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
Serialize,
Deserialize)]
Hash)]
pub struct SharedSecret(pub(crate) [u8; 32]);
impl SharedSecret {
@ -17,3 +15,110 @@ impl SharedSecret {
Self(shared_secret)
}
}
impl TryFrom<&str> for SharedSecret {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
use data_encoding::BASE64URL_NOPAD;
let mut shared_secret = SharedSecret([0; 32]);
let vec = BASE64URL_NOPAD.decode(value.as_ref());
match vec { // TODO do the same for the other types
Ok(res) => {
if res.len() == 32 {
shared_secret.0.copy_from_slice(&res[0..32]);
Ok(shared_secret)
} else {
Err("".to_string()) // TODO error
}
},
Err(_err) => Err("".to_string()) // TODO error
}
}
}
use serde::{Deserialize, Serialize, de::Visitor};
impl Serialize for SharedSecret {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer {
serializer.serialize_str(data_encoding::BASE64URL_NOPAD.encode(&self.0).as_str())
}
}
struct SharedSecretVisitor;
impl<'de> Visitor<'de> for SharedSecretVisitor {
type Value = SharedSecret;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a base64 encoded 32-byte public key")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
use serde::de::Unexpected;
use data_encoding::BASE64URL_NOPAD;
let mut public_key = SharedSecret([0; 32]);
let vec = BASE64URL_NOPAD.decode(v.as_ref());
match vec { // TODO do the same for the other types
Ok(res) => {
if res.len() == 32 {
public_key.0.copy_from_slice(&res[0..32]);
Ok(public_key)
} else {
Err(E::invalid_length(res.len(), &"a base64 encoded 32-byte shared secret"))
}
},
Err(err) => Err(E::invalid_value(Unexpected::Str(v), &"a base64 encoded 32-byte shared secret"))
}
// TODO WHAT THE ACTUAL FUCK IS HAPPENING HERE
// let array_len = BASE64URL.decode_len(v.len());
// match array_len {
// Ok(array_len) => {
// if vec.len() == 32 {
// match BASE64URL.decode_mut(v.as_ref(), &mut public_key.0) {
// Ok(res) => {
// public_key.0 = vec[0..32];
// Ok(public_key)
// }
// Err(err) => Err(E::invalid_value(Unexpected::Str(v), &format!("a base64 encoded 32-byte public key A, {:?}", err).as_str()))
// } // YXNkZmdoamtsOydxd2VydHl1aW9wW116eGN2Ym5tYQo-
// } else {
// Err(E::invalid_length(vec.len(), &"a base64 encoded 32-byte public key"))
// }
// }
// Err(err) => Err(E::invalid_value(Unexpected::Str(v), &format!("a base64 encoded 32-byte public key B, {:?}", err).as_str())),
// }
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(v.as_str())
}
}
impl<'de> Deserialize<'de> for SharedSecret {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
deserializer.deserialize_str(SharedSecretVisitor)
}
}

BIN
images/graph.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 56 KiB

BIN
images/localhost.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

19
netstar-error/Cargo.toml

@ -0,0 +1,19 @@
[package]
name = "netstar-error"
version = "0.1.0"
authors = ["Magic_RB <magic_rb@redalder.org>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.*"
thiserror = "1.0.*"
serde = { version = "1.0.*", features = ["derive"] }
log = "0.4.*"
actix = "0.11.0-beta.2" # "0.10.*"
actix-web = "4.0.0-beta.3"
crypto = { path = "../crypto" }

267
netstar-error/src/lib.rs

@ -0,0 +1,267 @@
#![feature(backtrace)]
#![feature(try_trait)]
use std::{backtrace::Backtrace, fmt};
use crypto::PublicKey;
use thiserror::Error as thisError;
use std::error::Error as stdError;
use serde::Serialize;
#[derive(thisError, Debug, Serialize)] // TODO what about expected errors, like packet (de)serialization and packet crypto
pub enum InErrorKind {
#[error("Actix Mailbox error")]
Mailbox,
#[error("Packet deserializetion error")]
PacketDeserialization,
#[error("Packet decryption error")]
PacketCrypto,
#[error("Socket bind error")]
SocketBindError
}
#[derive(thisError, Debug)]
pub struct InError {
kind: InErrorKind,
cause: Box<dyn stdError + Send>,
backtrace: Backtrace
}
impl fmt::Display for InError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self) // TODO proper formatting
}
}
#[derive(thisError, Debug, Serialize)]
pub enum ExtError {
#[error("Cannot read configuration from {}", path)]
CannotReadConfiguration { path: String },
#[error("Cannot write configuration to {}", path)]
CannotWriteConfiguration { path: String },
#[error("Unknown peer with public key {}", public_key)]
UnknownPeer { public_key: PublicKey },
#[error("Peer with public key {} already exists", public_key)]
PeerAlreadyExists { public_key: PublicKey },
#[error("No endpoint for peer {}", public_key)]
NoEndpoint { public_key: PublicKey }
}
#[derive(Debug)]
pub enum EResult<O> {
Ok(O),
InErr(InError),
ExtErr(ExtError)
}
impl<O> EResult<O> {
pub fn map<U, F: FnOnce(O) -> U>(self, op: F) -> EResult<U> {
match self {
EResult::Ok(ok) => EResult::Ok(op(ok)),
EResult::InErr(inerr) => EResult::InErr(inerr),
EResult::ExtErr(exterr) => EResult::ExtErr(exterr)
}
}
pub fn report<F: FnOnce(O)>(self, f: F) {
match self {
EResult::Ok(value) => f(value),
EResult::InErr(inerror) => log::error!("{:?}", inerror),
EResult::ExtErr(exterror) => log::error!("{:?}", exterror)
}
}
}
use std::ops::Try;
impl<O> Try for EResult<O> {
type Ok = O;
type Error = Error;
fn into_result(self) -> Result<O, Error> {
match self {
EResult::Ok(o) => Ok(o),
EResult::InErr(err) => Err(Error::InError(err)),
EResult::ExtErr(err) => Err(Error::ExtError(err))
}
}
fn from_error(v: Error) -> Self {
match v {
Error::InError(err) => EResult::InErr(err),
Error::ExtError(err) => EResult::ExtErr(err)
}
}
fn from_ok(v: O) -> Self {
EResult::Ok(v)
}
}
use actix_web::{body::Body, HttpResponse};
impl<O: Into<Body>> EResult<O> { // TODO we may need to set custom header or smth at some point
pub fn http_body(self) -> HttpResponse {
match self {
EResult::Ok(value) => {
HttpResponse::Ok()
.content_type("text/plain")
.body(value)
}
EResult::InErr(inerr) => {
log::error!("An internal error was caught at REST gateway! {:?}", inerr);
HttpResponse::InternalServerError()
.finish()
}
EResult::ExtErr(exterr) => {
match exterr {
ExtError::CannotReadConfiguration { .. } => HttpResponse::BadRequest(),
ExtError::CannotWriteConfiguration { .. } => HttpResponse::BadRequest(),
ExtError::UnknownPeer { .. } => HttpResponse::NotFound(),
ExtError::PeerAlreadyExists { .. } => HttpResponse::BadRequest(),
ExtError::NoEndpoint { .. } => HttpResponse::BadRequest(),
}.json(Box::new(exterr))
}
}
}
}
use std::ops::Deref;
impl<O> EResult<O>
where
O: Serialize,
{ // TODO we may need to set custom header or smth at some point
pub fn http_json(self) -> HttpResponse {
match self {
EResult::Ok(value) => {
HttpResponse::Ok()
.content_type("application/json")
.json(Box::new(value))
}
EResult::InErr(inerr) => {
log::error!("An internal error was caught at REST gateway! {:?}", inerr);
HttpResponse::InternalServerError()
.finish()
}
// TODO factor out
EResult::ExtErr(exterr) => {
match exterr {
ExtError::CannotReadConfiguration { .. } => HttpResponse::BadRequest(),
ExtError::CannotWriteConfiguration { .. } => HttpResponse::BadRequest(),
ExtError::UnknownPeer { .. } => HttpResponse::NotFound(),
ExtError::PeerAlreadyExists { .. } => HttpResponse::BadRequest(),
ExtError::NoEndpoint { .. } => HttpResponse::BadRequest(),
}.json(Box::new(exterr))
}
}
}
}
use actix::dev::MessageResponse;
impl<A: actix::Actor, M: actix::Message<Result = Self>, O> MessageResponse<A, M> for EResult<O> {
fn handle<R: actix::dev::ResponseChannel<M>>(self, ctx: &mut A::Context, tx: Option<R>) {
if let Some(tx) = tx {
tx.send(self)
}
}
}
#[derive(Debug)]
pub enum Error {
InError(InError),
ExtError(ExtError)
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error {
pub fn internal<E>(kind: InErrorKind, cause: E) -> InError
where E: Into<Box<dyn stdError + Send + Sync>>
{
InError {
kind,
cause: cause.into(),
backtrace: Backtrace::capture()
}
}
}
impl InError {
pub fn result<O>(self) -> EResult<O> {
EResult::InErr(self)
}
}
impl ExtError {
pub fn result<O>(self) -> EResult<O> {
EResult::ExtErr(self)
}
}
pub trait ErrorWrap<O> {
fn internal(self, in_error_kind: InErrorKind) -> EResult<O>;
fn external(self, ext_error: ExtError) -> EResult<O>;
}
impl<O, E: Into<Box<dyn stdError + Send + Sync>>> ErrorWrap<O> for Result<O, E> {
fn internal(self, in_error_kind: InErrorKind) -> EResult<O> {
match self {
Ok(ok) => EResult::Ok(ok),
Err(err) => EResult::InErr(Error::internal(in_error_kind, err))
}
}
fn external(self, ext_error: ExtError) -> EResult<O> {
match self {
Ok(ok) => EResult::Ok(ok),
Err(_err) => EResult::ExtErr(ext_error)
}
}
}
pub trait ErrorCollapse<O> {
fn collapse(self) -> EResult<O>;
}
impl<O> ErrorCollapse<O> for Result<EResult<O>, actix::MailboxError> {
fn collapse(self) -> EResult<O> {
match self {
Ok(res) => res,
Err(err) => EResult::InErr(Error::internal(InErrorKind::Mailbox, err))
}
}
}
impl<O> ErrorCollapse<O> for EResult<EResult<O>> {
fn collapse(self) -> EResult<O> {
match self {
EResult::Ok(res) => res,
EResult::ExtErr(err) => EResult::ExtErr(err),
EResult::InErr(err) => EResult::InErr(err),
}
}
}
impl<O> ErrorWrap<O> for Option<O> {
fn internal(self, _in_error_kind: InErrorKind) -> EResult<O> {
unimplemented!()
}
fn external(self, ext_error: ExtError) -> EResult<O> {
match self {
Some(ok) => EResult::Ok(ok),
None => EResult::ExtErr(ext_error)
}
}
}
pub mod prelude {
pub use super::{Error, ErrorWrap, EResult, InErrorKind, ExtError, ErrorCollapse};
}

276
spec/netstar.org

@ -0,0 +1,276 @@
#+LATEX_CLASS: article
#+LATEX_HEADER: \usepackage[a4paper, left=1.5cm, right=1.5cm, top=2cm, bottom=2cm]{geometry}
#+LATEX_HEADER: \usepackage{bytefield}
#+LATEX_HEADER: \usepackage{fancyvrb}
#+OPTIONS: tex:imagemagick
#+TITLE: Netstar
#+AUTHOR: Richard Brežák
#+DATE: 2019-12-01 - 2021-01-28
Copyright (C) 2021 Richard Brežák. \\
Permission is granted to copy, distribute and/or modify this document \\
under the terms of the GNU Free Documentation License, Version 1.3 \\
or any later version published by the Free Software Foundation; \\
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. \\
* Operational Description
Netstar is a suite of connected protocols and reference implementations which set out to create a fully distributed
overlay network with geographical locality and organization. Netstar is split into two distinct parts, the low level
protocol responsible for establishing connections between peers, which functions very similar to Wireguard, and a
higher level protocol, which responsible for assembling individual peers into fully functioning overlay networks.
Furthermore the lowerlevel protocol is split, once again, two parts, the remote part, responsible for making
connections to the outside world, and the local part which is used for configuring the whole application.
** Definitions
*** General terms
| Term | Rust | Definition |
|--------------------+----------------------+------------------------------------------------------------------------------------------------------|
| peer | N/A | Any computer running an implementation of this protocol |
| x25519 public key | [u8; 32] | x25519 public key |
| x25519 private key | [u8; 32] | x25519 private key |
| x25519 shared key | [u8; 32] | x25519 shared key derived from a /x25519 public key/ and /x25519 private key/ |
| key pair | ([u8; 32], [u8; 32]) | x25519 key pair |
| shared secret | [u8; 32] | randomly generated secret |
| symmetric key | [u8; 32] | key used in ChaCha20, derived from a /x25519 shared key/, 2 /ephemeral blobs/ and a /shared secret/. |
| x25519 ID Hash | [u8; 32] | A ID hash built by sha256 hashing the sender's public key and the shared secret |
*** REST Protocol
This protocol is used to configure an implementation of netstar. It can add and delete peers, edit peers, list
peers, set public key <-> IP address mappings.
#+BEGIN_SRC fundamental
GET http://localhost:8080/peer
Content-Type: application/json
{
"public_key": ""
}
#+END_SRC
#+BEGIN_SRC fundamental
POST http://localhost:8080/peer
Content-Type: application/json
{
"public_key": ""
}
#+END_SRC
#+BEGIN_SRC fundamental
PUT http://localhost:8080/peer
Content-Type: application/json
{
"public_key": ""
}
#+END_SRC
#+BEGIN_SRC fundamental
DELETE http://localhost:8080/peer
Content-Type: application/json
{
"public_key": ""
}
#+END_SRC
#+BEGIN_SRC fundamental
GET http://localhost:8080/peers
Content-Type: application/json
*** Remote Protocol
**** Packet
#+BEGIN_SRC latex :results value raw :exports none
\begin{center}
\begin{bytefield}[bitwidth=0.85em]{33}
\bitheader{0,7,8,15,16,23,24,31,32} \\
\begin{leftwordgroup}{Header}
\bitbox{32}{x25519 ID Hash}
\bitbox{1}{T}
\end{leftwordgroup} \\
\wordbox[lr]{1}{Payload} \\
\wordbox[blr]{1}{$\cdots$}
\end{bytefield}
\end{center}
#+END_SRC
#+RESULTS:
\begin{center}
\begin{bytefield}[bitwidth=0.85em]{33}
\bitheader{0,7,8,15,16,23,24,31,32} \\
\begin{leftwordgroup}{Header}
\bitbox{32}{x25519 ID Hash}
\bitbox{1}{T}
\end{leftwordgroup} \\
\wordbox[lr]{1}{Payload} \\
\wordbox[blr]{1}{$\cdots$}
\end{bytefield}
\end{center}
| Field | In Rust | Definition |
|----------------+----------+------------------------------------|
| x25519 ID Hash | [u8; 32] | Used for identifing the sender |
| Type | u8 | The type of the message being sent |
| Payload | [u8] | The data being sent |
**** Handshake
#+BEGIN_SRC latex :results value raw :exports none
\begin{center}
\begin{bytefield}[bitwidth=0.85em]{33}
\bitheader{0,7,8,15,16,23,24,31,32} \\
\begin{leftwordgroup}{Header}
\bitbox[tlr]{32}{x25519 ID Hash}
\bitbox{1}{T}
\end{leftwordgroup} \\
\bitbox[ltb]{32}{ephemeral blob}
\bitbox[lbr]{1}{R}
\end{bytefield}
\end{center}
#+END_SRC
#+RESULTS:
\begin{center}
\begin{bytefield}[bitwidth=0.85em]{33}
\bitheader{0,7,8,15,16,23,24,31,32} \\
\begin{leftwordgroup}{Header}
\bitbox[tlr]{32}{x25519 ID Hash}
\bitbox{1}{T}
\end{leftwordgroup} \\
\bitbox[ltb]{32}{ephemeral blob}
\bitbox[lbr]{1}{R}
\end{bytefield}
\end{center}
**** Data
#+BEGIN_SRC latex :results value raw :exports none
\begin{center}
\begin{bytefield}[bitwidth=0.85em]{33}
\bitheader{0,7,8,15,16,23,24,31,32} \\
\begin{leftwordgroup}{Header}
\bitbox{32}{x25519 ID Hash}
\bitbox{1}{T}
\end{leftwordgroup} \\
\bitbox{24}{Nonce}
\bitbox[tlr]{9}{} \\
\wordbox[lr]{1}{Encrypted Payload} \\
\wordbox[lbr]{1}{$\cdots$}
\end{bytefield}
\end{center}
#+END_SRC
#+RESULTS:
\begin{center}
\begin{bytefield}[bitwidth=0.85em]{33}
\bitheader{0,7,8,15,16,23,24,31,32} \\
\begin{leftwordgroup}{Header}
\bitbox{32}{x25519 ID Hash}
\bitbox{1}{T}
\end{leftwordgroup} \\
\bitbox{24}{Nonce}
\bitbox[tlr]{9}{} \\
\wordbox[lr]{1}{Encrypted Payload} \\
\wordbox[lbr]{1}{$\cdots$}
\end{bytefield}
\end{center}
*** Operational Description
All packets begin with a /x25519 ID Hash/, which is a sha256 hash of the following data structure.
#+BEGIN_SRC latex :results value raw :exports none
\begin{center}
\begin{bytefield}[bitwidth=0.9em]{32}
\bitheader{0-31} \\
\bitbox{32}{sender's \textit{public key}} \\
\bitbox{32}{\textit{shared secret}}
\end{bytefield}
\end{center}
#+END_SRC
#+RESULTS:
\begin{center}
\begin{bytefield}[bitwidth=0.9em]{32}
\bitheader{0-31} \\
\bitbox{32}{sender's \textit{public key}} \\
\bitbox{32}{\textit{shared secret}}
\end{bytefield}
\end{center}
The receiver of a packet almost always compares the =x25519 ID Hash= with their list, containing the =x25519 ID
Hashes= of peers they want to communicate with if they find a match they should process this packet. If the
receiver is a =server=, they should process packet's even from unknown peers, but only if it is a packet of type
=ExtendedHandshake= or RsaRequest.
**** Handshake
****** Peer to Peer
A peer to peer handshake is only possible between Alice and Bob if they know each other's =x25519 public keys=
and they have agreed on a =shared secret=. The handshake begins when Alice sends a packet of type =Handshake= to
Bob. The =Handshake= packet contains a =ephemeral blob= randomly generated by Alice using a cryptographically
safe random number generator or pseudo-random number generator. When Bob receives this packet, he decides
whether to accept it based on the =x25519 ID Hash= in the header of the packet. If Bob decides not to accept the
packet, he should simply ignore it. However, if Bob accepts the packet, he responds to Alice with another
=Handshake= packet, which again contains a =ephemeral blob=. Then he uses Alice's =x25519 public key= and his
=x25519 private key= to derive a =x25519 shared key= using the x25519 function. *TODO correct term*. Next, he
generates a symmetric key by hashing the following values, concatenated as shown, using sha256.
#+BEGIN_SRC latex :results value raw :exports none
\begin{center}
\begin{bytefield}[bitwidth=0.9em] {32}
\bitheader{0-31} \\
\bitbox{32}{\textit{shared key}} \\
\bitbox{32}{\textit{shared secret}} \\
\bitbox{32}{\textit{ephemeral blob A}} \\
\bitbox{32}{\textit{ephemeral blob B}}
\end{bytefield}
\end{center}
#+END_SRC
#+RESULTS:
\begin{center}
\begin{bytefield}[bitwidth=0.9em] {32}
\bitheader{0-31} \\
\bitbox{32}{\textit{shared key}} \\
\bitbox{32}{\textit{shared secret}} \\
\bitbox{32}{\textit{ephemeral blob A}} \\
\bitbox{32}{\textit{ephemeral blob B}}
\end{bytefield}
\end{center}
#+BEGIN_SRC latex :results value raw :exports none
\begin{footnotesize}
Note: The \textit{ephemeral blob}s are ordered according to the function below; the example is written in rust. \\
\end{footnotesize}
#+END_SRC
#+RESULTS:
\begin{footnotesize}
Note: The \textit{ephemeral blob}s are ordered according to the function below; the example is written in rust. \\
\end{footnotesize}
#+BEGIN_SRC rust
fn order_blobs(ephemeral_blob_a: [u8; 32], ephemeral_blob_b: [u8; 32])
-> ([u8; 32], [u8; 32]) {
for i in 0..32 {
if ephemeral_blob_a[i] > ephemeral_blob_b[i] {
return (ephemeral_blob_a, ephemeral_blob_b);
} else if ephemeral_blob_a[i] < ephemeral_blob_b[i] {
return (ephemeral_blob_b, ephemeral_blob_a);
}
}
}
#+END_SRC
Alice does the same thing, except she uses her =x25519 private key= and Bob's =x25519 public key=. Now both
sides have the same =symmetric key=, which is then used for encrypted communication using the ChaCha20 algorithm.
*** Generic
**** x25519 ID Hash
The =x25519 ID Hash= found the in the header of the incoming packet is

BIN
spec/netstar.pdf

Binary file not shown.

83
src/cmdline_opts.rs

@ -0,0 +1,83 @@
use clap::Clap;
use crypto::{PublicKey, SharedSecret};
#[derive(Clap)]
#[clap(version = "1.0", author = "Magic_RB <magic_rb@redalder.org>")]
pub struct Opts {
#[clap(subcommand)]
pub subcmd: SubCommand,
#[clap(long, default_value = "http://127.0.0.1:8050/")]
pub client_endpoint: String,
#[clap(long, default_value = "./netstar.log")]
pub log_file: String,
}
#[derive(Clap)]
pub enum SubCommand {
Peer(Peer),
Gen(Gen),
Server(Server),
AttemptHandshake(AttemptHandshake),
PublicKey,
}
#[derive(Clap)]
pub enum Gen {
PrivateKey,
SharedSecret,
}
#[derive(Clap)]
pub enum Peer {
Add(PeerAdd),
Delete(PeerDelete),
Get(PeerGet),
Set(PeerSet),
List(PeerList),
}
#[derive(Clap)]
pub struct AttemptHandshake {
#[clap(parse(try_from_str = <PublicKey as TryFrom::<&str>>::try_from))]
pub public_key: PublicKey
}
#[derive(Clap)]
pub struct Server {
#[clap(short = 'c', long = "config", default_value = "./config.toml")]
pub config: String
}
use std::{convert::TryFrom, net::SocketAddr};
#[derive(Clap)]
pub struct PeerAdd {
pub endpoint: SocketAddr,
#[clap(parse(try_from_str = <SharedSecret as TryFrom::<&str>>::try_from))]
pub shared_secret: SharedSecret,
#[clap(parse(try_from_str = <PublicKey as TryFrom::<&str>>::try_from))]
pub public_key: PublicKey,
}
#[derive(Clap)]
pub struct PeerSet {
pub endpoint: SocketAddr,
#[clap(parse(try_from_str = <SharedSecret as TryFrom::<&str>>::try_from))]
pub shared_secret: SharedSecret,
#[clap(parse(try_from_str = <PublicKey as TryFrom::<&str>>::try_from))]
pub public_key: PublicKey,
}
#[derive(Clap)]
pub struct PeerDelete {
#[clap(parse(try_from_str = <PublicKey as TryFrom::<&str>>::try_from))]
pub public_key: PublicKey
}
#[derive(Clap)]
pub struct PeerGet {
#[clap(parse(try_from_str = <PublicKey as TryFrom::<&str>>::try_from))]
pub public_key: PublicKey
}
#[derive(Clap)]
pub struct PeerList {}

40
src/config.rs

@ -1,4 +1,5 @@
use crate::Error;
use crate::error::prelude::*;
use crypto::PrivateKey;
use serde::{Deserialize, Serialize};
use std::{net::SocketAddr, path::Path};
use tokio::{fs::File, io::AsyncReadExt, io::AsyncWriteExt};
@ -11,12 +12,18 @@ fn default_local_address() -> SocketAddr {
"127.0.0.1:6677".parse().expect("Should never fail")
}
fn default_private_key() -> PrivateKey {
PrivateKey::new(&mut rand::thread_rng())
}
#[derive(Serialize, Deserialize)]
pub struct Config {
#[serde(default = "default_connection_address")]
pub connection_address: SocketAddr,
#[serde(default = "default_local_address")]
pub local_address: SocketAddr,
#[serde(default = "default_private_key")]
pub private_key: PrivateKey,
}
impl Default for Config {
@ -24,24 +31,31 @@ impl Default for Config {
Self {
connection_address: default_connection_address(),
local_address: default_local_address(),
private_key: default_private_key(),
}
}
}
impl Config {
pub async fn new_from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
pub fn new(connection_address: SocketAddr, local_address: SocketAddr, private_key: PrivateKey) -> Self { Self { connection_address, local_address, private_key } }
pub async fn new_from_file<P: AsRef<Path>>(path: P) -> EResult<Self> {
let path = path.as_ref();
if let Ok(mut file) = File::open(path).await {
let mut str = String::new();
file.read_to_string(&mut str).await?;
toml::from_str(&str).map_err(|err| err.into())
} else {
let mut file = File::create(path).await?;
let this = Self::default();
let data = toml::to_string(&this)?;
file.write_buf(&mut data.as_bytes()).await?;
Ok(this)
match File::open(path).await {
Ok(mut file) => {
let mut str = String::new();
file.read_to_string(&mut str).await.external(ExtError::CannotReadConfiguration { path: path.to_string_lossy().into() })?;
toml::from_str(&str)
.external(ExtError::CannotReadConfiguration { path: path.to_string_lossy().into() })
}
Err(_) => {
let mut file = File::create(path).await.external(ExtError::CannotReadConfiguration { path: path.to_string_lossy().into() })?;
let this = Self::default();
let data = toml::to_string(&this).external(ExtError::CannotReadConfiguration { path: path.to_string_lossy().into() })?;
file.write_buf(&mut data.as_bytes()).await.external(ExtError::CannotReadConfiguration { path: path.to_string_lossy().into() })?;
EResult::Ok(this)
}
}
}
}

155
src/error.rs

@ -1,55 +1,132 @@
use crypto::{IdHash, PublicKey};
use thiserror::Error;
use actix::MailboxError;
use nom::error::Error as NomError;
use nom::Err as NomErr;
use std::io::Error as IoError;
use toml::de::Error as TomlDeError;
use toml::ser::Error as TomlSerError;
use thiserror::Error as thisError;
use serde::Serialize;
#[derive(Error, Debug)]
pub enum Error {
#[error("an I/O error occured: {0}")]
Io(IoError),
#[error("invalid TOML: {0}")]
TomlDe(TomlDeError),
#[error("TOML serialization failed: {0}")]
TomlSer(TomlSerError),
#[error("Nom deserialization failed: {0}")]
Nom(NomErr<NomError<Vec<u8>>>),
#[error("Actix mailbox error occured: {0}")]
MailboxError(MailboxError),
#[error("Invalid IdHash: {0}")]
UnknownIdHash(IdHash),
#[error("Unknown Peer: {0}")]
UnknownPeer(PublicKey),
#[error("Peer has no endpoint")]
NoEndpoint,
#[error("Peer already exists")]
PeerExists,
use std::backtrace::Backtrace;
use std::error::Error as stdError;
pub mod prelude {
pub use super::{Error, ErrorKind, ErrorRes, ErrorWrap};
}
#[derive(thisError, Debug, Serialize)]
pub enum ErrorKind<S: AsRef<str> + fmt::Debug = String> {
#[error("Cannot read configuration, path: (path.as_ref())")]
ConfigurationRead { path: S },
#[error("Unknow peer: {public_key}")]
UnknownPeer { public_key: PublicKey },
#[error("Unknown id hash: {idhash}")]
UnknowIdHash { idhash: IdHash },
#[error("Peer {public_key} already exists")]
PeerAlreadyExists { public_key: PublicKey },
#[error("Peer {public_key} has no endpoint")]
NoEndpoint { public_key: PublicKey },
#[error("Other error, please look at the `cause`")]
Other
}
impl<S: AsRef<str> + fmt::Debug> ErrorKind<S> {
fn make_concrete(self) -> ErrorKind<String> {
match self {
ErrorKind::ConfigurationRead { path } => ErrorKind::ConfigurationRead {
path: path.as_ref().to_string(),
},
ErrorKind::PeerAlreadyExists { public_key } => ErrorKind::PeerAlreadyExists { public_key },
ErrorKind::UnknownPeer { public_key } => ErrorKind::UnknownPeer { public_key },
ErrorKind::UnknowIdHash { idhash } => ErrorKind::UnknowIdHash { idhash },
ErrorKind::Other => ErrorKind::Other,
ErrorKind::NoEndpoint { public_key } => ErrorKind::NoEndpoint { public_key },
}
}
}
#[derive(thisError, Debug)]
pub struct Error {
context: Option<String>,
backtrace: Backtrace,
cause: Option<Box<dyn stdError + Send>>,
kind: ErrorKind<String>,
}
impl Error {
pub fn new<S: AsRef<str> + fmt::Debug>(kind: ErrorKind<S>) -> Self {
Self {
context: None,
backtrace: Backtrace::capture(),
cause: None,
kind: kind.make_concrete(),
}
}
pub fn with_context<S: AsRef<str>>(mut self, context: S) -> Self {
self.context = Some(context.as_ref().into());
self
}
pub fn with_cause<E: 'static + stdError + Send>(mut self, cause: E) -> Self {
self.cause = Some(Box::new(cause));
self
}
pub fn with_kind<S: AsRef<str> + fmt::Debug>(mut self, kind: ErrorKind<S>) -> Self {
self.kind = kind.make_concrete();
self
}
}
impl From<IoError> for Error {
fn from(error: IoError) -> Self {
Error::Io(error)
use std::fmt;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self) // TODO proper formatting
}
}
impl From<TomlDeError> for Error {
fn from(error: TomlDeError) -> Self {
Error::TomlDe(error)
impl From<std::io::Error> for Error {
fn from(io_error: std::io::Error) -> Self {
Self::new(ErrorKind::Other as ErrorKind).with_cause(io_error)
}
}
impl From<TomlSerError> for Error {
fn from(error: TomlSerError) -> Self {
Error::TomlSer(error)
pub trait ErrorWrap<T, E> {
fn wrap(self) -> Result<T, Error>;
}
pub trait ErrorRes<T> {
fn with_context<S: AsRef<str>>(self, context: S) -> Result<T, Error>;
fn with_kind<S: AsRef<str> + fmt::Debug>(self, kind: ErrorKind<S>) -> Result<T, Error>;
}
impl<T, E: 'static + stdError + Send> ErrorWrap<T, E> for Result<T, E> {
fn wrap(self) -> Result<T, Error> {
match self {
Ok(res) => Ok(res),
Err(err) => Err(Error::new(ErrorKind::Other as ErrorKind).with_cause(err)),
}
}
}
impl<T> ErrorWrap<T, ()> for Option<T> {
fn wrap(self) -> Result<T, Error> {
match self {
Some(res) => Ok(res),
None => Err(Error::new(ErrorKind::Other as ErrorKind))
}
}
}
impl From<MailboxError> for Error {
fn from(error: MailboxError) -> Self {
Error::MailboxError(error)
impl<T> ErrorRes<T> for Result<T, Error> {
fn with_context<S: AsRef<str>>(self, context: S) -> Result<T, Error> {
match self {
Ok(res) => Ok(res),
Err(err) => Err(err.with_context(context)),
}
}
fn with_kind<S: AsRef<str> + fmt::Debug>(self, kind: ErrorKind<S>) -> Result<T, Error> {
match self {
Ok(res) => Ok(res),
Err(err) => Err(err.with_kind(kind)),
}
}
}

7
src/lib.rs

@ -1,8 +1,11 @@
#![feature(backtrace)]
mod config;
pub use config::Config;
mod error;
pub use error::Error;
pub use netstar_error as error;
mod rest;
mod netstar;
pub use crate::netstar::{

152
src/main.rs

@ -1,8 +1,15 @@
#![feature(backtrace)]
use clap::Clap;
mod config;
use cmdline_opts::{AttemptHandshake, Gen, Peer, PeerGet, PeerSet};
pub use config::Config;
mod error;
pub use error::Error;
use netstar_error as error;
mod cmdline_opts;
pub use cmdline_opts::{Opts, SubCommand, PeerAdd, PeerDelete, Server};
mod netstar;
pub use crate::netstar::{
@ -10,11 +17,148 @@ pub use crate::netstar::{
Netstar, Packet, PacketType,
};
mod rest;
use rest::{add_peer, attempt_handshake, delete_peer, get_peer, list_peers, set_peer};
use actix_web::client::Client;
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let netstar = Netstar::new().await;
use std::io::Read;
let opts = cmdline_opts::Opts::parse();
{
use simplelog::{CombinedLogger, Config, TerminalMode, LevelFilter, TermLogger, WriteLogger};
use std::fs::File;
CombinedLogger::init(
vec![
TermLogger::new(
LevelFilter::Warn,
Config::default(),
TerminalMode::Mixed),
WriteLogger::new(
LevelFilter::Info,
Config::default(),
File::create(opts.log_file).unwrap()),
]
).unwrap();
}
match opts.subcmd {
SubCommand::Peer(peer) => {
match peer {
Peer::Add(PeerAdd { endpoint, shared_secret, public_key }) => {
let response = Client::new()
.post(format!("{}peer", opts.client_endpoint))
.send_json(&add_peer::Request {
endpoint: Some(endpoint),
shared_secret,
public_key
}).await;
println!("{:?}", response)
}
Peer::Delete(PeerDelete { public_key }) => {
let response = Client::new()