it builds...for me at least
This commit is contained in:
commit
3713f58338
4 changed files with 2314 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
2114
Cargo.lock
generated
Normal file
2114
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "ip-reporter"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
gtk = "0.18.1"
|
||||
glib = "0.19.7"
|
||||
pcap = "2.0.0"
|
||||
reqwest = { version = "0.12.4", features = ["blocking"] }
|
||||
threadpool = "1.8"
|
||||
pnet = "0.35.0"
|
187
src/main.rs
Normal file
187
src/main.rs
Normal file
|
@ -0,0 +1,187 @@
|
|||
use glib::ControlFlow;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{
|
||||
Application, ApplicationWindow, Button, CellRendererText, FileChooserAction, FileChooserDialog,
|
||||
Label, ListStore, ResponseType, TreeView, TreeViewColumn,
|
||||
};
|
||||
use pcap::{Capture, Device};
|
||||
use reqwest;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::{self};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
const DESTINATION_IP: &str = "255.255.255.255";
|
||||
const SOURCE_PORT: u16 = 14236;
|
||||
const DESTINATION_PORT: u16 = 14235;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PacketInfo {
|
||||
source_ip: String,
|
||||
source_mac: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let application = Application::new(Some("com.example.ip_reporter"), Default::default());
|
||||
|
||||
application.connect_activate(|app| {
|
||||
let window = ApplicationWindow::new(app);
|
||||
window.set_title("IP Reporter");
|
||||
window.set_default_size(600, 400);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 5);
|
||||
let tree_view = TreeView::new();
|
||||
let list_store = ListStore::new(&[String::static_type(), String::static_type()]);
|
||||
|
||||
tree_view.set_model(Some(&list_store));
|
||||
tree_view.append_column(&create_column("IP Address", 0));
|
||||
tree_view.append_column(&create_column("MAC Address", 1));
|
||||
|
||||
let start_button = Button::with_label("Start");
|
||||
let export_button = Button::with_label("Export");
|
||||
let status_label = Rc::new(RefCell::new(Label::new(Some("Stopped"))));
|
||||
|
||||
vbox.pack_start(&tree_view, true, true, 0);
|
||||
vbox.pack_start(&start_button, false, false, 0);
|
||||
vbox.pack_start(&export_button, false, false, 0);
|
||||
vbox.pack_start(&*status_label.borrow(), false, false, 0);
|
||||
|
||||
window.add(&vbox);
|
||||
window.show_all();
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let listening = Arc::new(Mutex::new(false));
|
||||
let packets = Arc::new(Mutex::new(Vec::new()));
|
||||
let pool = ThreadPool::new(4);
|
||||
|
||||
{
|
||||
let packets = Arc::clone(&packets);
|
||||
let list_store = list_store.clone();
|
||||
let status_label = Rc::clone(&status_label);
|
||||
let listening_main = Arc::clone(&listening);
|
||||
|
||||
start_button.connect_clicked(move |button| {
|
||||
let tx = tx.clone();
|
||||
let listening = Arc::clone(&listening_main);
|
||||
let packets = Arc::clone(&packets);
|
||||
let status_label = Rc::clone(&status_label);
|
||||
|
||||
let mut is_listening = listening.lock().unwrap();
|
||||
if !*is_listening {
|
||||
*is_listening = true;
|
||||
button.set_label("Stop");
|
||||
status_label.borrow().set_text("Listening...");
|
||||
|
||||
let listening = Arc::clone(&listening);
|
||||
pool.execute(move || {
|
||||
let device = Device::lookup().unwrap().unwrap();
|
||||
let mut cap = Capture::from_device(device)
|
||||
.unwrap()
|
||||
.promisc(true)
|
||||
.timeout(1000)
|
||||
.open()
|
||||
.unwrap();
|
||||
while *listening.lock().unwrap() {
|
||||
if let Ok(packet) = cap.next_packet() {
|
||||
if let Some(info) = extract_packet_info(packet.data) {
|
||||
packets.lock().unwrap().push(info.clone());
|
||||
tx.send(info).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
*is_listening = false;
|
||||
button.set_label("Start");
|
||||
status_label.borrow().set_text("Stopped");
|
||||
}
|
||||
});
|
||||
|
||||
glib::idle_add_local(move || {
|
||||
while let Ok(info) = rx.try_recv() {
|
||||
list_store
|
||||
.insert_with_values(None, &[(0, &info.source_ip), (1, &info.source_mac)]);
|
||||
}
|
||||
ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let status_label = Rc::clone(&status_label);
|
||||
export_button.connect_clicked(move |_| {
|
||||
let dialog = FileChooserDialog::with_buttons(
|
||||
Some("Save File"),
|
||||
Some(&window),
|
||||
FileChooserAction::Save,
|
||||
&[("Cancel", ResponseType::Cancel), ("Save", ResponseType::Ok)],
|
||||
);
|
||||
if dialog.run() == ResponseType::Ok {
|
||||
if let Some(file_path) = dialog.filename() {
|
||||
let data = packets.lock().unwrap();
|
||||
std::fs::write(
|
||||
file_path,
|
||||
data.iter()
|
||||
.map(|info| {
|
||||
format!(
|
||||
"IP Address: {}, MAC Address: {}\n",
|
||||
info.source_ip, info.source_mac
|
||||
)
|
||||
})
|
||||
.collect::<String>(),
|
||||
)
|
||||
.unwrap();
|
||||
status_label.borrow().set_text("Data exported.");
|
||||
}
|
||||
}
|
||||
dialog.close();
|
||||
});
|
||||
}
|
||||
|
||||
tree_view.connect_row_activated(move |_, path, _| {
|
||||
if let Some(iter) = list_store.iter(path) {
|
||||
let ip_address: String = list_store.value(&iter, 0).get().unwrap();
|
||||
let url = format!("http://root:root@{}", ip_address);
|
||||
if let Err(err) = reqwest::blocking::get(&url) {
|
||||
eprintln!("Failed to open URL: {}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
application.run();
|
||||
}
|
||||
|
||||
fn create_column(title: &str, id: i32) -> TreeViewColumn {
|
||||
let column = TreeViewColumn::new();
|
||||
column.set_title(title);
|
||||
let cell = CellRendererText::new();
|
||||
gtk::prelude::CellLayoutExt::pack_start(&column, &cell, true);
|
||||
gtk::prelude::TreeViewColumnExt::add_attribute(&column, &cell, "text", id);
|
||||
column
|
||||
}
|
||||
|
||||
fn extract_packet_info(packet: &[u8]) -> Option<PacketInfo> {
|
||||
use pnet::packet::ethernet::{EtherTypes, EthernetPacket};
|
||||
use pnet::packet::ip::IpNextHeaderProtocols;
|
||||
use pnet::packet::{ipv4::Ipv4Packet, udp::UdpPacket, Packet};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
let ethernet = EthernetPacket::new(packet)?;
|
||||
if ethernet.get_ethertype() == EtherTypes::Ipv4 {
|
||||
let ipv4 = Ipv4Packet::new(ethernet.payload())?;
|
||||
if ipv4.get_next_level_protocol() == IpNextHeaderProtocols::Udp {
|
||||
let udp = UdpPacket::new(ipv4.payload())?;
|
||||
if ipv4.get_destination() == DESTINATION_IP.parse::<Ipv4Addr>().unwrap()
|
||||
&& udp.get_source() == SOURCE_PORT
|
||||
&& udp.get_destination() == DESTINATION_PORT
|
||||
{
|
||||
return Some(PacketInfo {
|
||||
source_ip: ipv4.get_source().to_string(),
|
||||
source_mac: ethernet.get_source().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
Loading…
Reference in a new issue