diff --git a/src/bin/punchnet/api/mod.rs b/src/bin/punchnet/api/mod.rs index 87a1d29..a782461 100644 --- a/src/bin/punchnet/api/mod.rs +++ b/src/bin/punchnet/api/mod.rs @@ -1,5 +1,5 @@ use hmac::{Hmac, Mac as HamcMac}; -use punchnet::{TokenLogin, get_hostname}; +use punchnet::{ExitNodeConfiguration, TokenLogin, get_hostname}; use reqwest::Client; use sdlan_sn_rs::utils::{Mac, Result, SDLanError}; use serde::{Deserialize, Serialize}; @@ -104,8 +104,8 @@ pub struct LoginData { // pub exit_node: Vec, } -#[derive(Debug, Deserialize)] -pub struct ExitNode { +#[derive(Deserialize, Debug)] +pub struct ExitNode{ pub node_id: u32, pub node_name: String, pub gateway: String, diff --git a/src/bin/punchnet/local_udp_info.rs b/src/bin/punchnet/local_udp_info.rs index 3913894..2194f2e 100644 --- a/src/bin/punchnet/local_udp_info.rs +++ b/src/bin/punchnet/local_udp_info.rs @@ -1,8 +1,9 @@ use std::{net::SocketAddr, sync::atomic::Ordering, time::Duration}; +use ipnet::Ipv4Net; use num_enum::TryFromPrimitive; -use punchnet::get_edge; -use sdlan_sn_rs::utils::{Mac, Result, SDLanError}; +use punchnet::{LocalConfigInfo, RouteInfo, get_edge, load_configuration, set_route_from_net, store_configuration}; +use sdlan_sn_rs::{config, utils::{Mac, Result, SDLanError}}; use serde::{Deserialize, Serialize}; use tokio::{net::UdpSocket, time::sleep}; @@ -18,14 +19,39 @@ pub enum InfoFuncCode { Info = 0x00, // query for exit node list, ExitNodeList = 0x01, + // set exit node + ExitNodeSet = 0x02, + ExitNodeStop = 0x03, InfoFeedback = 0x80, ExitNodeListFeedback = 0x81, + ExitNodeSetFeedback = 0x82, + ExitNodeStopFeedback = 0x83, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SingleExitNodeInfo { + pub in_use: bool, + pub id: u32, + pub name: String, + pub gw: String, +} + + +#[derive(Serialize, Deserialize)] +pub struct ExitNodeSetFeedback { + pub ok: bool, + pub message: String, +} + +#[derive(Serialize, Deserialize)] +pub struct ExitNodeSetQuery { + pub id: u32, } #[derive(Serialize, Deserialize)] pub struct ExitNodeListFeedback { - pub nodes: Vec, + pub nodes: Vec, } #[derive(Serialize, Deserialize)] @@ -92,13 +118,54 @@ pub async fn do_query(data_type: u8, data: &[u8] } } +pub async fn query_for_exit_node_stop() { + let result = do_query::(InfoFuncCode::ExitNodeStop as u8, &[], Duration::from_secs(3)).await; + match result { + Ok(data) => { + if data.ok { + println!("set exit-node ok"); + } else { + println!("failed to set exit-node: {}", data.message); + } + } + Err(e) => { + println!("failed to set exit-node: {}", e.as_str()); + } + } + +} + +pub async fn query_for_exit_node_set(nodeid: u32) { + let data = ExitNodeSetQuery { + id: nodeid, + }; + let data = serde_json::to_vec(&data).unwrap(); + let result = do_query::(InfoFuncCode::ExitNodeSet as u8, &data, Duration::from_secs(3)).await; + match result { + Ok(data) => { + if data.ok { + println!("set exit-node ok"); + } else { + println!("failed to set exit-node: {}", data.message); + } + } + Err(e) => { + println!("failed to set exit-node: {}", e.as_str()); + } + } +} + pub async fn query_for_exit_node_list() { let result = do_query::(InfoFuncCode::ExitNodeList as u8, &[], Duration::from_secs(3)).await; match result { Ok(data) => { println!("exit nodes:"); for node in &data.nodes { - println!(" {}", node); + if node.in_use { + println!(" * id: {}, name: {}, gw: {}", node.id, node.name, node.gw); + } else { + println!(" id: {}, name: {}, gw: {}", node.id, node.name, node.gw); + } } } Err(e) => { @@ -154,7 +221,7 @@ pub async fn handle_query_for_info_info() { } async fn handle_query_info(udp: &UdpSocket, buf: &[u8], from: SocketAddr) { - if buf.len() < 1 { + if buf.len() < 3 { return; } let tp = buf[0]; @@ -163,16 +230,139 @@ async fn handle_query_info(udp: &UdpSocket, buf: &[u8], from: SocketAddr) { eprintln!("invalid type: {}", tp); return; }; + let size_field = u16::from_be_bytes([buf[1], buf[2]]); + if size_field as usize + 3 != buf.len() { + // error!("got invalid size"); + eprintln!("got invalid size"); + return; + } match typecode { InfoFuncCode::Info => { send_info_back(udp, from).await; } + InfoFuncCode::ExitNodeList => { + send_exit_node_back(udp, from).await; + } + InfoFuncCode::ExitNodeSet => { + if let Ok(data) = serde_json::from_slice::(&buf[3..]) { + handle_set_exit_node(udp, from, data).await; + } + } + InfoFuncCode::ExitNodeStop => { + println!("got exit node stop"); + handle_stop_exit_node(udp, from).await; + } _other => { } } } +async fn send_exit_node_back(udp: &UdpSocket, from: SocketAddr) { + let config = load_configuration(); + + let mut temp = Vec::new(); + for (id, node) in &config.exit_node { + temp.push(SingleExitNodeInfo { + name: node.node_name.clone(), + gw: node.gateway.clone(), + id: node.node_id, + in_use: node.in_use, + }) + } + let res = ExitNodeListFeedback { + nodes: temp + }; + let value = serde_json::to_string(&res).unwrap(); + + let mut content = Vec::with_capacity(value.len() + 3); + content.push(InfoFuncCode::ExitNodeListFeedback as u8); + + let size = value.len() as u16; + let size_buf = size.to_be_bytes(); + + content.extend_from_slice(&size_buf); + content.extend_from_slice(value.as_bytes()); + udp.send_to(content.as_slice(), from).await; +} + +fn handle_set_exit_node_inner(data: u32) -> std::result::Result{ + let configuration = load_configuration(); + + let Some(data) = configuration.exit_node.get(&data) else { + return Err("no such id found".to_string()); + }; + let Ok(ip) = data.gateway.parse() else { + return Err("no such id found".to_string()); + }; + + let net = "0.0.0.0/0".parse::().unwrap(); + set_route_from_net(vec![RouteInfo{ + net, + gw: ip, + }]); + + return Ok(configuration); +} + +async fn _handle_set_exit_node(udp: &UdpSocket, from: SocketAddr, data:Option, result_code: InfoFuncCode) { + let result = if data.is_none() { + let mut configuration = load_configuration(); + for (node, value) in configuration.exit_node.iter_mut() { + value.in_use = false; + } + store_configuration(&configuration); + set_route_from_net(vec![]); + + ExitNodeSetFeedback { + ok: true, + message: "ok".to_string(), + } + } else { + let data = data.unwrap(); + let result = match handle_set_exit_node_inner(data.id) { + Ok(mut configuration) => { + if let Some(conf) = configuration.exit_node.get_mut(&data.id) { + conf.in_use = true; + } + store_configuration(&configuration); + ExitNodeSetFeedback { + ok: true, + message: "ok".to_string(), + } + } + Err(estr) => { + ExitNodeSetFeedback { + ok: false, + message: estr, + } + } + }; + + result + }; + + let value = serde_json::to_string(&result).unwrap(); + + let mut content = Vec::with_capacity(value.len() + 3); + content.push(result_code as u8); + + let size = value.len() as u16; + let size_buf = size.to_be_bytes(); + + content.extend_from_slice(&size_buf); + content.extend_from_slice(value.as_bytes()); + udp.send_to(content.as_slice(), from).await; +} + +async fn handle_stop_exit_node(udp: &UdpSocket, from: SocketAddr) { + _handle_set_exit_node(udp, from, None, InfoFuncCode::ExitNodeStopFeedback).await; +} + +async fn handle_set_exit_node(udp: &UdpSocket, from: SocketAddr, data:ExitNodeSetQuery) { + _handle_set_exit_node(udp, from, Some(data), InfoFuncCode::ExitNodeSetFeedback).await; +} + async fn send_info_back(udp: &UdpSocket, from: SocketAddr) { let edge = get_edge(); let ip = edge.device_config.get_ip(); diff --git a/src/bin/punchnet/main.rs b/src/bin/punchnet/main.rs index c97052b..aea2bfb 100755 --- a/src/bin/punchnet/main.rs +++ b/src/bin/punchnet/main.rs @@ -1,11 +1,14 @@ mod api; mod local_udp_info; +use std::collections::HashMap; use std::fs; use std::fs::OpenOptions; use std::process; use clap::Parser; +use punchnet::ExitNodeConfiguration; +use punchnet::store_configuration; use std::env; #[cfg(not(target_os = "windows"))] @@ -50,6 +53,8 @@ use crate::api::LoginResponse; use crate::api::TEST_PREFIX; use crate::local_udp_info::handle_query_for_info_info; use crate::local_udp_info::query_for_exit_node_list; +use crate::local_udp_info::query_for_exit_node_set; +use crate::local_udp_info::query_for_exit_node_stop; use crate::local_udp_info::query_for_info; const APP_USER_ENV_NAME: &str = "PUNCH_USER"; @@ -347,8 +352,12 @@ fn main() { ExitNodeCmd::List => { query_for_exit_node_list().await; } - ExitNodeCmd::Start(info) => {} - ExitNodeCmd::Stop => {} + ExitNodeCmd::Start(info) => { + query_for_exit_node_set(info.id).await; + } + ExitNodeCmd::Stop => { + query_for_exit_node_stop().await; + } } }); process::exit(0); @@ -425,7 +434,7 @@ fn main() { run_it(cmd, client_id, allow_routing, mac, system, version); } Err(e) => { - eprintln!("failed to daemonize"); + eprintln!("failed to daemonize: {}", e); } } } else { @@ -436,6 +445,24 @@ fn main() { run_it(cmd, client_id, allow_routing, mac, system, version); } +fn record_exit_node(connect_info: &ConnectData) { + let mut local_configuration = load_configuration(); + let mut temp = HashMap::new(); + for node in &connect_info.exit_node { + temp.insert(node.node_id, ExitNodeConfiguration { + in_use: false, + node_id: node.node_id, + node_name: node.node_name.clone(), + gateway: node.gateway.clone(), + target_network: node.target_network.clone(), + }); + } + local_configuration.exit_node = temp; + if let Err(e) = store_configuration(&local_configuration) { + error!("failed to store configuration"); + } +} + fn run_it( cmd: CommandLineInput2, client_id: String, @@ -456,6 +483,8 @@ fn run_it( let connect_info = parse_connect_result( connect(TEST_PREFIX, &client_id, &remembered.access_token).await, ); + + record_exit_node(&connect_info); daemonize_me( rtinfo.allow_routing || allow_routing, connect_info, @@ -490,6 +519,8 @@ fn run_it( let connect_info = parse_connect_result( connect(TEST_PREFIX, &client_id, &remembered.access_token).await, ); + + record_exit_node(&connect_info); daemonize_me( tk.allow_routing || allow_routing, connect_info, diff --git a/src/network/tun_linux.rs b/src/network/tun_linux.rs index 6e1cbcd..2319feb 100755 --- a/src/network/tun_linux.rs +++ b/src/network/tun_linux.rs @@ -874,7 +874,7 @@ pub fn get_install_channel() -> String { } fn check_has_resolvectl() -> bool { - return false; + // return false; let res = Command::new("resolvectl").arg("status").output(); if let Ok(_) = res { true @@ -912,7 +912,8 @@ fn add_resolvectl(name: &str, network_domain: &str) -> Result<()> { if !Command::new("resolvectl") .arg("domain") .arg(name) - .arg(format!("~{}", network_domain)) + // .arg(format!("~{}", network_domain)) + .arg("~.") .output()?.status.success() { error!("failed to run resolvectl domain"); return Err(SDLanError::IOError("failed to resolvectl domain".to_owned())) diff --git a/src/utils/command.rs b/src/utils/command.rs index 3e96349..051a512 100755 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -49,8 +49,8 @@ pub enum ExitNodeCmd { #[derive(Args, Debug)] pub struct ExitNodeInfo { - #[arg(short, long, default_value="")] - pub name: String, + #[arg(short, long)] + pub id: u32, } #[derive(Args, Debug)] diff --git a/src/utils/file_configuration.rs b/src/utils/file_configuration.rs index ede0b8e..2455d83 100644 --- a/src/utils/file_configuration.rs +++ b/src/utils/file_configuration.rs @@ -1,15 +1,24 @@ -use std::path::Path; +use std::{collections::HashMap, path::Path, sync::atomic::AtomicBool}; use sdlan_sn_rs::utils::{Mac, Result}; use serde::{Deserialize, Serialize}; -use crate::{generate_mac_address, get_base_dir}; +use crate::{ExitNodeInfo, generate_mac_address, get_base_dir}; +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ExitNodeConfiguration { + pub in_use: bool, + pub node_id: u32, + pub node_name: String, + pub gateway: String, + pub target_network: String, +} #[derive(Serialize, Deserialize, Debug)] pub struct LocalConfigInfo { pub allow_routing: Option, - pub mac: Option<[u8; 6]> + pub mac: Option<[u8; 6]>, + pub exit_node: HashMap, } pub fn store_configuration(config: &LocalConfigInfo) -> Result<()> { @@ -52,6 +61,7 @@ pub fn load_configuration() -> LocalConfigInfo { let config = LocalConfigInfo { allow_routing: Some(false), mac, + exit_node: HashMap::new(), }; let _ = store_configuration(&config); config