added exit-node list, exit-node start and exit-node stop

This commit is contained in:
alex 2026-04-25 00:45:39 +08:00
parent 0c045dd828
commit 9d0a5289ea
6 changed files with 250 additions and 18 deletions

View File

@ -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<ExitNode>,
}
#[derive(Debug, Deserialize)]
pub struct ExitNode {
#[derive(Deserialize, Debug)]
pub struct ExitNode{
pub node_id: u32,
pub node_name: String,
pub gateway: String,

View File

@ -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<String>,
pub nodes: Vec<SingleExitNodeInfo>,
}
#[derive(Serialize, Deserialize)]
@ -92,13 +118,54 @@ pub async fn do_query<T: serde::de::DeserializeOwned>(data_type: u8, data: &[u8]
}
}
pub async fn query_for_exit_node_stop() {
let result = do_query::<ExitNodeSetFeedback>(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::<ExitNodeSetFeedback>(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::<ExitNodeListFeedback>(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::<ExitNodeSetQuery>(&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<LocalConfigInfo, String>{
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::<Ipv4Net>().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<ExitNodeSetQuery>, 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();

View File

@ -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,

View File

@ -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()))

View File

@ -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)]

View File

@ -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<bool>,
pub mac: Option<[u8; 6]>
pub mac: Option<[u8; 6]>,
pub exit_node: HashMap<u32, ExitNodeConfiguration>,
}
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