Generate NetworkSettings with IPC
This generates and applies NetworkSettings object with unix socket IPC. - domain socket, json-rpc based communication - switches to anyhow for burrow crate - adds support for starting daemons on macos
This commit is contained in:
parent
6368ca7f74
commit
c9f104e523
31 changed files with 909 additions and 117 deletions
|
|
@ -10,18 +10,20 @@ crate-type = ["lib", "staticlib"]
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util"] }
|
||||
tun = { version = "0.1", path = "../tun", features = ["serde"] }
|
||||
tokio = { version = "1.21", features = ["rt", "macros", "sync", "io-util", "rt-multi-thread"] }
|
||||
tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] }
|
||||
clap = { version = "4.3.2", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
tracing-log = "0.1"
|
||||
tracing-journald = "0.3"
|
||||
tracing-oslog = {git = "https://github.com/Stormshield-robinc/tracing-oslog"}
|
||||
tracing-subscriber = "0.3"
|
||||
tracing-subscriber = { version = "0.3" , features = ["std", "env-filter"]}
|
||||
env_logger = "0.10"
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
async-channel = "1.9"
|
||||
schemars = "0.8"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
caps = "0.5.5"
|
||||
|
|
@ -30,6 +32,9 @@ libsystemd = "0.6"
|
|||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||
nix = { version = "0.26.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { version = "1.32.0", features = ["yaml"] }
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [
|
||||
{ source = "target/release/burrow", dest = "/usr/bin/burrow", mode = "755" },
|
||||
|
|
|
|||
15
burrow/src/apple.rs
Normal file
15
burrow/src/apple.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use tracing::{debug, Subscriber};
|
||||
use tracing::instrument::WithSubscriber;
|
||||
use tracing_oslog::OsLogger;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
pub use crate::daemon::start_srv;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn initialize_oslog() {
|
||||
let collector = tracing_subscriber::registry()
|
||||
.with(OsLogger::new("com.hackclub.burrow", "backend"));
|
||||
tracing::subscriber::set_global_default(collector).unwrap();
|
||||
debug!("Initialized oslog tracing in libburrow rust FFI");
|
||||
}
|
||||
|
|
@ -1,13 +1,32 @@
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tun::TunOptions;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum DaemonCommand {
|
||||
Start(DaemonStartOptions),
|
||||
ServerInfo,
|
||||
ServerConfig,
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DaemonStartOptions {
|
||||
pub(super) tun: TunOptions,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_daemoncommand_serialization() {
|
||||
insta::assert_snapshot!(
|
||||
serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
serde_json::to_string(&DaemonCommand::Stop).unwrap()
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()
|
||||
)
|
||||
}
|
||||
|
|
@ -1,40 +1,70 @@
|
|||
use tracing::{debug, info, warn};
|
||||
use DaemonResponse;
|
||||
use crate::daemon::response::{DaemonResponseData, ServerConfig, ServerInfo};
|
||||
use super::*;
|
||||
|
||||
pub struct DaemonInstance {
|
||||
rx: mpsc::Receiver<DaemonCommand>,
|
||||
rx: async_channel::Receiver<DaemonCommand>,
|
||||
sx: async_channel::Sender<DaemonResponse>,
|
||||
tun_interface: Option<TunInterface>,
|
||||
}
|
||||
|
||||
impl DaemonInstance {
|
||||
pub fn new(rx: mpsc::Receiver<DaemonCommand>) -> Self {
|
||||
pub fn new(rx: async_channel::Receiver<DaemonCommand>, sx: async_channel::Sender<DaemonResponse>) -> Self {
|
||||
Self {
|
||||
rx,
|
||||
sx,
|
||||
tun_interface: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<()> {
|
||||
while let Some(command) = self.rx.recv().await {
|
||||
match command {
|
||||
DaemonCommand::Start(options) => {
|
||||
if self.tun_interface.is_none() {
|
||||
self.tun_interface = Some(options.tun.open()?);
|
||||
eprintln!("Daemon starting tun interface.");
|
||||
} else {
|
||||
eprintln!("Got start, but tun interface already up.");
|
||||
}
|
||||
async fn proc_command(&mut self, command: DaemonCommand) -> Result<DaemonResponseData> {
|
||||
info!("Daemon got command: {:?}", command);
|
||||
match command {
|
||||
DaemonCommand::Start(st) => {
|
||||
if self.tun_interface.is_none() {
|
||||
debug!("Daemon attempting start tun interface.");
|
||||
self.tun_interface = Some(st.tun.open()?);
|
||||
info!("Daemon started tun interface");
|
||||
} else {
|
||||
warn!("Got start, but tun interface already up.");
|
||||
}
|
||||
DaemonCommand::Stop => {
|
||||
if self.tun_interface.is_some() {
|
||||
self.tun_interface = None;
|
||||
eprintln!("Daemon stopping tun interface.");
|
||||
} else {
|
||||
eprintln!("Got stop, but tun interface is not up.")
|
||||
Ok(DaemonResponseData::None)
|
||||
}
|
||||
DaemonCommand::ServerInfo => {
|
||||
match &self.tun_interface {
|
||||
None => {Ok(DaemonResponseData::None)}
|
||||
Some(ti) => {
|
||||
info!("{:?}", ti);
|
||||
Ok(
|
||||
DaemonResponseData::ServerInfo(
|
||||
ServerInfo::try_from(ti)?
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DaemonCommand::Stop => {
|
||||
if self.tun_interface.is_some() {
|
||||
self.tun_interface = None;
|
||||
info!("Daemon stopping tun interface.");
|
||||
} else {
|
||||
warn!("Got stop, but tun interface is not up.")
|
||||
}
|
||||
Ok(DaemonResponseData::None)
|
||||
}
|
||||
DaemonCommand::ServerConfig => {
|
||||
Ok(DaemonResponseData::ServerConfig(ServerConfig::default()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<()> {
|
||||
while let Ok(command) = self.rx.recv().await {
|
||||
let response = self.proc_command(command).await;
|
||||
info!("Daemon response: {:?}", response);
|
||||
self.sx.send(DaemonResponse::new(response)).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use tokio::sync::mpsc;
|
|||
mod command;
|
||||
mod instance;
|
||||
mod net;
|
||||
mod response;
|
||||
|
||||
use instance::DaemonInstance;
|
||||
use net::listen;
|
||||
|
|
@ -11,9 +12,15 @@ use net::listen;
|
|||
pub use command::{DaemonCommand, DaemonStartOptions};
|
||||
pub use net::DaemonClient;
|
||||
|
||||
pub async fn daemon_main() -> Result<()> {
|
||||
let (tx, rx) = mpsc::channel(2);
|
||||
let mut inst = DaemonInstance::new(rx);
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub use net::start_srv;
|
||||
|
||||
tokio::try_join!(inst.run(), listen(tx)).map(|_| ())
|
||||
pub use response::{DaemonResponseData, DaemonResponse, ServerInfo};
|
||||
|
||||
pub async fn daemon_main() -> Result<()> {
|
||||
let (commands_tx, commands_rx) = async_channel::unbounded();
|
||||
let (response_tx, response_rx) = async_channel::unbounded();
|
||||
let mut inst = DaemonInstance::new(commands_rx, response_tx);
|
||||
|
||||
tokio::try_join!(inst.run(), listen(commands_tx, response_rx)).map(|_| ())
|
||||
}
|
||||
|
|
|
|||
24
burrow/src/daemon/net/apple.rs
Normal file
24
burrow/src/daemon/net/apple.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use std::thread;
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing::error;
|
||||
use crate::daemon::{daemon_main, DaemonClient};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn start_srv(){
|
||||
let _handle = thread::spawn(move || {
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
if let Err(e) = daemon_main().await {
|
||||
error!("Error when starting rpc server: {}", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
loop {
|
||||
if let Ok(_) = DaemonClient::new().await{
|
||||
break
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -13,17 +13,19 @@ pub use systemd::{listen, DaemonClient};
|
|||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::{listen, DaemonClient};
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
mod apple;
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub use apple::start_srv;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct DaemonRequest {
|
||||
pub id: u32,
|
||||
pub command: DaemonCommand,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct DaemonResponse {
|
||||
// Error types can't be serialized, so this is the second best option.
|
||||
result: std::result::Result<(), String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
use super::*;
|
||||
use std::os::fd::IntoRawFd;
|
||||
|
||||
pub async fn listen(cmd_tx: mpsc::Sender<DaemonCommand>) -> Result<()> {
|
||||
if !libsystemd::daemon::booted() || listen_with_systemd(cmd_tx.clone()).await.is_err() {
|
||||
unix::listen(cmd_tx).await?;
|
||||
pub async fn listen(cmd_tx: async_channel::Sender<DaemonCommand>, rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
|
||||
if !libsystemd::daemon::booted() || listen_with_systemd(cmd_tx.clone(), rsp_rx.clone()).await.is_err() {
|
||||
unix::listen(cmd_tx, rsp_rx).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn listen_with_systemd(cmd_tx: mpsc::Sender<DaemonCommand>) -> Result<()> {
|
||||
let fds = libsystemd::activation::receive_descriptors(false).unwrap();
|
||||
super::unix::listen_with_optional_fd(cmd_tx, Some(fds[0].clone().into_raw_fd())).await
|
||||
async fn listen_with_systemd(cmd_tx: async_channel::Sender<DaemonCommand>, rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
|
||||
let fds = libsystemd::activation::receive_descriptors(false)?;
|
||||
super::unix::listen_with_optional_fd(cmd_tx, rsp_rx,Some(fds[0].clone().into_raw_fd())).await
|
||||
}
|
||||
|
||||
pub type DaemonClient = unix::DaemonClient;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,51 @@
|
|||
use super::*;
|
||||
use std::{
|
||||
os::fd::{FromRawFd, RawFd},
|
||||
os::unix::net::UnixListener as StdUnixListener,
|
||||
path::Path,
|
||||
};
|
||||
use std::{ascii, io, os::fd::{FromRawFd, RawFd}, os::unix::net::UnixListener as StdUnixListener, path::Path};
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
use anyhow::anyhow;
|
||||
use log::log;
|
||||
use tracing::info;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
net::{UnixListener, UnixStream},
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(not(target_vendor = "apple"))]
|
||||
const UNIX_SOCKET_PATH: &str = "/run/burrow.sock";
|
||||
|
||||
pub async fn listen(cmd_tx: mpsc::Sender<DaemonCommand>) -> Result<()> {
|
||||
listen_with_optional_fd(cmd_tx, None).await
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const UNIX_SOCKET_PATH: &str = "burrow.sock";
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn fetch_socket_path() -> Option<PathBuf>{
|
||||
let tries = vec![
|
||||
"burrow.sock".to_string(),
|
||||
format!("{}/Library/Containers/com.hackclub.burrow.network/Data/burrow.sock",
|
||||
std::env::var("HOME").unwrap_or_default())
|
||||
.to_string(),
|
||||
];
|
||||
for path in tries{
|
||||
let path = PathBuf::from(path);
|
||||
if path.exists(){
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn fetch_socket_path() -> Option<PathBuf>{
|
||||
Some(Path::new(UNIX_SOCKET_PATH).to_path_buf())
|
||||
}
|
||||
|
||||
pub async fn listen(cmd_tx: async_channel::Sender<DaemonCommand>, rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
|
||||
listen_with_optional_fd(cmd_tx, rsp_rx, None).await
|
||||
}
|
||||
|
||||
pub(crate) async fn listen_with_optional_fd(
|
||||
cmd_tx: mpsc::Sender<DaemonCommand>,
|
||||
cmd_tx: async_channel::Sender<DaemonCommand>,
|
||||
rsp_rx: async_channel::Receiver<DaemonResponse>,
|
||||
raw_fd: Option<RawFd>,
|
||||
) -> Result<()> {
|
||||
let path = Path::new(UNIX_SOCKET_PATH);
|
||||
|
|
@ -32,7 +61,16 @@ pub(crate) async fn listen_with_optional_fd(
|
|||
listener
|
||||
} else {
|
||||
// Won't help all that much, if we use the async version of fs.
|
||||
std::fs::remove_file(path)?;
|
||||
if let Some(par) = path.parent(){
|
||||
std::fs::create_dir_all(
|
||||
par
|
||||
)?;
|
||||
}
|
||||
match std::fs::remove_file(path){
|
||||
Err(e) if e.kind()==io::ErrorKind::NotFound => {Ok(())}
|
||||
stuff => stuff
|
||||
}?;
|
||||
info!("Relative path: {}", path.to_string_lossy());
|
||||
UnixListener::bind(path)?
|
||||
};
|
||||
loop {
|
||||
|
|
@ -41,29 +79,35 @@ pub(crate) async fn listen_with_optional_fd(
|
|||
|
||||
// I'm pretty sure we won't need to manually join / shut this down,
|
||||
// `lines` will return Err during dropping, and this task should exit gracefully.
|
||||
tokio::task::spawn(async {
|
||||
let rsp_rxc = rsp_rx.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let cmd_tx = cmd_tx;
|
||||
let mut stream = stream;
|
||||
let (mut read_stream, mut write_stream) = stream.split();
|
||||
let buf_reader = BufReader::new(&mut read_stream);
|
||||
let mut lines = buf_reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
let mut res = DaemonResponse { result: Ok(()) };
|
||||
let command = match serde_json::from_str::<DaemonRequest>(&line) {
|
||||
Ok(req) => Some(req.command),
|
||||
info!("Got line: {}", line);
|
||||
debug!("Line raw data: {:?}", line.as_bytes());
|
||||
let mut res : DaemonResponse = DaemonResponseData::None.into();
|
||||
let req = match serde_json::from_str::<DaemonRequest>(&line) {
|
||||
Ok(req) => Some(req),
|
||||
Err(e) => {
|
||||
res.result = Err(format!("{}", e));
|
||||
res.result = Err(e.to_string());
|
||||
None
|
||||
}
|
||||
};
|
||||
let mut res = serde_json::to_string(&res).unwrap();
|
||||
res.push('\n');
|
||||
|
||||
write_stream.write_all(res.as_bytes()).await.unwrap();
|
||||
|
||||
// I want this to come at the very end so that we always send a reponse back.
|
||||
if let Some(command) = command {
|
||||
cmd_tx.send(command).await.unwrap();
|
||||
if let Some(req) = req {
|
||||
cmd_tx.send(req.command).await.unwrap();
|
||||
let res = rsp_rxc.recv().await.unwrap().with_id(req.id);
|
||||
let mut retres = serde_json::to_string(&res).unwrap();
|
||||
retres.push('\n');
|
||||
info!("Sending response: {}", retres);
|
||||
write_stream.write_all(retres.as_bytes()).await.unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -76,7 +120,12 @@ pub struct DaemonClient {
|
|||
|
||||
impl DaemonClient {
|
||||
pub async fn new() -> Result<Self> {
|
||||
Self::new_with_path(UNIX_SOCKET_PATH).await
|
||||
let path = fetch_socket_path()
|
||||
.ok_or(anyhow!("Failed to find socket path"))?;
|
||||
// debug!("found path: {:?}", path);
|
||||
let connection = UnixStream::connect(path).await?;
|
||||
debug!("connected to socket");
|
||||
Ok(Self { connection })
|
||||
}
|
||||
|
||||
pub async fn new_with_path(path: &str) -> Result<Self> {
|
||||
|
|
@ -86,17 +135,19 @@ impl DaemonClient {
|
|||
Ok(Self { connection })
|
||||
}
|
||||
|
||||
pub async fn send_command(&mut self, command: DaemonCommand) -> Result<()> {
|
||||
pub async fn send_command(&mut self, command: DaemonCommand) -> Result<DaemonResponse> {
|
||||
let mut command = serde_json::to_string(&DaemonRequest { id: 0, command })?;
|
||||
command.push('\n');
|
||||
|
||||
self.connection.write_all(command.as_bytes()).await?;
|
||||
let buf_reader = BufReader::new(&mut self.connection);
|
||||
let mut lines = buf_reader.lines();
|
||||
// This unwrap *should* never cause issues.
|
||||
let response = lines.next_line().await?.unwrap();
|
||||
let response = lines
|
||||
.next_line()
|
||||
.await?
|
||||
.ok_or(anyhow!("Failed to read response"))?;
|
||||
debug!("Got raw response: {}", response);
|
||||
let res: DaemonResponse = serde_json::from_str(&response)?;
|
||||
res.result.unwrap();
|
||||
Ok(())
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::*;
|
||||
|
||||
pub async fn listen(_: mpsc::Sender<DaemonCommand>) -> Result<()> {
|
||||
pub async fn listen(_cmd_tx: async_channel::Sender<DaemonCommand>, _rsp_rx: async_channel::Receiver<DaemonResponse>) -> Result<()> {
|
||||
unimplemented!("This platform does not currently support daemon mode.")
|
||||
}
|
||||
|
||||
|
|
|
|||
109
burrow/src/daemon/response.rs
Normal file
109
burrow/src/daemon/response.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use anyhow::anyhow;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tun::TunInterface;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
|
||||
pub struct DaemonResponse {
|
||||
// Error types can't be serialized, so this is the second best option.
|
||||
pub result: Result<DaemonResponseData, String>,
|
||||
pub id: u32
|
||||
}
|
||||
|
||||
impl DaemonResponse{
|
||||
pub fn new(result: Result<DaemonResponseData, impl ToString>) -> Self{
|
||||
Self{
|
||||
result: result.map_err(|e| e.to_string()),
|
||||
id: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<DaemonResponse> for DaemonResponseData{
|
||||
fn into(self) -> DaemonResponse{
|
||||
DaemonResponse::new(Ok::<DaemonResponseData, String>(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl DaemonResponse{
|
||||
pub fn with_id(self, id: u32) -> Self{
|
||||
Self {
|
||||
id,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ServerInfo {
|
||||
pub name: Option<String>,
|
||||
pub ip: Option<String>,
|
||||
pub mtu: Option<i32>
|
||||
}
|
||||
|
||||
impl TryFrom<&TunInterface> for ServerInfo{
|
||||
type Error = anyhow::Error;
|
||||
|
||||
#[cfg(any(target_os="linux",target_vendor="apple"))]
|
||||
fn try_from(server: &TunInterface) -> anyhow::Result<Self> {
|
||||
Ok(
|
||||
ServerInfo{
|
||||
name: server.name().ok(),
|
||||
ip: server.ipv4_addr().ok().map(|ip| ip.to_string()),
|
||||
mtu: server.mtu().ok()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os="linux",target_vendor="apple")))]
|
||||
fn try_from(server: &TunInterface) -> anyhow::Result<Self> {
|
||||
Err(anyhow!("Not implemented in this platform"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ServerConfig {
|
||||
pub address: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub mtu: Option<i32>
|
||||
}
|
||||
|
||||
impl Default for ServerConfig {
|
||||
fn default() -> Self {
|
||||
Self{
|
||||
address: Some("10.0.0.1".to_string()), // Dummy remote address
|
||||
name: None,
|
||||
mtu: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum DaemonResponseData{
|
||||
ServerInfo(ServerInfo),
|
||||
ServerConfig(ServerConfig),
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_serialization() -> anyhow::Result<()>{
|
||||
insta::assert_snapshot!(
|
||||
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::None)))?
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::ServerInfo(ServerInfo{
|
||||
name: Some("burrow".to_string()),
|
||||
ip: None,
|
||||
mtu: Some(1500)
|
||||
}))))?
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
serde_json::to_string(&DaemonResponse::new(Err::<DaemonResponseData, String>("error".to_string())))?
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData, String>(DaemonResponseData::ServerConfig(
|
||||
ServerConfig::default()
|
||||
))))?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/command.rs
|
||||
expression: "serde_json::to_string(&DaemonCommand::ServerInfo).unwrap()"
|
||||
---
|
||||
"ServerInfo"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/command.rs
|
||||
expression: "serde_json::to_string(&DaemonCommand::Stop).unwrap()"
|
||||
---
|
||||
"Stop"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/command.rs
|
||||
expression: "serde_json::to_string(&DaemonCommand::ServerConfig).unwrap()"
|
||||
---
|
||||
"ServerConfig"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/command.rs
|
||||
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()"
|
||||
---
|
||||
{"Start":{"tun":{"name":null,"no_pi":null,"tun_excl":null}}}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/response.rs
|
||||
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::ServerInfo(ServerInfo {\n name: Some(\"burrow\".to_string()),\n ip: None,\n mtu: Some(1500),\n }))))?"
|
||||
---
|
||||
{"result":{"Ok":{"ServerInfo":{"name":"burrow","ip":null,"mtu":1500}}},"id":0}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/response.rs
|
||||
expression: "serde_json::to_string(&DaemonResponse::new(Err::<DaemonResponseData,\n String>(\"error\".to_string())))?"
|
||||
---
|
||||
{"result":{"Err":"error"},"id":0}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/response.rs
|
||||
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::ServerConfig(ServerConfig::default()))))?"
|
||||
---
|
||||
{"result":{"Ok":{"ServerConfig":{"address":"10.0.0.1","name":null,"mtu":null}}},"id":0}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: burrow/src/daemon/response.rs
|
||||
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::None)))?"
|
||||
---
|
||||
{"result":{"Ok":"None"},"id":0}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#![deny(missing_debug_implementations)]
|
||||
pub mod ensureroot;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
use std::{
|
||||
mem,
|
||||
|
|
@ -11,6 +13,15 @@ use tun::TunInterface;
|
|||
|
||||
// TODO Separate start and retrieve functions
|
||||
|
||||
mod daemon;
|
||||
pub use daemon::{DaemonCommand, DaemonResponseData, DaemonStartOptions, DaemonResponse, ServerInfo};
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
mod apple;
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub use apple::*;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn retrieve() -> i32 {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ use std::mem;
|
|||
use std::os::fd::FromRawFd;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use tracing::instrument;
|
||||
use tracing::{instrument, Level};
|
||||
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_oslog::OsLogger;
|
||||
use tracing_subscriber::{prelude::*, FmtSubscriber};
|
||||
use tokio::io::Result;
|
||||
use tracing_subscriber::{prelude::*, FmtSubscriber, EnvFilter};
|
||||
use anyhow::Result;
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
use burrow::retrieve;
|
||||
use tun::TunInterface;
|
||||
|
|
@ -17,6 +17,7 @@ use tun::TunInterface;
|
|||
mod daemon;
|
||||
|
||||
use daemon::{DaemonClient, DaemonCommand, DaemonStartOptions};
|
||||
use crate::daemon::DaemonResponseData;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "Burrow")]
|
||||
|
|
@ -44,6 +45,10 @@ enum Commands {
|
|||
Stop,
|
||||
/// Start Burrow daemon
|
||||
Daemon(DaemonArgs),
|
||||
/// Server Info
|
||||
ServerInfo,
|
||||
/// Server config
|
||||
ServerConfig,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -61,27 +66,38 @@ async fn try_start() -> Result<()> {
|
|||
client
|
||||
.send_command(DaemonCommand::Start(DaemonStartOptions::default()))
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
#[instrument]
|
||||
async fn try_retrieve() -> Result<()> {
|
||||
LogTracer::init().context("Failed to initialize LogTracer").unwrap();
|
||||
|
||||
if cfg!(target_os = "linux") || cfg!(target_vendor = "apple") {
|
||||
let maybe_layer = system_log().unwrap();
|
||||
if let Some(layer) = maybe_layer {
|
||||
let logger = layer.with_subscriber(FmtSubscriber::new());
|
||||
tracing::subscriber::set_global_default(logger).context("Failed to set the global tracing subscriber").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
burrow::ensureroot::ensure_root();
|
||||
let iface2 = retrieve();
|
||||
tracing::info!("{}", iface2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn initialize_tracing() -> Result<()> {
|
||||
LogTracer::init().context("Failed to initialize LogTracer")?;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
{
|
||||
let maybe_layer = system_log()?;
|
||||
if let Some(layer) = maybe_layer {
|
||||
let logger = layer.with_subscriber(
|
||||
FmtSubscriber::builder()
|
||||
.with_line_number(true)
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.finish()
|
||||
);
|
||||
tracing::subscriber::set_global_default(logger).context("Failed to set the global tracing subscriber")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
async fn try_stop() -> Result<()> {
|
||||
let mut client = DaemonClient::new().await?;
|
||||
|
|
@ -89,6 +105,44 @@ async fn try_stop() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
async fn try_serverinfo() -> Result<()>{
|
||||
let mut client = DaemonClient::new().await?;
|
||||
let res = client.send_command(DaemonCommand::ServerInfo).await?;
|
||||
match res.result {
|
||||
Ok(DaemonResponseData::ServerInfo(si)) => {
|
||||
println!("Got Result! {:?}", si);
|
||||
}
|
||||
Ok(DaemonResponseData::None) => {
|
||||
println!("Server not started.")
|
||||
}
|
||||
Ok(res) => {println!("Unexpected Response: {:?}", res)}
|
||||
Err(e) => {
|
||||
println!("Error when retrieving from server: {}", e)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
async fn try_serverconfig() -> Result<()>{
|
||||
let mut client = DaemonClient::new().await?;
|
||||
let res = client.send_command(DaemonCommand::ServerConfig).await?;
|
||||
match res.result {
|
||||
Ok(DaemonResponseData::ServerConfig(cfig)) => {
|
||||
println!("Got Result! {:?}", cfig);
|
||||
}
|
||||
Ok(DaemonResponseData::None) => {
|
||||
println!("Server not started.")
|
||||
}
|
||||
Ok(res) => {println!("Unexpected Response: {:?}", res)}
|
||||
Err(e) => {
|
||||
println!("Error when retrieving from server: {}", e)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
|
||||
async fn try_start() -> Result<()> {
|
||||
Ok(())
|
||||
|
|
@ -104,24 +158,40 @@ async fn try_stop() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
|
||||
async fn try_serverinfo() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_vendor = "apple")))]
|
||||
async fn try_serverconfig() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
initialize_tracing().await?;
|
||||
tracing::info!("Platform: {}", std::env::consts::OS);
|
||||
|
||||
let cli = Cli::parse();
|
||||
match &cli.command {
|
||||
Commands::Start(..) => {
|
||||
try_start().await.unwrap();
|
||||
try_start().await?;
|
||||
tracing::info!("FINISHED");
|
||||
}
|
||||
Commands::Retrieve(..) => {
|
||||
try_retrieve().await.unwrap();
|
||||
try_retrieve().await?;
|
||||
tracing::info!("FINISHED");
|
||||
}
|
||||
Commands::Stop => {
|
||||
try_stop().await.unwrap();
|
||||
try_stop().await?;
|
||||
}
|
||||
Commands::Daemon(_) => daemon::daemon_main().await?,
|
||||
Commands::ServerInfo => {
|
||||
try_serverinfo().await?
|
||||
}
|
||||
Commands::ServerConfig => {
|
||||
try_serverconfig().await?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -141,5 +211,5 @@ fn system_log() -> anyhow::Result<Option<tracing_journald::Layer>> {
|
|||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
fn system_log() -> anyhow::Result<Option<OsLogger>> {
|
||||
Ok(Some(OsLogger::new("com.hackclub.burrow", "default")))
|
||||
Ok(Some(OsLogger::new("com.hackclub.burrow", "burrow-cli")))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue