├── .gitignore ├── docker-compose.yml ├── src ├── pkt │ ├── mod.rs │ ├── generators.rs │ └── handlers.rs ├── bin │ └── failover.rs ├── state_machine.rs ├── router.rs ├── network.rs ├── checksum.rs ├── lib.rs ├── general.rs ├── core_tasks.rs ├── packet.rs ├── observer.rs └── config.rs ├── images ├── failover.png └── vrp-illustration.png ├── .dockerignore ├── Dockerfile ├── run.sh ├── README.md ├── sample-vrrp-config.json ├── snapcraft.yaml ├── Cargo.toml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | src/bin/trial.rs 3 | *.snap 4 | build.sh 5 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | failover: 4 | build: . -------------------------------------------------------------------------------- /src/pkt/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod generators; 2 | pub(crate) mod handlers; 3 | -------------------------------------------------------------------------------- /images/failover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/failover/HEAD/images/failover.png -------------------------------------------------------------------------------- /images/vrp-illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paul-weqe/failover/HEAD/images/vrp-illustration.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | images/ 2 | target/ 3 | build.sh 4 | run.sh 5 | *.snap 6 | sample-vrrp-config.json 7 | snapcraft.yaml -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.76.0 2 | 3 | RUN apt update && apt install -y iproute2 4 | 5 | WORKDIR /failover 6 | COPY . /failover/ 7 | 8 | RUN cargo build 9 | 10 | 11 | CMD [ "./target/debug/failover", "file-mode" ] 12 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build 4 | sudo ./target/debug/failover run file-mode --filename sample-vrrp-config.json --log-file-path /var/log/failover.log 5 | pid=$! 6 | wait $pid 7 | sudo ./target/debug/failover teardown file-mode --filename sample-vrrp-config.json 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/failover_vr.svg)](https://crates.io/crates/failover_vr) 2 | 3 | [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/failover) 4 | 5 | 6 | # Failover 7 | 8 | Failover is a VRRP(version 2) implementation currently configured for Ubuntu(soon introducing other Linux and windows systems). 9 | 10 | If you want to install and run it directly, find information in the [docs](https://failover-docs.readthedocs.io/). 11 | -------------------------------------------------------------------------------- /sample-vrrp-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "VR_1", 4 | "vrid": 51, 5 | "interface_name": "wlo1", 6 | "ip_addresses": [ 7 | "192.168.100.100/24" 8 | ], 9 | "priority": 101, 10 | "advert_interval": 1, 11 | "preempt_mode": true 12 | }, 13 | { 14 | "name": "VR_2", 15 | "vrid": 53, 16 | "interface_name": "wlo1", 17 | "ip_addresses": [ 18 | "192.168.100.100/24" 19 | ], 20 | "priority": 110, 21 | "advert_interval": 1, 22 | "preempt_mode": true 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: failover 2 | version: '0.3.2' 3 | summary: A rust implementation of VRRP 4 | description: | 5 | Failover is a rust implementation of the VRRP protocol. 6 | 7 | VRRP is a redundancy protocol that achieves this by 'creating' a virtual device with virtual network parameters. 8 | 9 | The protocol has been tested together with `keepalived` and will soon be tested with routers and switches from major vendors. 10 | 11 | 12 | grade: stable 13 | confinement: devmode 14 | base: core22 15 | 16 | parts: 17 | failover: 18 | plugin: rust 19 | source: . 20 | 21 | apps: 22 | main: 23 | command: bin/failover file-mode 24 | daemon: simple 25 | post-stop-command: bin/failover file-mode --action teardown 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "failover_vr" 3 | version = "0.4.2" 4 | edition = "2021" 5 | authors = [ "Paul-weqe " ] 6 | description = "VRRP implementation library (and binary installation) in Rust." 7 | license = "MIT" 8 | repository = "https://github.com/Paul-weqe/failover" 9 | keywords = [ "vrrp", "network", "RFC", "redundancy" ] 10 | documentation = "https://failover-docs.readthedocs.io/en/latest/install.html#running-as-library-in-rust" 11 | 12 | 13 | [dependencies] 14 | byteorder = "1.5.0" 15 | ipnet = { version = "2.9.0", features = ["serde"]} 16 | log = "0.4.21" 17 | pnet = "0.33.0" 18 | rand = "0.8.5" 19 | serde = { version = "1.0.197", features = ["derive"] } 20 | serde_json = "1.0.114" 21 | libc = "0.2.153" 22 | tokio = { version = "1.37.0", features = ["full"]} 23 | clap = { version = "4.5.4", features = ["derive"] } 24 | socket2 = "0.5.7" 25 | bytes = "1.7.1" 26 | log4rs = "1.3.0" 27 | 28 | [lib] 29 | doctest = false 30 | -------------------------------------------------------------------------------- /src/pkt/generators.rs: -------------------------------------------------------------------------------- 1 | use pnet::datalink::NetworkInterface; 2 | 3 | /// PktGenerator is meant to create Network packets (header + body) 4 | /// given specific parameters. The network interface together with the payload 5 | /// help us with getting the necessary items. 6 | /// 7 | /// Generating an ARP packet for example: 8 | /// 9 | /// ```no_run 10 | /// use pnet::packet::datalink::NetworkInterface; 11 | /// use crate::general::create_datalink_channel; 12 | /// use std::net::Ipv4Addr; 13 | /// 14 | /// let interface: NetworkInterface = create_datalink_channel("wlo1"); 15 | /// let mut eth_buff = [0u8; 42]; 16 | /// let mut arp_buff = [0u8; 28]; 17 | /// 18 | /// let gen = MutablePktGenerator(interface: interface); 19 | /// let arp_pkt = generator.gen_gratuitous_arp_packet(eth_buff, arp_buff, Ipv4Addr::from_str("192.168.100.12")); 20 | /// ``` 21 | #[derive(Clone, Debug)] 22 | pub(crate) struct MutablePktGenerator { 23 | pub(crate) interface: NetworkInterface, 24 | } 25 | 26 | impl MutablePktGenerator { 27 | pub(crate) fn new(interface: NetworkInterface) -> Self { 28 | MutablePktGenerator { interface } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/bin/failover.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use failover_vr::{ 3 | config::{parse_cli_opts, Action, CliArgs2}, 4 | general::{config_to_vr, virtual_address_action}, 5 | }; 6 | use tokio::task::JoinSet; 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | let args = CliArgs2::parse(); 11 | let routers_config = match parse_cli_opts(args) { 12 | Ok(config) => { 13 | log::debug!("Configs read successfully"); 14 | config 15 | } 16 | Err(err) => { 17 | log::error!("Error reading configs {err}"); 18 | std::process::exit(1); 19 | } 20 | }; 21 | 22 | let mut routers_tasks = JoinSet::new(); 23 | for config in routers_config { 24 | match config.action() { 25 | Action::Run => { 26 | let vrouter = config_to_vr(config); 27 | routers_tasks.spawn(async { failover_vr::run(vrouter).await }); 28 | } 29 | Action::Teardown => { 30 | log::info!("tearing down {:#?}", config.name()); 31 | virtual_address_action("delete", &config.ip_addresses(), &config.interface_name()); 32 | log::info!("{:#?} tear down complete", config.name()); 33 | } 34 | } 35 | } 36 | 37 | if routers_tasks.is_empty() { 38 | log::info!("failover shutting down. No VRRP instances to run"); 39 | std::process::exit(0); 40 | } 41 | 42 | while routers_tasks.join_next().await.is_some() {} 43 | } 44 | -------------------------------------------------------------------------------- /src/state_machine.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | 3 | #[derive(Debug, Clone, Default)] 4 | pub struct VirtualRouterMachine { 5 | pub(crate) timer: Timer, 6 | pub(crate) state: States, 7 | pub(crate) event: Event, 8 | } 9 | 10 | impl VirtualRouterMachine { 11 | pub fn set_advert_timer(&mut self, duration: f32) { 12 | self.timer = Timer { 13 | t_type: TimerType::Adver, 14 | remaining_time: duration, 15 | waiting_for: Some(Instant::now() + Duration::from_secs_f32(duration)), 16 | }; 17 | } 18 | 19 | pub fn set_master_down_timer(&mut self, duration: f32) { 20 | self.timer = Timer { 21 | t_type: TimerType::MasterDown, 22 | remaining_time: duration, 23 | waiting_for: Some(Instant::now() + Duration::from_secs_f32(duration)), 24 | }; 25 | } 26 | 27 | pub fn disable_timer(&mut self) { 28 | self.timer = Timer { 29 | t_type: TimerType::Null, 30 | remaining_time: f32::default(), 31 | waiting_for: None, 32 | }; 33 | } 34 | } 35 | 36 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 37 | pub(crate) enum States { 38 | #[default] 39 | Init, 40 | Backup, 41 | Master, 42 | } 43 | 44 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 45 | pub(crate) struct Timer { 46 | pub t_type: TimerType, 47 | pub remaining_time: f32, 48 | pub waiting_for: Option, 49 | } 50 | 51 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 52 | pub(crate) enum TimerType { 53 | #[default] 54 | Null, 55 | 56 | MasterDown, 57 | Adver, 58 | } 59 | 60 | #[derive(Debug, Clone, Copy, PartialEq, Default)] 61 | #[allow(dead_code)] 62 | pub(crate) enum Event { 63 | #[default] 64 | Startup, 65 | Null, 66 | Shutdown, 67 | MasterDown, 68 | } 69 | -------------------------------------------------------------------------------- /src/router.rs: -------------------------------------------------------------------------------- 1 | use ipnet::Ipv4Net; 2 | use std::net::Ipv4Addr; 3 | 4 | use crate::state_machine::VirtualRouterMachine; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct VirtualRouter { 8 | pub name: String, 9 | pub vrid: u8, 10 | pub ip_addresses: Vec, 11 | pub priority: u8, 12 | pub skew_time: f32, 13 | pub advert_interval: u8, 14 | pub master_down_interval: f32, 15 | pub preempt_mode: bool, 16 | pub network_interface: String, 17 | pub fsm: VirtualRouterMachine, 18 | } 19 | 20 | impl VirtualRouter { 21 | pub(crate) fn ipv4_addresses(&self) -> Vec { 22 | let mut addrs: Vec = vec![]; 23 | for a in self.ip_addresses.iter() { 24 | addrs.push(a.addr()); 25 | } 26 | addrs 27 | } 28 | 29 | pub(crate) fn str_ipv4_addresses(&self) -> Vec { 30 | let mut addrs: Vec = vec![]; 31 | for a in self.ip_addresses.iter() { 32 | addrs.push(a.to_string()); 33 | } 34 | addrs 35 | } 36 | 37 | pub fn new( 38 | name: String, 39 | vrid: u8, 40 | ip_addresses: Vec, 41 | priority: u8, 42 | advert_interval: u8, 43 | preempt_mode: bool, 44 | network_interface: String, 45 | ) -> Self { 46 | // SKEW TIME = (256 * priority) / 256 47 | let skew_time: f32 = (256_f32 - priority as f32) / 256_f32; 48 | // MASTER DOWN INTERVAL = (3 * ADVERTISEMENT INTERVAL ) + SKEW TIME 49 | let master_down_interval: f32 = (3_f32 * advert_interval as f32) + skew_time; 50 | 51 | Self { 52 | name, 53 | vrid, 54 | ip_addresses, 55 | priority, 56 | skew_time, 57 | advert_interval, 58 | master_down_interval, 59 | preempt_mode, 60 | network_interface, 61 | fsm: VirtualRouterMachine::default(), 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/network.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | checksum, 3 | packet::{ARPframe, VrrpPacket}, 4 | }; 5 | use libc::AF_PACKET; 6 | use socket2::{Domain, Protocol, Socket, Type}; 7 | use std::net::{Ipv4Addr, SocketAddrV4}; 8 | use tokio::io::unix::AsyncFd; 9 | 10 | pub fn send_vrrp_packet(ifname: &str, mut packet: VrrpPacket) -> std::io::Result { 11 | let sock = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::from(112))).unwrap(); 12 | let _ = sock.bind_device(Some(ifname.as_bytes())); 13 | let _ = sock.set_broadcast(true); 14 | let _ = sock.set_ttl(255); 15 | 16 | // confirm checksum. checksum position is the third item in 16 bit words 17 | packet.checksum = checksum::calculate(&packet.encode(), 3); 18 | 19 | let buf: &[u8] = &packet.encode(); 20 | let saddr = SocketAddrV4::new(Ipv4Addr::new(224, 0, 0, 18), 0); 21 | 22 | sock.send_to(buf, &saddr.into()) 23 | } 24 | 25 | pub fn send_packet_arp(ifname: &str, mut arp_frame: ARPframe) { 26 | use libc::{c_void, sendto, sockaddr, sockaddr_ll}; 27 | use std::ffi::CString; 28 | use std::os::fd::AsRawFd; 29 | 30 | let sock_init = Socket::new(Domain::PACKET, Type::RAW, Some(Protocol::from(0x0806))).unwrap(); 31 | 32 | let _ = sock_init.bind_device(Some(ifname.as_bytes())); 33 | let _ = sock_init.set_broadcast(true); 34 | let sock = AsyncFd::new(sock_init).unwrap(); 35 | 36 | let c_ifname = match CString::new(ifname) { 37 | Ok(c_ifname) => c_ifname, 38 | Err(err) => { 39 | log::error!("unable to cast {ifname} into CString"); 40 | log::error!("{err}"); 41 | return; 42 | } 43 | }; 44 | let ifindex = unsafe { libc::if_nametoindex(c_ifname.as_ptr()) }; 45 | 46 | let mut sa = sockaddr_ll { 47 | sll_family: AF_PACKET as u16, 48 | sll_protocol: 0x806_u16.to_be(), 49 | sll_ifindex: ifindex as i32, 50 | sll_hatype: 0, 51 | sll_pkttype: 0, 52 | sll_halen: 0, 53 | sll_addr: [0; 8], 54 | }; 55 | 56 | unsafe { 57 | let ptr_sockaddr = std::mem::transmute::<*mut sockaddr_ll, *mut sockaddr>(&mut sa); 58 | 59 | match sendto( 60 | sock.as_raw_fd(), 61 | &mut arp_frame as *mut _ as *const c_void, 62 | std::mem::size_of_val(&arp_frame), 63 | 0, 64 | ptr_sockaddr, 65 | std::mem::size_of_val(&sa) as u32, 66 | ) { 67 | -1 => { 68 | log::warn!("Problem sending ARP message"); 69 | } 70 | _fd => {} 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/checksum.rs: -------------------------------------------------------------------------------- 1 | //! checksums related functions module 2 | //! This module is dedicated to internet checksums functions. 3 | //! 4 | //! credit for rfc1071, propagate_carries and one_complement_sum 5 | //! calculation to ref. impl. 6 | //! and rust's rVVRP github 7 | use byteorder::{ByteOrder, NetworkEndian}; 8 | const _RFC1071_CHUNK_SIZE: usize = 32; 9 | 10 | // rfc1071() function 11 | /// compute rfc1071 internet checksum 12 | /// returns all-ones if carried checksum is valid 13 | #[allow(dead_code)] 14 | pub(crate) fn confirm_checksum(mut data: &[u8]) -> u16 { 15 | let mut acc = 0; 16 | 17 | // for each 32 bytes chunk 18 | while data.len() >= _RFC1071_CHUNK_SIZE { 19 | let mut d = &data[.._RFC1071_CHUNK_SIZE]; 20 | while d.len() >= 2 { 21 | // sum adjacent pairs converted to 16 bits integer 22 | acc += NetworkEndian::read_u16(d) as u32; 23 | // take the next 2 bytes for the next iteration 24 | d = &d[2..]; 25 | } 26 | data = &data[_RFC1071_CHUNK_SIZE..]; 27 | } 28 | 29 | // if it does not fit a 32 bytes chunk 30 | while data.len() >= 2 { 31 | acc += NetworkEndian::read_u16(data) as u32; 32 | data = &data[2..]; 33 | } 34 | 35 | // add odd byte is present 36 | if let Some(&v) = data.first() { 37 | acc += (v as u32) << 8; 38 | } 39 | 40 | propagate_carries(acc) 41 | } 42 | 43 | #[allow(dead_code)] 44 | // propagate final complement? 45 | fn propagate_carries(word: u32) -> u16 { 46 | let sum = (word >> 16) + (word & 0xffff); 47 | ((sum >> 16) as u16) + (sum as u16) 48 | } 49 | 50 | pub fn calculate(data: &[u8], checksum_position: usize) -> u16 { 51 | let mut result: u16 = 0; 52 | 53 | // since data is in u8's, we need pairs of the data to get u16 54 | for (i, pair) in data.chunks(2).enumerate() { 55 | // the fifth pair is the checksum field, which is ignored 56 | if i == checksum_position { 57 | continue; 58 | } 59 | 60 | result = ones_complement(result, ((pair[0] as u16) << 8) | pair[1] as u16); 61 | } 62 | 63 | // do a one's complement to get the sum 64 | !result 65 | } 66 | 67 | fn ones_complement(mut first: u16, mut second: u16) -> u16 { 68 | let mut carry: u32 = 10; 69 | let mut result: u16 = 0; 70 | 71 | while carry != 0 { 72 | let tmp_res = first as u32 + second as u32; 73 | result = (tmp_res & 0xFFFF) as u16; 74 | carry = tmp_res >> 16; 75 | first = result; 76 | second = carry as u16; 77 | } 78 | result 79 | } 80 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use error::{NetError, OptError}; 2 | use general::get_interface; 3 | use observer::EventObserver; 4 | use pkt::generators::{self, MutablePktGenerator}; 5 | use router::VirtualRouter; 6 | use state_machine::Event; 7 | use std::sync::{Arc, Mutex}; 8 | use tokio::task::JoinSet; 9 | 10 | mod checksum; 11 | pub mod config; 12 | mod core_tasks; 13 | pub mod general; 14 | mod network; 15 | mod observer; 16 | mod packet; 17 | mod pkt; 18 | pub mod router; 19 | mod state_machine; 20 | 21 | pub(crate) type NetResult = Result; 22 | pub(crate) type OptResult = Result; 23 | 24 | #[derive(Clone)] 25 | pub(crate) struct TaskItems { 26 | vrouter: Arc>, 27 | generator: MutablePktGenerator, 28 | } 29 | 30 | pub mod error { 31 | use std::{error::Error, fmt::Display}; 32 | 33 | // Network errors 34 | #[derive(Debug)] 35 | pub struct NetError(pub String); 36 | impl Error for NetError {} 37 | impl Display for NetError { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | write!(f, "{}", self.0) 40 | } 41 | } 42 | 43 | // OptError 44 | // used for getting errors when parsing CLI arguments 45 | #[derive(Debug)] 46 | pub struct OptError(pub String); 47 | impl Error for OptError {} 48 | impl Display for OptError { 49 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 | write!(f, "{}", self.0) 51 | } 52 | } 53 | } 54 | 55 | /// initiates the VRRP functions across the board. 56 | /// from interfaces, channels, packet handling etc... 57 | pub async fn run(vrouter: VirtualRouter) -> NetResult<()> { 58 | let interface = get_interface(&vrouter.network_interface)?; 59 | 60 | let items = TaskItems { 61 | vrouter: Arc::new(Mutex::new(vrouter)), 62 | generator: generators::MutablePktGenerator::new(interface.clone()), 63 | }; 64 | 65 | match EventObserver::notify(items.vrouter.clone(), Event::Startup) { 66 | Ok(_) => {} 67 | Err(err) => { 68 | log::error!("{err}"); 69 | panic!("Problem running initial notify statement"); 70 | } 71 | }; 72 | let mut tasks_set = JoinSet::new(); 73 | 74 | // sync process listens for any incoming network requests 75 | let network_items = items.clone(); 76 | tasks_set.spawn(async { core_tasks::network_process(network_items).await }); 77 | 78 | let timer_items = items.clone(); 79 | tasks_set.spawn(async { core_tasks::timer_process(timer_items).await }); 80 | 81 | while tasks_set.join_next().await.is_some() { 82 | // join tasks 83 | } 84 | 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /src/general.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::VrrpConfig, error::NetError, router::VirtualRouter, NetResult}; 2 | use ipnet::Ipv4Net; 3 | use pnet::datalink::{self, Channel, DataLinkReceiver, DataLinkSender, NetworkInterface}; 4 | use rand::{distributions::Alphanumeric, Rng}; 5 | use std::{process::Command, str::FromStr}; 6 | 7 | pub(crate) fn get_interface(name: &str) -> NetResult { 8 | let interface_names_match = |iface: &NetworkInterface| iface.name == name; 9 | let interfaces = datalink::linux::interfaces(); 10 | 11 | // check if interface name exists, if not create it 12 | match interfaces.into_iter().find(interface_names_match) { 13 | Some(interface) => Ok(interface), 14 | None => Err(NetError(format!( 15 | "unable to find interface with name {name}" 16 | ))), 17 | } 18 | } 19 | 20 | pub(crate) fn create_datalink_channel( 21 | interface: &NetworkInterface, 22 | ) -> NetResult<(Box, Box)> { 23 | match pnet::datalink::channel(interface, Default::default()) { 24 | Ok(Channel::Ethernet(tx, rx)) => Ok((tx, rx)), 25 | Ok(_) => { 26 | let err = "Unknown channel type"; 27 | log::error!("{err}"); 28 | Err(NetError(err.to_string())) 29 | } 30 | Err(err) => { 31 | log::error!("Problem creating datalink channel"); 32 | log::error!("{err}"); 33 | Err(NetError("Problem creating datalink channel".to_string())) 34 | } 35 | } 36 | } 37 | 38 | // takes the configs that have been received and converts them 39 | // into a virtual router instance. 40 | pub fn config_to_vr(conf: VrrpConfig) -> VirtualRouter { 41 | let mut ips: Vec = vec![]; 42 | if conf.ip_addresses().len() > 20 { 43 | log::warn!("({}) More than 20 IP addresses(max for VRRP) have been configured. Only first 20 addresses will be used..", conf.name()); 44 | } 45 | 46 | let addresses = if conf.ip_addresses().len() <= 20 { 47 | conf.ip_addresses() 48 | } else { 49 | conf.ip_addresses()[0..20].to_vec() 50 | }; 51 | for ip_config in addresses.iter() { 52 | match Ipv4Net::from_str(ip_config) { 53 | Ok(ip_addr) => ips.push(ip_addr), 54 | Err(_) => { 55 | //log::error!("({}) SKIPPING: Configured IP address '{:?}' not in the correct format ( oct[0].oct[1].oct[2].oct[3]/subnet )", ip_config, conf.name()); 56 | } 57 | } 58 | } 59 | 60 | let vr = VirtualRouter::new( 61 | conf.name(), 62 | conf.vrid(), 63 | ips, 64 | conf.priority(), 65 | conf.advert_interval(), 66 | conf.preempt_mode(), 67 | conf.interface_name(), 68 | ); 69 | log::info!("({}) Entered {:?} state.", vr.name, vr.fsm.state); 70 | vr 71 | } 72 | 73 | pub fn virtual_address_action(action: &str, addresses: &[String], interface_name: &str) { 74 | for addr in addresses { 75 | let cmd_args = vec!["address", action, &addr, "dev", interface_name]; 76 | let _ = Command::new("ip").args(cmd_args).output(); 77 | } 78 | } 79 | 80 | pub(crate) fn random_vr_name() -> String { 81 | let val: String = rand::thread_rng() 82 | .sample_iter(&Alphanumeric) 83 | .take(10) 84 | .map(char::from) 85 | .collect(); 86 | 87 | log::info!("Name for Virtual Router not given. generated name VR_{val}"); 88 | format!("VR_{val}") 89 | } 90 | -------------------------------------------------------------------------------- /src/core_tasks.rs: -------------------------------------------------------------------------------- 1 | /// This is the main file for the processes being run. 2 | /// There are three functions holding these processes 3 | /// functions that are to be run: 4 | /// - Network Process (pub(crate) fn network_process) 5 | /// - Timer Process (pub(crate) fn timer_process) 6 | /// 7 | /// Each of the above will be run on a thread of their own. 8 | /// Avoided using async since they were only three separate threads needed. 9 | /// 10 | /// 11 | use crate::packet::VrrpPacket; 12 | use crate::{checksum, network}; 13 | use crate::{ 14 | general::create_datalink_channel, 15 | observer::EventObserver, 16 | pkt::handlers::{handle_incoming_arp_pkt, handle_incoming_vrrp_pkt}, 17 | state_machine::{Event, States}, 18 | NetResult, 19 | }; 20 | use pnet::packet::{ 21 | ethernet::{EtherTypes, EthernetPacket}, 22 | ip::IpNextHeaderProtocols, 23 | ipv4::Ipv4Packet, 24 | Packet, 25 | }; 26 | use std::net::Ipv4Addr; 27 | use std::{ 28 | sync::Arc, 29 | time::{Duration, Instant}, 30 | }; 31 | use tokio::time; 32 | 33 | /// Waits for network connections and does the necessary actions. 34 | /// Acts on the queries mostly described from the state machine 35 | /// in chapter 6.3 onwards ofRFC 3768 36 | pub(crate) async fn network_process(items: crate::TaskItems) -> NetResult<()> { 37 | // NetworkInterface 38 | let interface = items.generator.interface; 39 | 40 | let (_sender, mut receiver) = create_datalink_channel(&interface)?; 41 | let vrouter = items.vrouter; 42 | 43 | loop { 44 | let buff = match receiver.next() { 45 | Ok(buf) => buf, 46 | Err(_) => { 47 | log::warn!("Error Receiving Packet"); 48 | continue; 49 | } 50 | }; 51 | 52 | let incoming_eth_pkt = match EthernetPacket::new(buff) { 53 | Some(incoming_eth_pkt) => incoming_eth_pkt, 54 | None => continue, 55 | }; 56 | 57 | match incoming_eth_pkt.get_ethertype() { 58 | EtherTypes::Ipv4 => { 59 | let incoming_ip_pkt = match Ipv4Packet::new(incoming_eth_pkt.payload()) { 60 | Some(pkt) => pkt, 61 | // when there is no IPv4 packet received or the IP packet is unable to be read 62 | None => { 63 | log::warn!("Unable to read IP packet"); 64 | continue; 65 | } 66 | }; 67 | 68 | if incoming_ip_pkt.get_next_level_protocol() == IpNextHeaderProtocols::Vrrp { 69 | match handle_incoming_vrrp_pkt(&incoming_eth_pkt, Arc::clone(&vrouter)) { 70 | Ok(_) => {} 71 | Err(_) => continue, 72 | } 73 | } 74 | } 75 | 76 | EtherTypes::Arp => { 77 | match handle_incoming_arp_pkt(&incoming_eth_pkt, Arc::clone(&vrouter)) { 78 | Ok(_) => {} 79 | Err(err) => { 80 | log::error!("problem handing incoming ARP packet"); 81 | log::error!("{err}"); 82 | } 83 | } 84 | } 85 | 86 | _ => { 87 | // see if we can get the vrouter 88 | let lock = &vrouter.lock(); 89 | let net_vr = match lock { 90 | Ok(net_vr) => net_vr, 91 | Err(err) => { 92 | log::warn!("Cannot Get router mutex"); 93 | log::warn!("{err}"); 94 | continue; 95 | } 96 | }; 97 | 98 | // if we are master, we forward the packet. 99 | // otherwise we leave the packet be 100 | if net_vr.fsm.state == States::Master {} 101 | } 102 | } 103 | } 104 | } 105 | 106 | /// Used to track the various timers: (MasterDownTimer and Advertimer) 107 | /// Has been explained in RFC 3768 section 6.2 108 | pub(crate) async fn timer_process(items: crate::TaskItems) -> NetResult<()> { 109 | let mut interval = time::interval(Duration::from_millis(100)); 110 | let vrouter = items.vrouter; 111 | 112 | loop { 113 | interval.tick().await; 114 | let mut vrouter = match vrouter.lock() { 115 | Ok(vrouter) => vrouter, 116 | Err(_) => { 117 | log::error!("Unable to get mutex for vrouter"); 118 | continue; 119 | } 120 | }; 121 | let timer = vrouter.fsm.timer; 122 | 123 | match timer.t_type { 124 | crate::state_machine::TimerType::MasterDown => { 125 | match vrouter.fsm.timer.waiting_for { 126 | // waiting is the time being waited for 127 | // to notify for the master down 128 | Some(waiting) => { 129 | if Instant::now() > waiting { 130 | match EventObserver::notify_mut(vrouter, Event::MasterDown) { 131 | Ok(info) => info, 132 | Err(err) => return Err(err), 133 | } 134 | } 135 | } 136 | None => { 137 | log::warn!("No timer being waited for."); 138 | continue; 139 | } 140 | }; 141 | } 142 | 143 | crate::state_machine::TimerType::Adver => { 144 | match vrouter.fsm.timer.waiting_for { 145 | Some(waiting) => { 146 | if Instant::now() > waiting { 147 | // send vrrp advertisement 148 | let mut addresses: Vec = vec![]; 149 | vrouter.ip_addresses.iter().for_each(|ip| { 150 | addresses.push(ip.addr()); 151 | }); 152 | let mut pkt = VrrpPacket { 153 | version: 2, 154 | hdr_type: 1, 155 | vrid: vrouter.vrid, 156 | priority: vrouter.priority, 157 | count_ip: vrouter.ip_addresses.len() as u8, 158 | auth_type: 0, 159 | adver_int: vrouter.advert_interval, 160 | checksum: 0, 161 | ip_addresses: addresses, 162 | auth_data: 0, 163 | auth_data2: 0, 164 | }; 165 | // confirm checksum. checksum position is the third item in 16 bit words 166 | pkt.checksum = checksum::calculate(&pkt.encode(), 3); 167 | 168 | let _ = network::send_vrrp_packet(&vrouter.network_interface, pkt); 169 | let advert_time = vrouter.advert_interval as f32; 170 | vrouter.fsm.set_advert_timer(advert_time); 171 | } 172 | } 173 | None => { 174 | log::warn!("No timer being waited for."); 175 | continue; 176 | } 177 | }; 178 | } 179 | 180 | crate::state_machine::TimerType::Null => {} 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/packet.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | 5 | // 6 | // VRRP Packet Format. 7 | // 8 | // 0 1 2 3 9 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | // |Version| Type | Virtual Rtr ID| Priority | Count IP Addrs| 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | // | Auth Type | Adver Int | Checksum | 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // | IP Address (1) | 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | // | . | 18 | // | . | 19 | // | . | 20 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | // | IP Address (n) | 22 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | // | Authentication Data (1) | 24 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 25 | // | Authentication Data (2) | 26 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 | // 28 | #[derive(Clone, Debug)] 29 | pub struct VrrpPacket { 30 | pub version: u8, 31 | pub hdr_type: u8, 32 | pub vrid: u8, 33 | pub priority: u8, 34 | pub count_ip: u8, 35 | pub auth_type: u8, 36 | pub adver_int: u8, 37 | pub checksum: u16, 38 | pub ip_addresses: Vec, 39 | 40 | // the following two are only used for backward compatibility. 41 | pub auth_data: u32, 42 | pub auth_data2: u32, 43 | } 44 | 45 | impl VrrpPacket { 46 | const MIN_PKT_LENGTH: usize = 16; 47 | const MAX_PKT_LENGTH: usize = 80; 48 | const MAX_IP_COUNT: usize = 16; 49 | 50 | // Encodes VRRP packet into a bytes buffer. 51 | pub fn encode(&self) -> BytesMut { 52 | let mut buf = BytesMut::with_capacity(114); 53 | let ver_type = (self.version << 4) | self.hdr_type; 54 | buf.put_u8(ver_type); 55 | buf.put_u8(self.vrid); 56 | buf.put_u8(self.priority); 57 | buf.put_u8(self.count_ip); 58 | buf.put_u8(self.auth_type); 59 | buf.put_u8(self.adver_int); 60 | buf.put_u16(self.checksum); 61 | for addr in self.ip_addresses.clone() { 62 | let octets = addr.octets(); 63 | octets.iter().for_each(|octet| buf.put_u8(*octet)); 64 | } 65 | 66 | buf.put_u32(self.auth_data); 67 | buf.put_u32(self.auth_data2); 68 | buf 69 | } 70 | 71 | pub fn decode(data: &[u8]) -> Option { 72 | // 1. pkt length verification 73 | let pkt_size = data.len(); 74 | 75 | let mut buf: Bytes = Bytes::copy_from_slice(data); 76 | let ver_type = buf.get_u8(); 77 | let version = ver_type >> 4; 78 | let hdr_type = ver_type & 0x0F; 79 | let vrid = buf.get_u8(); 80 | let priority = buf.get_u8(); 81 | let count_ip = buf.get_u8(); 82 | let auth_type = buf.get_u8(); 83 | let adver_int = buf.get_u8(); 84 | 85 | if !(Self::MIN_PKT_LENGTH..=Self::MAX_PKT_LENGTH).contains(&pkt_size) 86 | || count_ip as usize > Self::MAX_IP_COUNT 87 | || (count_ip * 4) + 16 != pkt_size as u8 88 | { 89 | return None; 90 | } 91 | 92 | let checksum = buf.get_u16(); 93 | 94 | let mut ip_addresses: Vec = vec![]; 95 | for _ in 0..count_ip { 96 | ip_addresses.push(Ipv4Addr::from_bits(buf.get_u32())); 97 | } 98 | 99 | let auth_data = buf.get_u32(); 100 | let auth_data2 = buf.get_u32(); 101 | 102 | Some(Self { 103 | version, 104 | hdr_type, 105 | vrid, 106 | priority, 107 | count_ip, 108 | auth_type, 109 | adver_int, 110 | checksum, 111 | ip_addresses, 112 | auth_data, 113 | auth_data2, 114 | }) 115 | } 116 | } 117 | 118 | #[repr(C)] 119 | pub struct ARPframe { 120 | // Ethernet Header 121 | pub dst_mac: [u8; 6], // destination MAC address 122 | pub src_mac: [u8; 6], // source MAC address 123 | pub ethertype: u16, // ether type 124 | 125 | // ARP 126 | pub hardware_type: u16, // network link type (0x1=ethernet) 127 | pub protocol_type: u16, // upper-layer protocol for resolution 128 | pub hw_addr_len: u8, // length of hardware address (bytes) 129 | pub proto_addr_len: u8, // upper-layer protocol address length 130 | pub opcode: u16, // operation (0x1=request, 0x2=reply) 131 | pub sender_hw_addr: [u8; 6], // sender hardware address 132 | pub sender_proto_addr: [u8; 4], // internetwork address of sender 133 | pub target_hw_addr: [u8; 6], // hardware address of target 134 | pub target_proto_addr: [u8; 4], // internetwork address of target 135 | } 136 | 137 | impl ARPframe { 138 | pub fn new(eth_pkt: EthernetFrame, arp_pkt: ArpPacket) -> Self { 139 | Self { 140 | dst_mac: eth_pkt.dst_mac, 141 | src_mac: eth_pkt.src_mac, 142 | ethertype: eth_pkt.ethertype.to_be(), 143 | 144 | hardware_type: arp_pkt.hw_type.to_be(), 145 | protocol_type: arp_pkt.proto_type.to_be(), 146 | hw_addr_len: arp_pkt.hw_length, 147 | proto_addr_len: arp_pkt.proto_length, 148 | opcode: arp_pkt.operation.to_be(), 149 | 150 | sender_hw_addr: arp_pkt.sender_hw_address, 151 | sender_proto_addr: arp_pkt.sender_proto_address, 152 | target_hw_addr: arp_pkt.target_hw_address, 153 | target_proto_addr: arp_pkt.target_proto_address, 154 | } 155 | } 156 | } 157 | 158 | #[derive(Clone, Debug)] 159 | pub struct EthernetFrame { 160 | pub dst_mac: [u8; 6], 161 | pub src_mac: [u8; 6], 162 | pub ethertype: u16, 163 | } 164 | 165 | #[derive(Clone, Debug)] 166 | pub struct ArpPacket { 167 | pub hw_type: u16, 168 | pub proto_type: u16, 169 | pub hw_length: u8, 170 | pub proto_length: u8, 171 | pub operation: u16, 172 | pub sender_hw_address: [u8; 6], 173 | pub sender_proto_address: [u8; 4], 174 | pub target_hw_address: [u8; 6], 175 | pub target_proto_address: [u8; 4], 176 | } 177 | 178 | impl ArpPacket { 179 | pub fn decode(data: &[u8]) -> Option { 180 | if data.len() != 28 { 181 | return None; 182 | } 183 | let mut buf = Bytes::copy_from_slice(data); 184 | let hw_type = buf.get_u16(); 185 | let proto_type = buf.get_u16(); 186 | let hw_length = buf.get_u8(); 187 | let proto_length = buf.get_u8(); 188 | let operation = buf.get_u16(); 189 | 190 | let sender_hw_address: [u8; 6] = [0_u8; 6]; 191 | for mut _x in &sender_hw_address { 192 | _x = &buf.get_u8(); 193 | } 194 | 195 | let sender_proto_address: [u8; 4] = [0_u8; 4]; 196 | for mut _x in &sender_proto_address { 197 | _x = &buf.get_u8(); 198 | } 199 | 200 | let target_hw_address: [u8; 6] = [0_u8; 6]; 201 | for mut _x in &target_hw_address { 202 | _x = &buf.get_u8(); 203 | } 204 | 205 | let target_proto_address: [u8; 4] = [0_u8; 4]; 206 | for mut _x in &target_proto_address { 207 | _x = &buf.get_u8(); 208 | } 209 | 210 | Some(Self { 211 | hw_type, 212 | proto_type, 213 | hw_length, 214 | proto_length, 215 | operation, 216 | sender_hw_address, 217 | sender_proto_address, 218 | target_hw_address, 219 | target_proto_address, 220 | }) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/observer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::Ipv4Addr, 3 | sync::{Arc, Mutex, MutexGuard}, 4 | }; 5 | 6 | use crate::{ 7 | checksum, 8 | error::NetError, 9 | general::{get_interface, virtual_address_action}, 10 | network, 11 | packet::{ARPframe, ArpPacket, EthernetFrame, VrrpPacket}, 12 | router::VirtualRouter, 13 | state_machine::{Event, States}, 14 | NetResult, 15 | }; 16 | 17 | /// Listens for when any Event occurs in the Virtual Router. 18 | /// Events that can occur are: Startup, Shutdown, MasterDown, Null 19 | /// Actions happening on when each of these Events is fired are 20 | /// Specified in RFC 3768 section 6.3, 6.4 and 6.5 21 | #[derive(Debug, Clone)] 22 | pub(crate) struct EventObserver; 23 | 24 | impl EventObserver { 25 | pub(crate) fn notify(vrouter: Arc>, event: Event) -> NetResult<()> { 26 | let vrouter = match vrouter.lock() { 27 | Ok(vrouter) => vrouter, 28 | Err(_) => return Err(NetError("Unable to fetch vrouter mutex".to_string())), 29 | }; 30 | EventObserver::notify_mut(vrouter, event)?; 31 | Ok(()) 32 | } 33 | 34 | pub(crate) fn notify_mut( 35 | mut vrouter: MutexGuard<'_, VirtualRouter>, 36 | event: Event, 37 | ) -> NetResult<()> { 38 | let interface = get_interface(&vrouter.network_interface)?; 39 | 40 | match event { 41 | Event::Startup => { 42 | if vrouter.fsm.state == States::Init { 43 | if vrouter.priority == 255 { 44 | // send VRRP advertisement. 45 | let mut addresses: Vec = vec![]; 46 | vrouter.ip_addresses.iter().for_each(|ip| { 47 | addresses.push(ip.addr()); 48 | }); 49 | 50 | let mut pkt = VrrpPacket { 51 | version: 2, 52 | hdr_type: 1, 53 | vrid: vrouter.vrid, 54 | priority: vrouter.priority, 55 | count_ip: vrouter.ip_addresses.len() as u8, 56 | auth_type: 0, 57 | adver_int: vrouter.advert_interval, 58 | checksum: 0, 59 | ip_addresses: addresses, 60 | auth_data: 0, 61 | auth_data2: 0, 62 | }; 63 | 64 | // confirm checksum. checksum position is the third item in 16 bit words 65 | pkt.checksum = checksum::calculate(&pkt.encode(), 3); 66 | 67 | let _ = network::send_vrrp_packet(&vrouter.network_interface, pkt); 68 | 69 | for ip in &vrouter.ip_addresses { 70 | let eth_frame = EthernetFrame { 71 | dst_mac: [0xff; 6], 72 | src_mac: interface.mac.unwrap().octets(), 73 | ethertype: 0x0806, 74 | }; 75 | let arp_pkt = ArpPacket { 76 | hw_type: 1, 77 | proto_type: 0x0800, 78 | hw_length: 6, 79 | proto_length: 4, 80 | operation: 1, 81 | sender_hw_address: interface.mac.unwrap().octets(), 82 | sender_proto_address: ip.addr().octets(), 83 | target_hw_address: [0xff; 6], 84 | target_proto_address: ip.addr().octets(), 85 | }; 86 | let arp_frame = ARPframe::new(eth_frame, arp_pkt); 87 | network::send_packet_arp(&interface.name, arp_frame); 88 | } 89 | 90 | // bring virtual IP back up. 91 | virtual_address_action( 92 | "add", 93 | &vrouter.str_ipv4_addresses(), 94 | &vrouter.network_interface, 95 | ); 96 | let advert_time = vrouter.advert_interval as f32; 97 | vrouter.fsm.set_advert_timer(advert_time); 98 | vrouter.fsm.state = States::Master; 99 | log::info!("({}) transitioned to MASTER (init)", vrouter.name); 100 | } else { 101 | // delete virtual IP. 102 | virtual_address_action( 103 | "delete", 104 | &vrouter.str_ipv4_addresses(), 105 | &vrouter.network_interface, 106 | ); 107 | let m_down_interval = vrouter.master_down_interval; 108 | vrouter.fsm.set_master_down_timer(m_down_interval); 109 | vrouter.fsm.state = States::Backup; 110 | log::info!("({}) transitioned to BACKUP (init)", vrouter.name); 111 | } 112 | } 113 | } 114 | Event::Shutdown => { 115 | match vrouter.fsm.state { 116 | States::Backup => { 117 | vrouter.fsm.disable_timer(); 118 | vrouter.fsm.state = States::Init; 119 | } 120 | States::Master => { 121 | vrouter.fsm.disable_timer(); 122 | // send ADVERTIEMENT 123 | /* 124 | // VRRP pakcet 125 | let mut vrrp_buff: Vec = vec![0; 16 + (4 * vrouter.ip_addresses.len())]; 126 | let mut vrrp_packet = generator.gen_vrrp_header(&mut vrrp_buff, &vrouter); 127 | vrrp_packet.set_checksum(checksum::one_complement_sum( 128 | vrrp_packet.packet(), 129 | Some(6), 130 | )); 131 | 132 | // IP packet 133 | let ip_len = vrrp_packet.packet().len() + 20; 134 | let mut ip_buff: Vec = vec![0; ip_len]; 135 | let mut ip_packet = generator.gen_vrrp_ip_header(&mut ip_buff); 136 | ip_packet.set_payload(vrrp_packet.packet()); 137 | 138 | // Ethernet packet 139 | let mut eth_buffer: Vec = vec![0; 14 + ip_packet.packet().len()]; 140 | let mut ether_packet = generator.gen_vrrp_eth_packet(&mut eth_buffer); 141 | ether_packet.set_payload(ip_packet.packet()); 142 | sender.send_to(ether_packet.packet(), None); 143 | */ 144 | let mut addresses: Vec = vec![]; 145 | vrouter.ip_addresses.iter().for_each(|ip| { 146 | addresses.push(ip.addr()); 147 | }); 148 | 149 | let mut pkt = VrrpPacket { 150 | version: 2, 151 | hdr_type: 1, 152 | vrid: vrouter.vrid, 153 | priority: vrouter.priority, 154 | count_ip: vrouter.ip_addresses.len() as u8, 155 | auth_type: 0, 156 | adver_int: vrouter.advert_interval, 157 | checksum: 0, 158 | ip_addresses: addresses, 159 | auth_data: 0, 160 | auth_data2: 0, 161 | }; 162 | // confirm checksum. checksum position is the third item in 16 bit words 163 | pkt.checksum = checksum::calculate(&pkt.encode(), 3); 164 | 165 | let _ = network::send_vrrp_packet(&vrouter.network_interface, pkt); 166 | vrouter.fsm.state = States::Init; 167 | } 168 | States::Init => {} 169 | } 170 | } 171 | Event::MasterDown => { 172 | if vrouter.fsm.state == States::Backup { 173 | // send ADVERTIEMENT then send gratuitous ARP 174 | 175 | // VRRP advertisement 176 | { 177 | let mut ips: Vec = vec![]; 178 | for addr in vrouter.ip_addresses.clone() { 179 | ips.push(addr.addr()); 180 | } 181 | let mut pkt = VrrpPacket { 182 | version: 2, 183 | hdr_type: 1, 184 | vrid: vrouter.vrid, 185 | priority: vrouter.priority, 186 | count_ip: vrouter.ip_addresses.len() as u8, 187 | checksum: 0, 188 | auth_type: 0, 189 | adver_int: vrouter.advert_interval, 190 | auth_data: 0, 191 | auth_data2: 0, 192 | ip_addresses: ips, 193 | }; 194 | // confirm checksum. checksum position is the third item in 16 bit words 195 | pkt.checksum = checksum::calculate(&pkt.encode(), 3); 196 | 197 | let _ = network::send_vrrp_packet(vrouter.network_interface.as_str(), pkt); 198 | } 199 | 200 | // gratuitous ARP 201 | { 202 | for ip in &vrouter.ip_addresses { 203 | let eth_frame = EthernetFrame { 204 | dst_mac: [0xff; 6], 205 | src_mac: interface.mac.unwrap().octets(), 206 | ethertype: 0x0806, 207 | }; 208 | let arp_pkt = ArpPacket { 209 | hw_type: 1, 210 | proto_type: 0x0800, 211 | hw_length: 6, 212 | proto_length: 4, 213 | operation: 1, 214 | sender_hw_address: interface.mac.unwrap().octets(), 215 | sender_proto_address: ip.addr().octets(), 216 | target_hw_address: [0xff; 6], 217 | target_proto_address: ip.addr().octets(), 218 | }; 219 | let arp_frame = ARPframe::new(eth_frame, arp_pkt); 220 | network::send_packet_arp(&interface.name, arp_frame); 221 | } 222 | } 223 | 224 | // add virtual IP address 225 | virtual_address_action( 226 | "add", 227 | &vrouter.str_ipv4_addresses(), 228 | &vrouter.network_interface, 229 | ); 230 | let advert_interval = vrouter.advert_interval as f32; 231 | vrouter.fsm.set_advert_timer(advert_interval); 232 | vrouter.fsm.state = States::Master; 233 | log::info!("({}) Transitioned to MASTER", vrouter.name); 234 | } 235 | } 236 | _ => {} 237 | } 238 | Ok(()) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::OptError, general::random_vr_name, OptResult}; 2 | use clap::Parser; 3 | use ipnet::Ipv4Net; 4 | use log::LevelFilter; 5 | use log4rs::{ 6 | append::{console::ConsoleAppender, file::FileAppender}, 7 | config::{Appender, Root}, 8 | Config, 9 | }; 10 | use serde::{Deserialize, Serialize}; 11 | use std::{ 12 | env, 13 | ffi::OsStr, 14 | fs::{create_dir_all, File}, 15 | io::{BufReader, Write}, 16 | path::Path, 17 | }; 18 | 19 | const DEFAULT_JSON_CONFIG: &[u8; 201] = b" 20 | { 21 | \"name\": \"VR_1\", 22 | \"vrid\": 51, 23 | \"interface_name\": \"wlo1\", 24 | \"ip_addresses\": [ 25 | \"192.168.100.100/24\" 26 | ], 27 | \"priority\": 101, 28 | \"advert_interval\": 1, 29 | \"preempt_mode\": true 30 | } 31 | "; 32 | 33 | fn default_priority() -> u8 { 34 | 100 35 | } 36 | fn default_advert_int() -> u8 { 37 | 1 38 | } 39 | fn default_preempt_mode() -> bool { 40 | true 41 | } 42 | fn default_action() -> Action { 43 | Action::Run 44 | } 45 | 46 | // for reading JSON config file 47 | #[derive(Debug, Serialize, Deserialize, Clone)] 48 | pub struct FileConfig { 49 | pub vrid: u8, 50 | pub ip_addresses: Vec, 51 | pub interface_name: String, 52 | 53 | #[serde(default = "random_vr_name")] 54 | pub name: String, 55 | #[serde(default = "default_priority")] 56 | pub priority: u8, 57 | #[serde(default = "default_advert_int")] 58 | pub advert_interval: u8, 59 | #[serde(default = "default_preempt_mode")] 60 | pub preempt_mode: bool, 61 | #[serde(default = "default_action")] 62 | pub action: Action, 63 | } 64 | 65 | #[derive(Debug, Clone)] 66 | pub struct CliConfig { 67 | pub name: Option, 68 | pub vrid: u8, 69 | pub ip_addresses: Vec, 70 | pub interface_name: String, 71 | 72 | pub priority: u8, 73 | pub advert_interval: u8, 74 | pub preempt_mode: bool, 75 | pub action: Action, 76 | } 77 | 78 | #[derive(Debug, Clone)] 79 | pub struct BaseConfig { 80 | pub name: Option, 81 | pub vrid: u8, 82 | pub ip_addresses: Vec, 83 | pub interface_name: String, 84 | pub priority: u8, 85 | pub advert_interval: u8, 86 | pub preempt_mode: bool, 87 | pub action: Action, 88 | } 89 | 90 | // pub struct 91 | 92 | #[derive(Debug, Clone)] 93 | pub enum VrrpConfig { 94 | File(FileConfig), 95 | Cli(CliConfig), 96 | } 97 | 98 | impl VrrpConfig { 99 | // for name, if not specified, we will generate a random name (VR-{random-string}) 100 | pub fn name(&self) -> String { 101 | let name = match self { 102 | VrrpConfig::File(config) => Some(config.name.clone()), 103 | VrrpConfig::Cli(config) => config.name.clone(), 104 | // VrrpConfig::Base(config) => config.name.unwrap() 105 | }; 106 | match name { 107 | Some(n) => n, 108 | None => random_vr_name(), 109 | } 110 | } 111 | 112 | pub fn vrid(&self) -> u8 { 113 | match self { 114 | VrrpConfig::File(config) => config.vrid, 115 | VrrpConfig::Cli(config) => config.vrid, 116 | } 117 | } 118 | 119 | pub fn ip_addresses(&self) -> Vec { 120 | match self { 121 | VrrpConfig::File(config) => config.ip_addresses.clone(), 122 | VrrpConfig::Cli(config) => config.ip_addresses.clone(), 123 | } 124 | } 125 | 126 | // if interface name has not been specified, we will create one with format: ( fover-{random-string} ) 127 | pub fn interface_name(&self) -> String { 128 | match self { 129 | VrrpConfig::File(config) => config.interface_name.clone(), 130 | VrrpConfig::Cli(config) => config.interface_name.clone(), 131 | } 132 | } 133 | 134 | pub fn priority(&self) -> u8 { 135 | match self { 136 | VrrpConfig::File(config) => config.priority, 137 | VrrpConfig::Cli(config) => config.priority, 138 | } 139 | } 140 | 141 | pub fn advert_interval(&self) -> u8 { 142 | match self { 143 | VrrpConfig::File(config) => config.advert_interval, 144 | VrrpConfig::Cli(config) => config.advert_interval, 145 | } 146 | } 147 | 148 | pub fn preempt_mode(&self) -> bool { 149 | match self { 150 | VrrpConfig::File(config) => config.preempt_mode, 151 | VrrpConfig::Cli(config) => config.preempt_mode, 152 | } 153 | } 154 | 155 | pub fn action(&self) -> Action { 156 | match self { 157 | VrrpConfig::File(config) => config.action.clone(), 158 | VrrpConfig::Cli(config) => config.action.clone(), 159 | } 160 | } 161 | } 162 | 163 | #[derive(Parser, Debug)] 164 | #[command(name = "Version")] 165 | #[command(about = "Runs the VRRP protocol", long_about = None)] 166 | pub struct CliArgs2 { 167 | #[command( 168 | subcommand, 169 | help = "`run` for starting the VRRP instances and `teardown` for stopping them" 170 | )] 171 | cfg: Cfg, 172 | } 173 | 174 | #[derive(Parser, Debug)] 175 | enum Cfg { 176 | Run { 177 | #[command(subcommand)] 178 | mode: Mode, 179 | }, 180 | Teardown { 181 | #[command(subcommand)] 182 | mode: Mode, 183 | }, 184 | } 185 | 186 | #[derive(Parser, Debug)] 187 | enum Mode { 188 | FileMode { 189 | #[arg(long, help = "path to the we will get our configs from")] 190 | filename: Option, 191 | 192 | #[arg( 193 | long, 194 | default_value = None, 195 | help = "Path log file you want to use" 196 | )] 197 | log_file_path: Option, 198 | }, 199 | CliMode { 200 | #[arg(long, help = "The name of the Virtual Router Instance. e.g `VR_1`")] 201 | name: Option, 202 | 203 | #[arg(long, help = "Virtual Router ID of the Virtual router instance. ")] 204 | vrid: u8, 205 | 206 | #[arg(long, num_args=1.., help="The IP Address(es) of that will the Virtual router will be assigned. Can be more than one. ")] 207 | ip_address: Vec, 208 | 209 | #[arg( 210 | long, 211 | help = "name of the network interface where the Virtual Router instance will be attached. " 212 | )] 213 | interface_name: String, 214 | 215 | #[arg( 216 | long, 217 | default_value = "100", 218 | help = "The priority of this instance of the Virtual Router, maximum of 255. The higher priority is chosen to be MASTER." 219 | )] 220 | priority: u8, 221 | 222 | #[arg( 223 | long, 224 | default_value = "1", 225 | help = "Interval(in seconds) between which the priodic advert updates are sent (when MASTER). Also used to calculate MasterDown interval when in BACKUP state." 226 | )] 227 | advert_interval: u8, 228 | 229 | #[arg( 230 | long, 231 | action, 232 | help = "(highly advised to be true). When true, the higher priority will always preempt the lower priority." 233 | )] 234 | preempt_mode: bool, 235 | 236 | #[arg( 237 | long, 238 | default_value = None, 239 | help = "Path log file you want to use" 240 | )] 241 | log_file_path: Option, 242 | }, 243 | } 244 | 245 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 246 | pub enum Action { 247 | Run, 248 | Teardown, 249 | } 250 | 251 | pub fn parse_cli_opts(args: CliArgs2) -> OptResult> { 252 | match args.cfg { 253 | Cfg::Run { mode } => load_mode(mode, Action::Run), 254 | Cfg::Teardown { mode } => load_mode(mode, Action::Teardown), 255 | } 256 | } 257 | 258 | fn load_mode(mode: Mode, action: Action) -> OptResult> { 259 | match mode { 260 | Mode::FileMode { 261 | filename, 262 | log_file_path, 263 | } => { 264 | configure_logging(log_file_path); 265 | // generate file path if none is given 266 | let fpath = match filename { 267 | None => { 268 | // get default file path and create new directory if it does not exist 269 | match env::var("SNAP_COMMON") { 270 | Ok(path) => path + "/vrrp-config.json", 271 | Err(_) => "/etc/failover/vrrp-config.json".to_string(), 272 | } 273 | } 274 | Some(f) => f, 275 | }; 276 | 277 | log::info!("using config file {:#?}", fpath); 278 | // create the config file (if it does not exist) 279 | if !Path::new(&fpath).exists() { 280 | let mut file = match File::create(&fpath) { 281 | Ok(f) => f, 282 | Err(_) => { 283 | let dir_path = match std::path::Path::new(&fpath).parent() { 284 | Some(dir) => dir, 285 | None => { 286 | return Err(OptError(String::from("unable to find config path"))) 287 | } 288 | }; 289 | let _ = create_dir_all(dir_path); 290 | File::create(&fpath).unwrap() 291 | } 292 | }; 293 | let _ = file.write_all(DEFAULT_JSON_CONFIG); 294 | } 295 | 296 | let mut configs: Vec = vec![]; 297 | match read_json_config(&fpath) { 298 | Ok(vec_config) => { 299 | for mut config_item in vec_config { 300 | config_item.action = action.clone(); 301 | configs.push(VrrpConfig::File(config_item)); 302 | } 303 | } 304 | Err(_) => return Result::Err(OptError(format!("Problem parsing file {}", &fpath))), 305 | } 306 | Ok(configs) 307 | } 308 | Mode::CliMode { 309 | mut name, 310 | vrid, 311 | ip_address, 312 | interface_name, 313 | priority, 314 | advert_interval, 315 | preempt_mode, 316 | log_file_path, 317 | } => { 318 | configure_logging(log_file_path); 319 | if name.is_none() { 320 | name = Some(random_vr_name()); 321 | } 322 | 323 | let config = CliConfig { 324 | name, 325 | vrid, 326 | ip_addresses: ip_address, 327 | interface_name, 328 | priority, 329 | advert_interval, 330 | preempt_mode, 331 | action, 332 | }; 333 | Ok(vec![VrrpConfig::Cli(config)]) 334 | } 335 | } 336 | } 337 | 338 | fn configure_logging(log_file_path: Option) { 339 | let log_console_stderr = ConsoleAppender::builder().build(); 340 | let mut log_builder = Config::builder() 341 | .appender(Appender::builder().build("stderr", Box::new(log_console_stderr))); 342 | let mut root_builder = Root::builder(); 343 | 344 | // set file path logging 345 | if let Some(file_path) = log_file_path { 346 | // Logging to log file. 347 | let log_file = FileAppender::builder() 348 | // Pattern: https://docs.rs/log4rs/*/log4rs/encode/pattern/index.html 349 | //.encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) 350 | .build(file_path) 351 | .unwrap(); 352 | log_builder = 353 | log_builder.appender(Appender::builder().build("logfile", Box::new(log_file))); 354 | root_builder = root_builder.appender("logfile"); 355 | } 356 | root_builder = root_builder.appender("stderr"); // .appender("stdout"); 357 | //.build(LevelFilter::Trace); 358 | 359 | let log_config = log_builder 360 | .build(root_builder.build(LevelFilter::Trace)) 361 | .unwrap(); 362 | let _handler = log4rs::init_config(log_config); 363 | } 364 | 365 | fn read_json_config>(path: P) -> OptResult> { 366 | let path_str = path.as_ref().as_os_str(); 367 | 368 | //log::info!("Reading from config file {:?}", path_str); 369 | let file = match File::open(path_str) { 370 | Ok(f) => f, 371 | Err(_) => { 372 | //log::error!("Unable to open file {:?}", path.as_ref().as_os_str()); 373 | return Err(OptError(format!( 374 | "unable to open file `{:?}`", 375 | path.as_ref().as_os_str() 376 | ))); 377 | } 378 | }; 379 | 380 | let reader = BufReader::new(file); 381 | let mut result: Vec = Vec::new(); 382 | 383 | let list_file_configs: Vec = match serde_json::from_reader(reader) { 384 | Ok(config) => config, 385 | Err(_) => match single_file_config(path_str) { 386 | Ok(conf) => conf, 387 | Err(err) => return Err(err), 388 | }, 389 | }; 390 | 391 | for file_config in list_file_configs { 392 | // check if the name of Virtual Router being entered is unique 393 | if let Some(_con) = result 394 | .iter() 395 | .find(|r: &&FileConfig| r.name == file_config.name) 396 | { 397 | //log::warn!("Configs for Virtual Router with name {:?} already exist. Will be ignored", con.name); 398 | continue; 399 | }; 400 | 401 | // check if VRID of the Virtual Router being entered is unique 402 | if let Some(_con) = result 403 | .iter() 404 | .find(|r: &&FileConfig| r.vrid == file_config.vrid) 405 | { 406 | //log::warn!("Configs for Virtual Router with VRID {:?} already exist. Will be ignored", con.vrid); 407 | continue; 408 | }; 409 | 410 | result.push(file_config); 411 | } 412 | 413 | Ok(result) 414 | } 415 | 416 | fn single_file_config(path: &OsStr) -> OptResult> { 417 | // this single file config method is called only after 418 | // the normal config fails, which it does after reading the file. 419 | // thus unwrap()'ing here is safe. 420 | let file = File::open(path).unwrap(); 421 | 422 | let reader = BufReader::new(file); 423 | let _: FileConfig = match serde_json::from_reader(reader) { 424 | Ok(config) => return Ok(vec![config]), 425 | Err(_) => { 426 | //log::error!("Wrong configurations for file {:?}", path); 427 | return Err(OptError(format!( 428 | "Wrong config formatting in file {:?}", 429 | path 430 | ))); 431 | } 432 | }; 433 | } 434 | -------------------------------------------------------------------------------- /src/pkt/handlers.rs: -------------------------------------------------------------------------------- 1 | use crate::network; 2 | use crate::packet::{ARPframe, ArpPacket, EthernetFrame, VrrpPacket}; 3 | use crate::{ 4 | error::NetError, general::virtual_address_action, observer::EventObserver, 5 | state_machine::Event, NetResult, 6 | }; 7 | use crate::{general::get_interface, router::VirtualRouter, state_machine::States}; 8 | 9 | /// Defines how each different type of packet should be handled. 10 | /// Depending on the current state of the machine. 11 | /// The two main packets being anticipated are: 12 | /// - VRRP packets 13 | /// - ARP packets 14 | /// 15 | /// The actions on each of the above are specified in section 16 | /// 6 of RFC 3768. 17 | /// 18 | use core::f32; 19 | use ipnet::Ipv4Net; 20 | use pnet::{ 21 | datalink, 22 | packet::{ethernet::EthernetPacket, ipv4::Ipv4Packet, Packet}, 23 | }; 24 | use std::{ 25 | net::Ipv4Addr, 26 | sync::{Arc, Mutex}, 27 | }; 28 | //use vrrp_packet::VrrpPacket; 29 | 30 | pub(crate) fn handle_incoming_arp_pkt( 31 | eth_packet: &EthernetPacket<'_>, 32 | vrouter: Arc>, 33 | ) -> NetResult<()> { 34 | let vrouter = match vrouter.lock() { 35 | Ok(vr) => vr, 36 | Err(err) => { 37 | log::error!("Unable to create mutex lock for vrouter"); 38 | return Err(NetError(format!( 39 | "Unable to create mutex lock for vrouter\n\n {err}" 40 | ))); 41 | } 42 | }; 43 | let interface = get_interface(&vrouter.network_interface)?; 44 | let arp_packet = match ArpPacket::decode(eth_packet.payload()) { 45 | Some(arp_packet) => arp_packet, 46 | None => return Ok(()), 47 | }; 48 | 49 | let interface_mac = match interface.clone().mac { 50 | Some(mac) => mac, 51 | None => { 52 | log::warn!("interface {} does not have mac address. Unable to continue with incoming VRRP packet checks", &interface.name); 53 | return Ok(()); 54 | } 55 | }; 56 | 57 | match vrouter.fsm.state { 58 | States::Init => {} 59 | States::Backup => { 60 | // MUST NOT respond to ARP requests for the IP address(s) associated 61 | // with the virtual router. 62 | for ip in &vrouter.ip_addresses { 63 | if ip.addr().octets() == arp_packet.target_proto_address { 64 | return Ok(()); 65 | } 66 | } 67 | 68 | // !TODO 69 | // MUST discard packets with a destination link layer MAC address 70 | // equal to the virtual router MAC address. 71 | if arp_packet.target_hw_address == interface_mac.octets() { 72 | return Ok(()); 73 | } 74 | } 75 | 76 | States::Master => { 77 | // MUST respond to ARP requests for the IP address(es) associated 78 | // with the virtual router. 79 | for ip in &vrouter.ip_addresses { 80 | if ip.addr().octets() == arp_packet.target_proto_address { 81 | let eth_frame = EthernetFrame { 82 | dst_mac: eth_packet.get_source().octets(), 83 | src_mac: interface_mac.octets(), 84 | ethertype: 0x806, 85 | }; 86 | 87 | let arp_packet = ArpPacket { 88 | hw_type: 1, 89 | proto_type: 0x0800, 90 | hw_length: 6, 91 | proto_length: 4, 92 | operation: 2, 93 | sender_hw_address: interface_mac.octets(), 94 | sender_proto_address: arp_packet.target_proto_address, 95 | target_hw_address: arp_packet.sender_hw_address, 96 | target_proto_address: arp_packet.sender_proto_address, 97 | }; 98 | 99 | let arp_frame = ARPframe::new(eth_frame, arp_packet); 100 | network::send_packet_arp(interface.name.as_str(), arp_frame); 101 | } 102 | } 103 | } 104 | } 105 | 106 | Ok(()) 107 | } 108 | 109 | pub(crate) fn handle_incoming_vrrp_pkt( 110 | eth_packet: &EthernetPacket<'_>, 111 | vrouter_mutex: Arc>, 112 | ) -> NetResult<()> { 113 | let mut vrouter = match vrouter_mutex.lock() { 114 | Ok(vr) => vr, 115 | Err(err) => { 116 | log::warn!("problem fetching vrouter mutex"); 117 | log::warn!("{err}"); 118 | return Ok(()); 119 | } 120 | }; 121 | let ip_packet = match Ipv4Packet::new(eth_packet.payload()) { 122 | Some(pkt) => pkt, 123 | None => { 124 | log::warn!("Unable to read incoming IP packet"); 125 | return Ok(()); 126 | } 127 | }; 128 | 129 | let vrrp_packet = match VrrpPacket::decode(ip_packet.payload()) { 130 | Some(pkt) => pkt, 131 | None => { 132 | log::warn!("Unable to read incoming VRRP packet"); 133 | return Ok(()); 134 | } 135 | }; 136 | 137 | let mut error; 138 | 139 | // TODO { 140 | // - currently we are looking at the first IP address of the interface that is sending the data. 141 | // - this should be changed to looking through all the IP addresses in the device. 142 | // } 143 | // received packets from the same device 144 | for interface in datalink::interfaces().iter() { 145 | if let Some(ip) = interface.ips.first() { 146 | if ip.ip() == ip_packet.get_source() { 147 | return Ok(()); 148 | } 149 | }; 150 | } 151 | 152 | // MUST DO verifications(rfc3768 section 7.1) 153 | { 154 | // 1. Verify IP TTL is 255 155 | if ip_packet.get_ttl() != 255 { 156 | error = format!("({}) TTL of incoming VRRP packet != 255", vrouter.name); 157 | log::warn!("{error}"); 158 | return Result::Err(NetError(error)); 159 | } 160 | 161 | // 2. MUST verify the VRRP version is 2. 162 | if vrrp_packet.version != 2 { 163 | error = format!("({}) Incoming VRRP packet Version != 2", vrouter.name); 164 | log::error!("{error}"); 165 | return Result::Err(NetError(error)); 166 | } 167 | 168 | // 3. MUST verify that the received packet contains the complete VRRP 169 | // packet (including fixed fields, IP Address(es), and Authentication 170 | // Data) 171 | 172 | // 4. MUST verify the VRRP checksum. 173 | // rfc1071() function should return value with all 1's 174 | 175 | // 5. MUST verify that the VRID is configured on the receiving interface 176 | // and the local router is not the IP Address owner (Priority equals 177 | // 255 (decimal)). 178 | // TODO Once implemented multiple interfaces 179 | if vrrp_packet.vrid != vrouter.vrid { 180 | return Ok(()); 181 | } 182 | 183 | // 6. Auth Type must be same. 184 | // TODO once multiple authentication types are configured 185 | 186 | // 7. MUST verify that the Adver Interval in the packet is the same as 187 | // the locally configured for this virtual router 188 | // If the above check fails, the receiver MUST discard the packet, 189 | // SHOULD log the event and MAY indicate via network management that a 190 | // misconfiguration was detected. 191 | if vrrp_packet.adver_int != vrouter.advert_interval { 192 | error = format!( 193 | "({}) Incoming VRRP packet has advert interval {} while configured advert interval is {}", 194 | vrouter.name, vrrp_packet.adver_int, vrouter.advert_interval); 195 | 196 | log::error!("{error}"); 197 | return Result::Err(NetError(error)); 198 | } 199 | } 200 | 201 | // MAY DO verifications (rfc3768 section 7.1) 202 | { 203 | // 1. MAY verify that "Count IP Addrs" and the list of IP Address 204 | // matches the IP_Addresses configured for the VRID 205 | // 206 | // If the packet was not generated by the address owner (Priority does 207 | // not equal 255 (decimal)), the receiver MUST drop the packet, 208 | // otherwise continue processing. 209 | let count_check = vrrp_packet.count_ip == vrouter.ip_addresses.len() as u8; 210 | let mut addr_check = true; 211 | 212 | if vrrp_packet.ip_addresses.clone().len() % 4 != 0 { 213 | error = format!("({}) Invalid Ip Addresses in vrrp packet", vrouter.name); 214 | log::error!("{error}"); 215 | return Result::Err(NetError(error)); 216 | } 217 | 218 | let mut addr: Vec = vec![]; 219 | for (counter, ip_ad) in vrrp_packet.ip_addresses.iter().enumerate() { 220 | //addr.push(ip_ad.octets()); 221 | ip_ad.octets().iter().for_each(|oc| { 222 | addr.push(*oc); 223 | }); 224 | if (counter + 1) % 4 == 0 { 225 | let ip = match Ipv4Net::new(Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]), 24) { 226 | Ok(ip) => ip.addr(), 227 | Err(err) => { 228 | log::error!("Invalid IP on incoming VRRP packet: {:?}", addr); 229 | log::error!("{err}"); 230 | return Ok(()); 231 | } 232 | }; 233 | if !vrouter.ipv4_addresses().contains(&ip) { 234 | log::error!( 235 | "({}) IP address {:?} for incoming VRRP packet not found in local config", 236 | vrouter.name, 237 | ip 238 | ); 239 | addr_check = false; 240 | } 241 | } 242 | } 243 | 244 | if !count_check { 245 | error = 246 | format!( 247 | "({}) ip count check({}) does not match with local configuration of ip count {}", 248 | vrouter.name, vrrp_packet.count_ip, vrouter.ip_addresses.len() 249 | ); 250 | log::error!("{error}"); 251 | if vrrp_packet.priority != 255 { 252 | return Result::Err(NetError(error)); 253 | } 254 | } 255 | 256 | if !addr_check && vrrp_packet.priority != 255 { 257 | error = format!( 258 | "({}) IP addresses for incoming vrrp don't match ", 259 | vrouter.name 260 | ); 261 | log::error!("{error}"); 262 | if vrrp_packet.priority != 255 { 263 | return Result::Err(NetError(error)); 264 | } 265 | } 266 | } 267 | 268 | match vrouter.fsm.state { 269 | States::Backup => { 270 | if vrrp_packet.priority == 0 { 271 | let skew_time = vrouter.skew_time; 272 | vrouter.fsm.set_master_down_timer(skew_time); 273 | } else if !vrouter.preempt_mode || vrrp_packet.priority >= vrouter.priority { 274 | let m_down_interval = vrouter.master_down_interval; 275 | vrouter.fsm.set_master_down_timer(m_down_interval); 276 | } else if vrouter.priority > vrrp_packet.priority { 277 | virtual_address_action( 278 | "add", 279 | &vrouter.str_ipv4_addresses(), 280 | &vrouter.network_interface, 281 | ); 282 | vrouter.fsm.state = States::Master; 283 | let advert_interval = vrouter.advert_interval as f32; 284 | vrouter.fsm.set_advert_timer(advert_interval); 285 | log::info!("({}) transitioned to MASTER", vrouter.name); 286 | } 287 | Ok(()) 288 | } 289 | 290 | States::Master => { 291 | let incoming_ip_pkt = match Ipv4Packet::new(eth_packet.payload()) { 292 | Some(pkt) => pkt, 293 | None => { 294 | let err = "Problem processing incoming IP packet"; 295 | log::warn!("{err}"); 296 | return Err(NetError(err.to_string())); 297 | } 298 | }; 299 | let adv_priority_gt_local_priority = vrrp_packet.priority > vrouter.priority; 300 | let adv_priority_eq_local_priority = vrrp_packet.priority == vrouter.priority; 301 | let _send_ip_gt_local_ip = 302 | incoming_ip_pkt.get_source() > incoming_ip_pkt.get_destination(); 303 | 304 | // If an ADVERTISEMENT is received, then 305 | if vrrp_packet.priority == 0 { 306 | // send ADVERTISEMENT 307 | let mut ips: Vec = vec![]; 308 | for addr in vrouter.ip_addresses.clone() { 309 | ips.push(addr.addr()); 310 | } 311 | 312 | let pkt = VrrpPacket { 313 | version: 2, 314 | hdr_type: 1, 315 | vrid: vrouter.vrid, 316 | priority: vrouter.priority, 317 | count_ip: vrouter.ip_addresses.len() as u8, 318 | auth_type: 0, 319 | adver_int: vrouter.advert_interval, 320 | checksum: 0, 321 | ip_addresses: ips, 322 | auth_data: 0, 323 | auth_data2: 0, 324 | }; 325 | let _ = network::send_vrrp_packet(&vrouter.network_interface, pkt); 326 | let advert_interval = vrouter.advert_interval as f32; 327 | vrouter.fsm.set_advert_timer(advert_interval); 328 | 329 | Ok(()) 330 | } else if adv_priority_gt_local_priority { 331 | // delete virtual IP address 332 | virtual_address_action( 333 | "delete", 334 | &vrouter.str_ipv4_addresses(), 335 | &vrouter.network_interface, 336 | ); 337 | let m_down_interval = vrouter.master_down_interval; 338 | vrouter.fsm.set_master_down_timer(m_down_interval); 339 | vrouter.fsm.state = States::Backup; 340 | log::info!("({}) transitioned to BACKUP", vrouter.name); 341 | EventObserver::notify_mut(vrouter, Event::Null)?; 342 | Ok(()) 343 | } else if adv_priority_eq_local_priority { 344 | // delete virtual IP address 345 | virtual_address_action( 346 | "delete", 347 | &vrouter.str_ipv4_addresses(), 348 | &vrouter.network_interface, 349 | ); 350 | let m_down_interval = vrouter.master_down_interval; 351 | vrouter.fsm.set_master_down_timer(m_down_interval); 352 | vrouter.fsm.state = States::Backup; 353 | vrouter.fsm.event = Event::Null; 354 | log::info!("({}) transitioned to BACKUP", vrouter.name); 355 | EventObserver::notify_mut(vrouter, Event::Null)?; 356 | Ok(()) 357 | } else { 358 | Ok(()) 359 | } 360 | } 361 | _ => Ok(()), 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "anstream" 46 | version = "0.6.13" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" 49 | dependencies = [ 50 | "anstyle", 51 | "anstyle-parse", 52 | "anstyle-query", 53 | "anstyle-wincon", 54 | "colorchoice", 55 | "utf8parse", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle" 60 | version = "1.0.6" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 63 | 64 | [[package]] 65 | name = "anstyle-parse" 66 | version = "0.2.3" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 69 | dependencies = [ 70 | "utf8parse", 71 | ] 72 | 73 | [[package]] 74 | name = "anstyle-query" 75 | version = "1.0.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 78 | dependencies = [ 79 | "windows-sys 0.52.0", 80 | ] 81 | 82 | [[package]] 83 | name = "anstyle-wincon" 84 | version = "3.0.2" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 87 | dependencies = [ 88 | "anstyle", 89 | "windows-sys 0.52.0", 90 | ] 91 | 92 | [[package]] 93 | name = "anyhow" 94 | version = "1.0.91" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" 97 | 98 | [[package]] 99 | name = "arc-swap" 100 | version = "1.7.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 103 | 104 | [[package]] 105 | name = "autocfg" 106 | version = "1.2.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 109 | 110 | [[package]] 111 | name = "backtrace" 112 | version = "0.3.71" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 115 | dependencies = [ 116 | "addr2line", 117 | "cc", 118 | "cfg-if", 119 | "libc", 120 | "miniz_oxide", 121 | "object", 122 | "rustc-demangle", 123 | ] 124 | 125 | [[package]] 126 | name = "bitflags" 127 | version = "1.3.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 130 | 131 | [[package]] 132 | name = "bumpalo" 133 | version = "3.16.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 136 | 137 | [[package]] 138 | name = "byteorder" 139 | version = "1.5.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 142 | 143 | [[package]] 144 | name = "bytes" 145 | version = "1.7.1" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" 148 | 149 | [[package]] 150 | name = "cc" 151 | version = "1.0.90" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 154 | 155 | [[package]] 156 | name = "cfg-if" 157 | version = "1.0.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 160 | 161 | [[package]] 162 | name = "chrono" 163 | version = "0.4.38" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 166 | dependencies = [ 167 | "android-tzdata", 168 | "iana-time-zone", 169 | "num-traits", 170 | "windows-targets 0.52.5", 171 | ] 172 | 173 | [[package]] 174 | name = "clap" 175 | version = "4.5.4" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 178 | dependencies = [ 179 | "clap_builder", 180 | "clap_derive", 181 | ] 182 | 183 | [[package]] 184 | name = "clap_builder" 185 | version = "4.5.2" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 188 | dependencies = [ 189 | "anstream", 190 | "anstyle", 191 | "clap_lex", 192 | "strsim", 193 | ] 194 | 195 | [[package]] 196 | name = "clap_derive" 197 | version = "4.5.4" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 200 | dependencies = [ 201 | "heck", 202 | "proc-macro2", 203 | "quote", 204 | "syn 2.0.53", 205 | ] 206 | 207 | [[package]] 208 | name = "clap_lex" 209 | version = "0.7.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 212 | 213 | [[package]] 214 | name = "colorchoice" 215 | version = "1.0.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 218 | 219 | [[package]] 220 | name = "core-foundation-sys" 221 | version = "0.8.7" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 224 | 225 | [[package]] 226 | name = "derivative" 227 | version = "2.2.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 230 | dependencies = [ 231 | "proc-macro2", 232 | "quote", 233 | "syn 1.0.109", 234 | ] 235 | 236 | [[package]] 237 | name = "destructure_traitobject" 238 | version = "0.2.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" 241 | 242 | [[package]] 243 | name = "equivalent" 244 | version = "1.0.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 247 | 248 | [[package]] 249 | name = "failover_vr" 250 | version = "0.4.2" 251 | dependencies = [ 252 | "byteorder", 253 | "bytes", 254 | "clap", 255 | "ipnet", 256 | "libc", 257 | "log", 258 | "log4rs", 259 | "pnet", 260 | "rand", 261 | "serde", 262 | "serde_json", 263 | "socket2", 264 | "tokio", 265 | ] 266 | 267 | [[package]] 268 | name = "fnv" 269 | version = "1.0.7" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 272 | 273 | [[package]] 274 | name = "getrandom" 275 | version = "0.2.12" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 278 | dependencies = [ 279 | "cfg-if", 280 | "libc", 281 | "wasi", 282 | ] 283 | 284 | [[package]] 285 | name = "gimli" 286 | version = "0.28.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 289 | 290 | [[package]] 291 | name = "glob" 292 | version = "0.3.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 295 | 296 | [[package]] 297 | name = "hashbrown" 298 | version = "0.15.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 301 | 302 | [[package]] 303 | name = "heck" 304 | version = "0.5.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 307 | 308 | [[package]] 309 | name = "hermit-abi" 310 | version = "0.3.9" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 313 | 314 | [[package]] 315 | name = "humantime" 316 | version = "2.1.0" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 319 | 320 | [[package]] 321 | name = "iana-time-zone" 322 | version = "0.1.61" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 325 | dependencies = [ 326 | "android_system_properties", 327 | "core-foundation-sys", 328 | "iana-time-zone-haiku", 329 | "js-sys", 330 | "wasm-bindgen", 331 | "windows-core", 332 | ] 333 | 334 | [[package]] 335 | name = "iana-time-zone-haiku" 336 | version = "0.1.2" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 339 | dependencies = [ 340 | "cc", 341 | ] 342 | 343 | [[package]] 344 | name = "indexmap" 345 | version = "2.6.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 348 | dependencies = [ 349 | "equivalent", 350 | "hashbrown", 351 | ] 352 | 353 | [[package]] 354 | name = "ipnet" 355 | version = "2.9.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 358 | dependencies = [ 359 | "serde", 360 | ] 361 | 362 | [[package]] 363 | name = "ipnetwork" 364 | version = "0.20.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" 367 | dependencies = [ 368 | "serde", 369 | ] 370 | 371 | [[package]] 372 | name = "itoa" 373 | version = "1.0.10" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 376 | 377 | [[package]] 378 | name = "js-sys" 379 | version = "0.3.72" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 382 | dependencies = [ 383 | "wasm-bindgen", 384 | ] 385 | 386 | [[package]] 387 | name = "libc" 388 | version = "0.2.153" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 391 | 392 | [[package]] 393 | name = "lock_api" 394 | version = "0.4.11" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 397 | dependencies = [ 398 | "autocfg", 399 | "scopeguard", 400 | ] 401 | 402 | [[package]] 403 | name = "log" 404 | version = "0.4.21" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 407 | dependencies = [ 408 | "serde", 409 | ] 410 | 411 | [[package]] 412 | name = "log-mdc" 413 | version = "0.1.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" 416 | 417 | [[package]] 418 | name = "log4rs" 419 | version = "1.3.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6" 422 | dependencies = [ 423 | "anyhow", 424 | "arc-swap", 425 | "chrono", 426 | "derivative", 427 | "fnv", 428 | "humantime", 429 | "libc", 430 | "log", 431 | "log-mdc", 432 | "once_cell", 433 | "parking_lot", 434 | "rand", 435 | "serde", 436 | "serde-value", 437 | "serde_json", 438 | "serde_yaml", 439 | "thiserror", 440 | "thread-id", 441 | "typemap-ors", 442 | "winapi", 443 | ] 444 | 445 | [[package]] 446 | name = "memchr" 447 | version = "2.7.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 450 | 451 | [[package]] 452 | name = "miniz_oxide" 453 | version = "0.7.2" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 456 | dependencies = [ 457 | "adler", 458 | ] 459 | 460 | [[package]] 461 | name = "mio" 462 | version = "0.8.11" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 465 | dependencies = [ 466 | "libc", 467 | "wasi", 468 | "windows-sys 0.48.0", 469 | ] 470 | 471 | [[package]] 472 | name = "no-std-net" 473 | version = "0.6.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" 476 | 477 | [[package]] 478 | name = "num-traits" 479 | version = "0.2.19" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 482 | dependencies = [ 483 | "autocfg", 484 | ] 485 | 486 | [[package]] 487 | name = "num_cpus" 488 | version = "1.16.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 491 | dependencies = [ 492 | "hermit-abi", 493 | "libc", 494 | ] 495 | 496 | [[package]] 497 | name = "object" 498 | version = "0.32.2" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 501 | dependencies = [ 502 | "memchr", 503 | ] 504 | 505 | [[package]] 506 | name = "once_cell" 507 | version = "1.20.2" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 510 | 511 | [[package]] 512 | name = "ordered-float" 513 | version = "2.10.1" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" 516 | dependencies = [ 517 | "num-traits", 518 | ] 519 | 520 | [[package]] 521 | name = "parking_lot" 522 | version = "0.12.1" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 525 | dependencies = [ 526 | "lock_api", 527 | "parking_lot_core", 528 | ] 529 | 530 | [[package]] 531 | name = "parking_lot_core" 532 | version = "0.9.9" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 535 | dependencies = [ 536 | "cfg-if", 537 | "libc", 538 | "redox_syscall", 539 | "smallvec", 540 | "windows-targets 0.48.5", 541 | ] 542 | 543 | [[package]] 544 | name = "pin-project-lite" 545 | version = "0.2.14" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 548 | 549 | [[package]] 550 | name = "pnet" 551 | version = "0.33.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "cd959a8268165518e2bf5546ba84c7b3222744435616381df3c456fe8d983576" 554 | dependencies = [ 555 | "ipnetwork", 556 | "pnet_base", 557 | "pnet_datalink", 558 | "pnet_packet", 559 | "pnet_sys", 560 | "pnet_transport", 561 | ] 562 | 563 | [[package]] 564 | name = "pnet_base" 565 | version = "0.33.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "872e46346144ebf35219ccaa64b1dffacd9c6f188cd7d012bd6977a2a838f42e" 568 | dependencies = [ 569 | "no-std-net", 570 | ] 571 | 572 | [[package]] 573 | name = "pnet_datalink" 574 | version = "0.33.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "c302da22118d2793c312a35fb3da6846cb0fab6c3ad53fd67e37809b06cdafce" 577 | dependencies = [ 578 | "ipnetwork", 579 | "libc", 580 | "pnet_base", 581 | "pnet_sys", 582 | "winapi", 583 | ] 584 | 585 | [[package]] 586 | name = "pnet_macros" 587 | version = "0.33.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "2a780e80005c2e463ec25a6e9f928630049a10b43945fea83207207d4a7606f4" 590 | dependencies = [ 591 | "proc-macro2", 592 | "quote", 593 | "regex", 594 | "syn 1.0.109", 595 | ] 596 | 597 | [[package]] 598 | name = "pnet_macros_support" 599 | version = "0.33.0" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "e6d932134f32efd7834eb8b16d42418dac87086347d1bc7d142370ef078582bc" 602 | dependencies = [ 603 | "pnet_base", 604 | ] 605 | 606 | [[package]] 607 | name = "pnet_packet" 608 | version = "0.33.0" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "8bde678bbd85cb1c2d99dc9fc596e57f03aa725f84f3168b0eaf33eeccb41706" 611 | dependencies = [ 612 | "glob", 613 | "pnet_base", 614 | "pnet_macros", 615 | "pnet_macros_support", 616 | ] 617 | 618 | [[package]] 619 | name = "pnet_sys" 620 | version = "0.33.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "faf7a58b2803d818a374be9278a1fe8f88fce14b936afbe225000cfcd9c73f16" 623 | dependencies = [ 624 | "libc", 625 | "winapi", 626 | ] 627 | 628 | [[package]] 629 | name = "pnet_transport" 630 | version = "0.33.0" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "813d1c0e4defbe7ee22f6fe1755f122b77bfb5abe77145b1b5baaf463cab9249" 633 | dependencies = [ 634 | "libc", 635 | "pnet_base", 636 | "pnet_packet", 637 | "pnet_sys", 638 | ] 639 | 640 | [[package]] 641 | name = "ppv-lite86" 642 | version = "0.2.17" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 645 | 646 | [[package]] 647 | name = "proc-macro2" 648 | version = "1.0.79" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 651 | dependencies = [ 652 | "unicode-ident", 653 | ] 654 | 655 | [[package]] 656 | name = "quote" 657 | version = "1.0.35" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 660 | dependencies = [ 661 | "proc-macro2", 662 | ] 663 | 664 | [[package]] 665 | name = "rand" 666 | version = "0.8.5" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 669 | dependencies = [ 670 | "libc", 671 | "rand_chacha", 672 | "rand_core", 673 | ] 674 | 675 | [[package]] 676 | name = "rand_chacha" 677 | version = "0.3.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 680 | dependencies = [ 681 | "ppv-lite86", 682 | "rand_core", 683 | ] 684 | 685 | [[package]] 686 | name = "rand_core" 687 | version = "0.6.4" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 690 | dependencies = [ 691 | "getrandom", 692 | ] 693 | 694 | [[package]] 695 | name = "redox_syscall" 696 | version = "0.4.1" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 699 | dependencies = [ 700 | "bitflags", 701 | ] 702 | 703 | [[package]] 704 | name = "regex" 705 | version = "1.10.3" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 708 | dependencies = [ 709 | "aho-corasick", 710 | "memchr", 711 | "regex-automata", 712 | "regex-syntax", 713 | ] 714 | 715 | [[package]] 716 | name = "regex-automata" 717 | version = "0.4.6" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 720 | dependencies = [ 721 | "aho-corasick", 722 | "memchr", 723 | "regex-syntax", 724 | ] 725 | 726 | [[package]] 727 | name = "regex-syntax" 728 | version = "0.8.2" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 731 | 732 | [[package]] 733 | name = "rustc-demangle" 734 | version = "0.1.23" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 737 | 738 | [[package]] 739 | name = "ryu" 740 | version = "1.0.17" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 743 | 744 | [[package]] 745 | name = "scopeguard" 746 | version = "1.2.0" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 749 | 750 | [[package]] 751 | name = "serde" 752 | version = "1.0.197" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 755 | dependencies = [ 756 | "serde_derive", 757 | ] 758 | 759 | [[package]] 760 | name = "serde-value" 761 | version = "0.7.0" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" 764 | dependencies = [ 765 | "ordered-float", 766 | "serde", 767 | ] 768 | 769 | [[package]] 770 | name = "serde_derive" 771 | version = "1.0.197" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 774 | dependencies = [ 775 | "proc-macro2", 776 | "quote", 777 | "syn 2.0.53", 778 | ] 779 | 780 | [[package]] 781 | name = "serde_json" 782 | version = "1.0.114" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" 785 | dependencies = [ 786 | "itoa", 787 | "ryu", 788 | "serde", 789 | ] 790 | 791 | [[package]] 792 | name = "serde_yaml" 793 | version = "0.9.34+deprecated" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 796 | dependencies = [ 797 | "indexmap", 798 | "itoa", 799 | "ryu", 800 | "serde", 801 | "unsafe-libyaml", 802 | ] 803 | 804 | [[package]] 805 | name = "signal-hook-registry" 806 | version = "1.4.1" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 809 | dependencies = [ 810 | "libc", 811 | ] 812 | 813 | [[package]] 814 | name = "smallvec" 815 | version = "1.13.2" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 818 | 819 | [[package]] 820 | name = "socket2" 821 | version = "0.5.7" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 824 | dependencies = [ 825 | "libc", 826 | "windows-sys 0.52.0", 827 | ] 828 | 829 | [[package]] 830 | name = "strsim" 831 | version = "0.11.1" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 834 | 835 | [[package]] 836 | name = "syn" 837 | version = "1.0.109" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 840 | dependencies = [ 841 | "proc-macro2", 842 | "quote", 843 | "unicode-ident", 844 | ] 845 | 846 | [[package]] 847 | name = "syn" 848 | version = "2.0.53" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" 851 | dependencies = [ 852 | "proc-macro2", 853 | "quote", 854 | "unicode-ident", 855 | ] 856 | 857 | [[package]] 858 | name = "thiserror" 859 | version = "1.0.65" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" 862 | dependencies = [ 863 | "thiserror-impl", 864 | ] 865 | 866 | [[package]] 867 | name = "thiserror-impl" 868 | version = "1.0.65" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" 871 | dependencies = [ 872 | "proc-macro2", 873 | "quote", 874 | "syn 2.0.53", 875 | ] 876 | 877 | [[package]] 878 | name = "thread-id" 879 | version = "4.2.2" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" 882 | dependencies = [ 883 | "libc", 884 | "winapi", 885 | ] 886 | 887 | [[package]] 888 | name = "tokio" 889 | version = "1.37.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 892 | dependencies = [ 893 | "backtrace", 894 | "bytes", 895 | "libc", 896 | "mio", 897 | "num_cpus", 898 | "parking_lot", 899 | "pin-project-lite", 900 | "signal-hook-registry", 901 | "socket2", 902 | "tokio-macros", 903 | "windows-sys 0.48.0", 904 | ] 905 | 906 | [[package]] 907 | name = "tokio-macros" 908 | version = "2.2.0" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 911 | dependencies = [ 912 | "proc-macro2", 913 | "quote", 914 | "syn 2.0.53", 915 | ] 916 | 917 | [[package]] 918 | name = "typemap-ors" 919 | version = "1.0.0" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" 922 | dependencies = [ 923 | "unsafe-any-ors", 924 | ] 925 | 926 | [[package]] 927 | name = "unicode-ident" 928 | version = "1.0.12" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 931 | 932 | [[package]] 933 | name = "unsafe-any-ors" 934 | version = "1.0.0" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" 937 | dependencies = [ 938 | "destructure_traitobject", 939 | ] 940 | 941 | [[package]] 942 | name = "unsafe-libyaml" 943 | version = "0.2.11" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 946 | 947 | [[package]] 948 | name = "utf8parse" 949 | version = "0.2.1" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 952 | 953 | [[package]] 954 | name = "wasi" 955 | version = "0.11.0+wasi-snapshot-preview1" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 958 | 959 | [[package]] 960 | name = "wasm-bindgen" 961 | version = "0.2.95" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 964 | dependencies = [ 965 | "cfg-if", 966 | "once_cell", 967 | "wasm-bindgen-macro", 968 | ] 969 | 970 | [[package]] 971 | name = "wasm-bindgen-backend" 972 | version = "0.2.95" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 975 | dependencies = [ 976 | "bumpalo", 977 | "log", 978 | "once_cell", 979 | "proc-macro2", 980 | "quote", 981 | "syn 2.0.53", 982 | "wasm-bindgen-shared", 983 | ] 984 | 985 | [[package]] 986 | name = "wasm-bindgen-macro" 987 | version = "0.2.95" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 990 | dependencies = [ 991 | "quote", 992 | "wasm-bindgen-macro-support", 993 | ] 994 | 995 | [[package]] 996 | name = "wasm-bindgen-macro-support" 997 | version = "0.2.95" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 1000 | dependencies = [ 1001 | "proc-macro2", 1002 | "quote", 1003 | "syn 2.0.53", 1004 | "wasm-bindgen-backend", 1005 | "wasm-bindgen-shared", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "wasm-bindgen-shared" 1010 | version = "0.2.95" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 1013 | 1014 | [[package]] 1015 | name = "winapi" 1016 | version = "0.3.9" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1019 | dependencies = [ 1020 | "winapi-i686-pc-windows-gnu", 1021 | "winapi-x86_64-pc-windows-gnu", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "winapi-i686-pc-windows-gnu" 1026 | version = "0.4.0" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1029 | 1030 | [[package]] 1031 | name = "winapi-x86_64-pc-windows-gnu" 1032 | version = "0.4.0" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1035 | 1036 | [[package]] 1037 | name = "windows-core" 1038 | version = "0.52.0" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1041 | dependencies = [ 1042 | "windows-targets 0.52.5", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "windows-sys" 1047 | version = "0.48.0" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1050 | dependencies = [ 1051 | "windows-targets 0.48.5", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "windows-sys" 1056 | version = "0.52.0" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1059 | dependencies = [ 1060 | "windows-targets 0.52.5", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "windows-targets" 1065 | version = "0.48.5" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1068 | dependencies = [ 1069 | "windows_aarch64_gnullvm 0.48.5", 1070 | "windows_aarch64_msvc 0.48.5", 1071 | "windows_i686_gnu 0.48.5", 1072 | "windows_i686_msvc 0.48.5", 1073 | "windows_x86_64_gnu 0.48.5", 1074 | "windows_x86_64_gnullvm 0.48.5", 1075 | "windows_x86_64_msvc 0.48.5", 1076 | ] 1077 | 1078 | [[package]] 1079 | name = "windows-targets" 1080 | version = "0.52.5" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1083 | dependencies = [ 1084 | "windows_aarch64_gnullvm 0.52.5", 1085 | "windows_aarch64_msvc 0.52.5", 1086 | "windows_i686_gnu 0.52.5", 1087 | "windows_i686_gnullvm", 1088 | "windows_i686_msvc 0.52.5", 1089 | "windows_x86_64_gnu 0.52.5", 1090 | "windows_x86_64_gnullvm 0.52.5", 1091 | "windows_x86_64_msvc 0.52.5", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "windows_aarch64_gnullvm" 1096 | version = "0.48.5" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1099 | 1100 | [[package]] 1101 | name = "windows_aarch64_gnullvm" 1102 | version = "0.52.5" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 1105 | 1106 | [[package]] 1107 | name = "windows_aarch64_msvc" 1108 | version = "0.48.5" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1111 | 1112 | [[package]] 1113 | name = "windows_aarch64_msvc" 1114 | version = "0.52.5" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 1117 | 1118 | [[package]] 1119 | name = "windows_i686_gnu" 1120 | version = "0.48.5" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1123 | 1124 | [[package]] 1125 | name = "windows_i686_gnu" 1126 | version = "0.52.5" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 1129 | 1130 | [[package]] 1131 | name = "windows_i686_gnullvm" 1132 | version = "0.52.5" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 1135 | 1136 | [[package]] 1137 | name = "windows_i686_msvc" 1138 | version = "0.48.5" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1141 | 1142 | [[package]] 1143 | name = "windows_i686_msvc" 1144 | version = "0.52.5" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 1147 | 1148 | [[package]] 1149 | name = "windows_x86_64_gnu" 1150 | version = "0.48.5" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1153 | 1154 | [[package]] 1155 | name = "windows_x86_64_gnu" 1156 | version = "0.52.5" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 1159 | 1160 | [[package]] 1161 | name = "windows_x86_64_gnullvm" 1162 | version = "0.48.5" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1165 | 1166 | [[package]] 1167 | name = "windows_x86_64_gnullvm" 1168 | version = "0.52.5" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1171 | 1172 | [[package]] 1173 | name = "windows_x86_64_msvc" 1174 | version = "0.48.5" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1177 | 1178 | [[package]] 1179 | name = "windows_x86_64_msvc" 1180 | version = "0.52.5" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1183 | --------------------------------------------------------------------------------