Archived
1

Working packet parser

This commit is contained in:
Evan Pratten 2021-12-17 12:27:37 -05:00
parent f510d57ab4
commit 309e463578
4 changed files with 218 additions and 13 deletions

@ -1,13 +1,13 @@
[package]
name = "<crate_name>"
name = "netbrowse"
version = "0.1.0"
authors = ["Evan Pratten <ewpratten@gmail.com>"]
edition = "2018"
description = "<description>"
documentation = "https://docs.rs/<crate_name>"
description = "A graphical frontend to avahi-browse"
documentation = "https://docs.rs/netbrowse"
readme = "README.md"
homepage = "https://github.com/ewpratten/<repo_name>"
repository = "https://github.com/ewpratten/<repo_name>"
homepage = "https://github.com/ewpratten/netbrowse"
repository = "https://github.com/ewpratten/netbrowse"
license = "GPL-3.0"
keywords = []
categories = []
@ -15,3 +15,8 @@ categories = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
which = "4.2.2"
colored = "2.0.0"
eframe = "0.15.0"
ipnet = "2.3.1"
thiserror = "1.0.30"

@ -1,9 +1,9 @@
# <repo_name>
[![Crates.io](https://img.shields.io/crates/v/<crate_name>)](https://crates.io/crates/<crate_name>)
[![Docs.rs](https://docs.rs/<crate_name>/badge.svg)](https://docs.rs/<crate_name>)
[![Build](https://github.com/Ewpratten/<repo_name>/actions/workflows/build.yml/badge.svg)](https://github.com/Ewpratten/<repo_name>/actions/workflows/build.yml)
[![Clippy](https://github.com/Ewpratten/<repo_name>/actions/workflows/clippy.yml/badge.svg)](https://github.com/Ewpratten/<repo_name>/actions/workflows/clippy.yml)
[![Audit](https://github.com/Ewpratten/<repo_name>/actions/workflows/audit.yml/badge.svg)](https://github.com/Ewpratten/<repo_name>/actions/workflows/audit.yml)
# netbrowse
[![Crates.io](https://img.shields.io/crates/v/netbrowse)](https://crates.io/crates/netbrowse)
[![Docs.rs](https://docs.rs/netbrowse/badge.svg)](https://docs.rs/netbrowse)
[![Build](https://github.com/Ewpratten/netbrowse/actions/workflows/build.yml/badge.svg)](https://github.com/Ewpratten/netbrowse/actions/workflows/build.yml)
[![Clippy](https://github.com/Ewpratten/netbrowse/actions/workflows/clippy.yml/badge.svg)](https://github.com/Ewpratten/netbrowse/actions/workflows/clippy.yml)
[![Audit](https://github.com/Ewpratten/netbrowse/actions/workflows/audit.yml/badge.svg)](https://github.com/Ewpratten/netbrowse/actions/workflows/audit.yml)
repo description
@ -13,5 +13,5 @@ repo description
This crate can be installed via `cargo` with:
```sh
cargo install <crate_name>
cargo install netbrowse
```

183
src/ipc.rs Normal file

@ -0,0 +1,183 @@
//! This module handles pulling and parsing data from an `avahi-browse` subprocess in a way that the GUI can render
use std::{
convert::TryFrom,
io::BufRead,
process::{Child, Stdio},
sync::mpsc::{self, Receiver, Sender},
thread::{self, JoinHandle},
};
use thiserror::Error;
/// Defines possible errors generated by the packet parsing process
#[derive(Debug, Error)]
pub enum PacketParseError {
#[error("Failed to parse mode char: {0}")]
ModeParseError(char),
#[error("Failed to internet protocol type: {0}")]
IpTypeParseError(String),
#[error("Ran out of arguments")]
NotEnoughArgsError,
}
/// Tries to unwrap a string from an arg iter, or just returns an error
fn try_unwrap_arg(arg: Option<&str>) -> Result<&str, PacketParseError> {
arg.ok_or(PacketParseError::NotEnoughArgsError)
}
/// Defines the type of packet received from the `avahi-browse` subprocess
#[derive(Debug)]
pub enum PacketMode {
/// A new service was discovered
New,
/// An existing service was updated
Update,
/// An existing service was removed
Remove,
}
impl TryFrom<char> for PacketMode {
type Error = PacketParseError;
/// Parses one of avahi's update chars into something we can make use of
fn try_from(c: char) -> Result<Self, Self::Error> {
match c {
'+' => Ok(PacketMode::New),
'=' => Ok(PacketMode::Update),
'-' => Ok(PacketMode::Remove),
_ => Err(PacketParseError::ModeParseError(c)),
}
}
}
/// Used to specify weather a packet came over IPv4 or IPv6
#[derive(Debug)]
pub enum IpType {
V4,
V6,
}
impl TryFrom<String> for IpType {
type Error = PacketParseError;
/// Parses the type of IP address from the avahi-browse output
fn try_from(s: String) -> Result<Self, Self::Error> {
match s.as_str() {
"IPv4" => Ok(IpType::V4),
"IPv6" => Ok(IpType::V6),
_ => Err(PacketParseError::IpTypeParseError(s)),
}
}
}
/// An information packet parsed from a `avahi-browse` subprocess
#[derive(Debug)]
pub struct MdnsPacket {
/// The type of packet received
pub mode: PacketMode,
/// The interface the packet was received on
pub interface_name: String,
/// The internet protocol type of the packet
pub internet_protocol: IpType,
/// The hostname / service name
pub hostname: String,
/// The type of the service
pub service_type: String,
/// The domain of the service
pub domain: String,
/// Additional service metadata
pub metadata: Vec<String>,
}
impl TryFrom<String> for MdnsPacket {
type Error = PacketParseError;
/// Parses a packet from the `avahi-browse` subprocess
fn try_from(s: String) -> Result<Self, Self::Error> {
// Convert the incoming data into an iter over its components
let mut iter = s.split(';');
Ok(Self {
// Grabs the first char of the first arg, and reads it as a mode
mode: PacketMode::try_from(
try_unwrap_arg(iter.next())?
.chars()
.next()
.ok_or(PacketParseError::NotEnoughArgsError)?,
)?,
// Grabs the second arg, and reads it as an interface name
interface_name: try_unwrap_arg(iter.next())?.to_string(),
// Grabs the third arg, and reads it as an internet protocol type
internet_protocol: IpType::try_from(try_unwrap_arg(iter.next())?.to_string())?,
// Grabs the fourth arg, and reads it as a hostname
hostname: try_unwrap_arg(iter.next())?.to_string(),
// Grabs the fifth arg, and reads it as a service type
service_type: try_unwrap_arg(iter.next())?.to_string(),
// Grabs the sixth arg, and reads it as a domain
domain: try_unwrap_arg(iter.next())?.to_string(),
// Grabs the remaining args, and reads them as metadata
metadata: iter.map(|s| s.to_string()).collect(),
})
}
}
/// Encapsulates everything needed to read from and control an `avahi-browse` subprocess
#[derive(Debug)]
pub struct AvahiSubprocess {
thread: JoinHandle<()>,
/// This stream sends parsed packets back to the main thread
pub packet_stream: Receiver<MdnsPacket>,
}
impl AvahiSubprocess {
/// Creates a new `avahi-browse` subprocess
pub fn spawn() -> Self {
// Create a channel to send parsed packets back to the main thread
let (packet_sender, packet_receiver) = mpsc::channel::<MdnsPacket>();
let thread = thread::spawn(move || {
// Spawn avahi-browse
let child = std::process::Command::new("avahi-browse")
.arg("-p")
.arg("-r")
.arg("-a")
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn avahi-browse subprocess");
// Create a stream from the stdout of the child
let stream = child
.stdout
.expect("Failed to get stdout of avahi-browse subprocess");
let stream = std::io::BufReader::new(stream);
let stream = stream.lines();
// Parse the lines from the stream
for line in stream {
let line = line.expect("Failed to read line from avahi-browse subprocess");
match MdnsPacket::try_from(line.to_string()) {
Ok(packet) => {
// Send the packet to the main thread
println!("{:?}", packet);
packet_sender
.send(packet)
.expect("Failed to send packet to main thread");
}
Err(e) => {
// Log the error
eprintln!("Failed to parse packet: {}", e);
}
}
}
});
// Return the subprocess
Self {
thread,
packet_stream: packet_receiver,
}
}
}

@ -1,4 +1,3 @@
#![doc = include_str!("../README.md")]
#![deny(unsafe_code)]
#![warn(
clippy::all,
@ -64,3 +63,21 @@
nonstandard_style,
rust_2018_idioms
)]
use colored::Colorize;
mod ipc;
fn main() {
// Check for avahi-browse on this system
if let Err(_) = which::which("avahi-browse") {
eprintln!("{}", "`avahi-browse` was not found in system $PATH".red());
std::process::exit(1);
}
// Spawn the subprocess
let subprocess = ipc::AvahiSubprocess::spawn();
loop{}
}