Я работаю над модификацией кода, написанного другой стороной. Цель состоит в том, чтобы эмулировать шлюз безопасности UniFi для получения отчетов в программном обеспечении UniFi Controller. Я планирую запустить эмуляцию на Zota c Mini P C с двумя сетевыми картами под управлением CentOS 8. Чтобы мне не пришлось беспокоиться о дополнительном NAT, у меня есть две сетевые карты, настроенные как мост.
Структура для каждого сетевого устройства определяется как:
#[derive(PartialEq, Clone, Debug)]
pub(crate) struct UnixNetworkDevice {
name: String,
mac: MacAddr,
interface: NetworkInterface,
statistics: UnixNetworkDeviceStatistics,
}
Когда структура заполнена для устройства eth0, вы получаете это (wan_device
в начале - это просто мой тег, поэтому я знаете, на что я смотрю):
wan_device Some(UnixNetworkDevice {
name: "eth0",
mac: 00:01:2e:80:3e:1d,
interface: NetworkInterface {
name: "eth0",
index: 2,
mac: Some(00:01:2e:80:3e:1d),
ips: [],
flags: 69699
},
statistics: UnixNetworkDeviceStatistics {
collisions: 0,
multicast: 497,
rx_bytes: 359185,
rx_compressed: 0,
rx_crc_errors: 0,
rx_dropped: 26,
rx_errors: 0,
rx_fifo_errors: 0,
rx_frame_errors: 0,
rx_length_errors: 0,
rx_missed_errors: 0,
rx_nohandler: 0,
rx_over_errors: 0,
rx_packets: 1587,
tx_aborted_errors: 0,
tx_bytes: 74695,
tx_carrier_errors: 0,
tx_compressed: 0,
tx_dropped: 0,
tx_errors: 0,
tx_fifo_errors: 0,
tx_heartbeat_errors: 0,
tx_packets: 474,
tx_window_errors: 0
}
})
Я также получаю ту же информацию с устройства Bridge:
bri_device Some(UnixNetworkDevice {
name: "bri0",
mac: 00:01:2e:80:3e:1d,
interface: NetworkInterface {
name: "bri0",
index: 5,
mac: Some(00:01:2e:80:3e:1d),
ips: [V4(Ipv4Network { addr: 192.168.113.2, prefix: 24 }),
V6(Ipv6Network { addr: fe80::c8ee:a0ff:fe3a:3096, prefix: 64 })],
flags: 69699
},
statistics: UnixNetworkDeviceStatistics {
collisions: 0,
multicast: 0,
rx_bytes: 275467,
rx_compressed: 0,
rx_crc_errors: 0,
rx_dropped: 0,
rx_errors: 0,
rx_fifo_errors: 0,
rx_frame_errors: 0,
rx_length_errors: 0,
rx_missed_errors: 0,
rx_nohandler: 0,
rx_over_errors: 0,
rx_packets: 1371,
tx_aborted_errors: 0,
tx_bytes: 68355,
tx_carrier_errors: 0,
tx_compressed: 0,
tx_dropped: 0,
tx_errors: 0,
tx_fifo_errors: 0,
tx_heartbeat_errors: 0,
tx_packets: 345,
tx_window_errors: 0
}
})
wan_device
не показывает IP адреса, поскольку он назначен мосту.
Прямо сейчас, когда он пытается выполнить clone()
из wan_device
, он завершается ошибкой из-за пустого раздела ips
в структуре. Я хотел бы скопировать часть структуры IPS из bri_device
в wan_device
. Я надеюсь, что это возможно.
Вот MRE, которое можно превратить в проект, включая файл Car go .toml после него.
Это ссылка на ZIP-файл. того же: https://www.dropbox.com/s/7pfxbmv49ra1jxs/Test-OpnFi.zip?dl=0
#[macro_use]
extern crate log;
extern crate lazy_static;
extern crate clap;
extern crate regex;
extern crate simple_logger;
/// Network interface for inform
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)]
pub struct OpnFiInformNetworkInterface {
pub drops: usize,
pub enabled: bool,
pub full_duplex: bool,
pub gateways: Vec<String>,
pub ip: String,
pub latency: usize,
pub mac: String,
pub name: String,
pub nameservers: Vec<String>,
pub netmask: String,
pub num_port: usize,
pub rx_bytes: usize,
pub rx_dropped: usize,
pub rx_errors: usize,
pub rx_multicast: usize,
pub rx_packets: usize,
pub speed: usize,
pub speedtest_lastrun: usize,
pub speedtest_ping: usize,
pub speedtest_status: String,
pub tx_bytes: usize,
pub tx_dropped: usize,
pub tx_errors: usize,
pub tx_packets: usize,
pub up: bool,
pub uptime: usize,
pub xput_down: usize,
pub xput_up: usize,
}
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub(crate) struct Config {
pub inform_url: String,
pub capability: Vec<String>,
pub cfgversion: String,
pub selfrun_guest_mode: String,
pub led_enabled: bool,
pub stun_url: String,
pub mgmt_url: String,
pub authkey: String,
pub use_aes_gcm: bool,
pub report_crash: bool,
}
use pnet::{
datalink::{interfaces, NetworkInterface},
util::MacAddr,
};
use std::str::FromStr;
use std::{fs, io, path};
#[derive(PartialEq, Clone, Debug)]
pub(crate) struct UnixNetworkDevice {
name: String,
mac: MacAddr,
interface: NetworkInterface,
statistics: UnixNetworkDeviceStatistics,
}
impl UnixNetworkDevice {
pub fn new(name: &String) -> io::Result<UnixNetworkDevice> {
let device_path = path::Path::new("/sys/class/net").join(name);
if !device_path.as_path().is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Unable to locate UnixNetworkDevice {}", name),
));
}
let mac_string = fs::read_to_string(device_path.join("address"))?;
if mac_string.trim().len() < 15 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Mac address is empty.",
));
}
let mac = MacAddr::from_str(mac_string.trim())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let interface = interfaces().into_iter().filter(|i| i.name == *name).next();
if interface.is_none() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Unable to locate NetworkInterface {}", name),
));
}
let interface = interface.unwrap();
let statistics = UnixNetworkDeviceStatistics::new(name);
Ok(UnixNetworkDevice {
name: name.clone(),
mac,
interface,
statistics,
})
}
pub fn name(&self) -> String {
self.name.clone()
}
pub fn mac(&self) -> MacAddr {
self.mac
}
pub fn interface(&self) -> NetworkInterface {
self.interface.clone()
}
pub fn statistics(&self) -> UnixNetworkDeviceStatistics {
self.statistics.clone()
}
}
// ===== Statistics =====
#[derive(PartialOrd, PartialEq, Clone, Debug)]
pub(crate) struct UnixNetworkDeviceStatistics {
pub collisions: usize,
pub multicast: usize,
pub rx_bytes: usize,
pub rx_compressed: usize,
pub rx_crc_errors: usize,
pub rx_dropped: usize,
pub rx_errors: usize,
pub rx_fifo_errors: usize,
pub rx_frame_errors: usize,
pub rx_length_errors: usize,
pub rx_missed_errors: usize,
pub rx_nohandler: usize,
pub rx_over_errors: usize,
pub rx_packets: usize,
pub tx_aborted_errors: usize,
pub tx_bytes: usize,
pub tx_carrier_errors: usize,
pub tx_compressed: usize,
pub tx_dropped: usize,
pub tx_errors: usize,
pub tx_fifo_errors: usize,
pub tx_heartbeat_errors: usize,
pub tx_packets: usize,
pub tx_window_errors: usize,
}
impl UnixNetworkDeviceStatistics {
pub fn new(device_name: &String) -> UnixNetworkDeviceStatistics {
let read_value = |statistic_name: &str| -> io::Result<usize> {
let stat_path = path::Path::new("/sys/class/net")
.join(device_name)
.join("statistics")
.join(statistic_name);
let value = fs::read_to_string(stat_path.as_path())?;
usize::from_str(value.trim())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
};
UnixNetworkDeviceStatistics {
collisions: read_value("collisions").unwrap_or_default(),
multicast: read_value("multicast").unwrap_or_default(),
rx_bytes: read_value("rx_bytes").unwrap_or_default(),
rx_compressed: read_value("rx_compressed").unwrap_or_default(),
rx_crc_errors: read_value("rx_crc_errors").unwrap_or_default(),
rx_dropped: read_value("rx_dropped").unwrap_or_default(),
rx_errors: read_value("rx_errors").unwrap_or_default(),
rx_fifo_errors: read_value("rx_fifo_errors").unwrap_or_default(),
rx_frame_errors: read_value("rx_frame_errors").unwrap_or_default(),
rx_length_errors: read_value("rx_length_errors").unwrap_or_default(),
rx_missed_errors: read_value("rx_missed_errors").unwrap_or_default(),
rx_nohandler: read_value("rx_nohandler").unwrap_or_default(),
rx_over_errors: read_value("rx_over_errors").unwrap_or_default(),
rx_packets: read_value("rx_packets").unwrap_or_default(),
tx_aborted_errors: read_value("tx_aborted_errors").unwrap_or_default(),
tx_bytes: read_value("tx_bytes").unwrap_or_default(),
tx_carrier_errors: read_value("tx_carrier_errors").unwrap_or_default(),
tx_compressed: read_value("tx_compressed").unwrap_or_default(),
tx_dropped: read_value("tx_dropped").unwrap_or_default(),
tx_errors: read_value("tx_errors").unwrap_or_default(),
tx_fifo_errors: read_value("tx_fifo_errors").unwrap_or_default(),
tx_heartbeat_errors: read_value("tx_heartbeat_errors").unwrap_or_default(),
tx_packets: read_value("tx_packets").unwrap_or_default(),
tx_window_errors: read_value("tx_window_errors").unwrap_or_default(),
}
}
}
impl From<UnixNetworkDevice> for OpnFiInformNetworkInterface {
fn from(value: UnixNetworkDevice) -> Self {
let interface = value.interface();
let stats = value.statistics();
let ip = interface
.ips
.iter()
.filter(|ip| ip.is_ipv4())
.next()
.unwrap();
Self {
drops: stats.rx_dropped + stats.tx_dropped,
enabled: true,
full_duplex: true,
gateways: vec![],
ip: ip.ip().to_string(),
latency: 1,
mac: value.mac().to_string(),
name: value.name().to_string(),
nameservers: vec![],
netmask: ip.mask().to_string(),
num_port: interface.index as usize,
rx_bytes: stats.rx_bytes,
rx_dropped: stats.rx_dropped,
rx_errors: stats.rx_errors,
rx_multicast: 0,
rx_packets: stats.rx_packets,
speed: 1000,
speedtest_lastrun: 0,
speedtest_ping: 0,
speedtest_status: "Idle".to_string(),
tx_bytes: stats.tx_bytes,
tx_dropped: stats.tx_dropped,
tx_errors: stats.tx_errors,
tx_packets: stats.tx_packets,
up: true,
uptime: 0,
xput_down: 0,
xput_up: 0,
}
}
}
fn main() {
let matches = clap::App::new("OpnFi Device")
.version("0.1.0")
.author("James Parks <jrjparks@zathera.com>")
.about("Emulates a UniFi device")
.arg(
clap::Arg::with_name("config")
.short("c")
.long("config")
.value_name("FILE")
.help("Sets a config file path to use")
.takes_value(true),
)
.arg(
clap::Arg::with_name("controller")
.long("controller")
.value_name("FILE")
.help("FQDN or IP Address of UniFi Controller")
.takes_value(true),
)
.arg(
clap::Arg::with_name("wan")
.short("w")
.long("wan")
.value_name("NIC")
.help("Set the nic to report for WAN")
.takes_value(true)
.default_value("eth0"),
)
.arg(
clap::Arg::with_name("lan")
.short("l")
.long("lan")
.value_name("NIC")
.help("Set the nic to report for LAN")
.takes_value(true)
.default_value("eth1"),
)
.arg(
clap::Arg::with_name("bri")
.short("b")
.long("bri")
.value_name("NIC")
.help("Set the nic to report for BRIDGE")
.takes_value(true)
.default_value("bri0"),
)
.get_matches();
let _wan_device = match matches.value_of("wan") {
Some(wan_name) => {
info!("Using {} as WAN device.", wan_name);
UnixNetworkDevice::new(&wan_name.to_string()).ok()
}
None => None,
};
let _lan_device = match matches.value_of("lan") {
Some(lan_name) => {
info!("Using {} as LAN device.", lan_name);
UnixNetworkDevice::new(&lan_name.to_string()).ok()
}
None => None,
};
let _bri_device = match matches.value_of("bri") {
Some(bri_name) => {
info!("Using {} as BRIDGE device.", bri_name);
UnixNetworkDevice::new(&bri_name.to_string()).ok()
}
None => None,
};
println!("wan_device {:?}", _wan_device);
println!("lan_device {:?}", _lan_device);
println!("bri_device {:?}", _bri_device);
// Interfaces
println!("Wan_Interface");
let _wan_interface: Option<OpnFiInformNetworkInterface> = match &mut _wan_device.as_ref()
{
Some(wan) => Some(wan.clone().into()),
_ => None,
};
println!("Lan_Interface");
let _lan_interface: Option<OpnFiInformNetworkInterface> = match &mut _lan_device.as_ref()
{
Some(lan) => Some(lan.clone().into()),
_ => None,
};
println!("Bri_Interface");
let _bri_interface: Option<OpnFiInformNetworkInterface> = match &mut _bri_device.as_ref()
{
Some(bri) => Some(bri.clone().into()),
_ => None,
};
}
[package]
name = "test_opnfi"
version = "0.0.1"
authors = ["Mike Schaffner <mcschaffner@gmail.com>"]
edition = "2018"
license = "Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lazy_static = "1.4.0"
toml = "0.5.5"
regex = "1.3.1"
rand = "0.7.2"
net2 = "0.2.33"
byteorder = "1.3.2"
serde = "1.0.103"
serde_json = "1.0.42"
sysinfo = "0.9.6"
reqwest = "0.9"
hex = "0.4.0"
pnet = "0.23.0"
clap = "2.33"
ctrlc = "3.1.3"
log = "0.4.8"
simple_logger = "1.3.0"