Simplify the implementation of different scanner types

This commit is contained in:
2025-04-06 20:27:03 +02:00
parent 1d2ef921ff
commit b651f7ff66
5 changed files with 155 additions and 191 deletions

View File

@ -6,7 +6,8 @@ use hickory_resolver::Name;
use rocket::futures::channel::mpsc as rocket_mpsc; use rocket::futures::channel::mpsc as rocket_mpsc;
use rocket::futures::StreamExt; use rocket::futures::StreamExt;
use rocket::tokio; use rocket::tokio;
use snow_scanner_worker::detection::{detect_scanner_from_name, validate_ip}; use snow_scanner_worker::detection::validate_ip;
use snow_scanner_worker::scanners::Scanners;
use crate::Scanner; use crate::Scanner;
@ -57,9 +58,13 @@ impl EventBus {
return; return;
} }
let name = Name::from_str(name.as_str()).unwrap(); let name = Name::from_str(name.as_str()).unwrap();
match detect_scanner_from_name(&name) { let scanner: Result<Scanners, String> = name.clone().try_into();
Ok(Some(scanner_type)) => {
match Scanner::find_or_new(ip, scanner_type, Some(name), db).await { match scanner {
Ok(scanner_type) => {
match Scanner::find_or_new(ip, scanner_type.to_owned(), Some(name), db)
.await
{
Ok(scanner) => { Ok(scanner) => {
let _ = scanner.save(db).await; let _ = scanner.save(db).await;
} }
@ -68,9 +73,6 @@ impl EventBus {
} }
} }
} }
Ok(None) => {
error!("No name detected for: {:?}", name);
}
Err(err) => { Err(err) => {
error!("No name detected error: {:?}", err); error!("No name detected error: {:?}", err);

View File

@ -33,11 +33,9 @@ use rocket_ws::WebSocket;
use server::Server; use server::Server;
use weighted_rs::Weight; use weighted_rs::Weight;
use snow_scanner_worker::detection::{ use snow_scanner_worker::detection::{get_dns_client, get_dns_server_config, validate_ip};
detect_scanner, get_dns_client, get_dns_server_config, validate_ip,
};
use snow_scanner_worker::modules::{Network, WorkerMessages}; use snow_scanner_worker::modules::{Network, WorkerMessages};
use snow_scanner_worker::scanners::IsStatic; use snow_scanner_worker::scanners::ScannerMethods;
use snow_scanner_worker::scanners::Scanners; use snow_scanner_worker::scanners::Scanners;
use snow_scanner_worker::utils::get_dns_rr; use snow_scanner_worker::utils::get_dns_rr;
@ -125,14 +123,14 @@ impl FromFormField<'_> for SafeIpAddr {
async fn handle_ip( async fn handle_ip(
query_address: IpAddr, query_address: IpAddr,
) -> Result<(IpAddr, Option<Scanners>, ResolvedResult), ()> { ) -> Result<(IpAddr, Option<Scanners>, ResolvedResult), String> {
let ptr_result: Result<ResolvedResult, ()> = std::thread::spawn(move || { let ptr_result: Result<ResolvedResult, String> = std::thread::spawn(move || {
let mut rr_dns_servers = get_dns_rr(); let mut rr_dns_servers = get_dns_rr();
let client = get_dns_client(&get_dns_server_config(&rr_dns_servers.next().unwrap())); let client = get_dns_client(&get_dns_server_config(&rr_dns_servers.next().unwrap()));
let ptr_result: ResolvedResult = if let Ok(res) = get_ptr(query_address, client) { let ptr_result: ResolvedResult = if let Ok(res) = get_ptr(query_address, client) {
res res
} else { } else {
return Err(()); return Err("Resolving error".to_string());
}; };
Ok(ptr_result) Ok(ptr_result)
}) })
@ -140,17 +138,20 @@ async fn handle_ip(
.unwrap(); .unwrap();
match ptr_result { match ptr_result {
Ok(result) => match detect_scanner(&result) { Ok(result) => {
Ok(Some(scanner_type)) => { let scanner: Result<Scanners, String> = result.query.clone().try_into();
if !validate_ip(query_address) {
error!("Invalid IP address: {query_address}"); match scanner {
return Err(()); Ok(scanner_type) => {
if !validate_ip(query_address) {
error!("Invalid IP address: {query_address}");
return Err("".to_string());
}
Ok((query_address, Some(scanner_type), result))
} }
Ok((query_address, Some(scanner_type), result)) Err(err) => Err(err),
} }
Ok(None) => Ok((query_address, None, result)), }
Err(err) => Err(err),
},
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
@ -263,44 +264,19 @@ pub struct ReportParams {
} }
fn reply_contents_for_scanner_found(scanner: Scanner) -> HtmlContents { fn reply_contents_for_scanner_found(scanner: Scanner) -> HtmlContents {
HtmlContents(match scanner.scanner_name { HtmlContents(match scanner.last_checked_at {
Scanners::Binaryedge => match scanner.last_checked_at { Some(date) => format!(
Some(date) => format!( "Reported a {}! <b>{}</b> known as {} since {date}.",
"Reported a binaryedge ninja! <b>{}</b> known as {} since {date}.", scanner.scanner_name.funny_name(),
scanner.ip, scanner.ip,
scanner.ip_ptr.unwrap_or("".to_string()) scanner.ip_ptr.unwrap_or("".to_string())
), ),
None => format!( None => format!(
"Reported a binaryedge ninja! <b>{}</b> known as {}.", "Reported a {}! <b>{}</b> known as {}.",
scanner.ip, scanner.scanner_name.funny_name(),
scanner.ip_ptr.unwrap_or("".to_string()) scanner.ip,
), scanner.ip_ptr.unwrap_or("".to_string())
}, ),
Scanners::Stretchoid => match scanner.last_checked_at {
Some(date) => format!(
"Reported a stretchoid agent! <b>{}</b> known as {} since {date}.",
scanner.ip,
scanner.ip_ptr.unwrap_or("".to_string())
),
None => format!(
"Reported a stretchoid agent! <b>{}</b> known as {}.",
scanner.ip,
scanner.ip_ptr.unwrap_or("".to_string())
),
},
Scanners::Shadowserver => match scanner.last_checked_at {
Some(date) => format!(
"Reported a cloudy shadowserver ! <b>{}</b> known as {} since {date}.",
scanner.ip,
scanner.ip_ptr.unwrap_or("".to_string())
),
None => format!(
"Reported a cloudy shadowserver ! <b>{}</b> known as {}.",
scanner.ip,
scanner.ip_ptr.unwrap_or("".to_string())
),
},
_ => format!("Not supported"),
}) })
} }
@ -397,13 +373,11 @@ async fn handle_list_scanners(
let mut path: PathBuf = PathBuf::new(); let mut path: PathBuf = PathBuf::new();
path.push(static_data_dir); path.push(static_data_dir);
path.push("scanners"); path.push("scanners");
path.push(match scanner_name { path.push(
Scanners::Stretchoid | Scanners::Binaryedge | Scanners::Shadowserver => { scanner_name
panic!("This should not happen") .static_file_name()
} .expect("Static files should have a static file name"),
Scanners::Censys => "censys.txt".to_string(), );
Scanners::InternetMeasurement => "internet-measurement.com.txt".to_string(),
});
return match NamedFile::open(path).await { return match NamedFile::open(path).await {
Ok(file) => MultiReply::FileContents(file), Ok(file) => MultiReply::FileContents(file),

View File

@ -1,12 +1,8 @@
use std::net::IpAddr; use std::net::IpAddr;
use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use crate::scanners::Scanners;
use dns_ptr_resolver::ResolvedResult;
use hickory_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts}; use hickory_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts};
use hickory_resolver::{Name, Resolver}; use hickory_resolver::Resolver;
use crate::ip_addr::is_global_hardcoded; use crate::ip_addr::is_global_hardcoded;
@ -33,68 +29,3 @@ pub fn validate_ip(ip: IpAddr) -> bool {
} }
return is_global_hardcoded(ip); return is_global_hardcoded(ip);
} }
pub fn detect_scanner(ptr_result: &ResolvedResult) -> Result<Option<Scanners>, ()> {
match &ptr_result.result {
Some(name) => detect_scanner_from_name(&name),
None => Ok(None),
}
}
pub fn detect_scanner_from_name(name: &Name) -> Result<Option<Scanners>, ()> {
match name {
ref name
if name
.trim_to(2)
.eq_case(&Name::from_str("binaryedge.ninja.").expect("Should parse")) =>
{
Ok(Some(Scanners::Binaryedge))
}
ref name
if name
.trim_to(2)
.eq_case(&Name::from_str("stretchoid.com.").expect("Should parse")) =>
{
Ok(Some(Scanners::Stretchoid))
}
ref name
if name
.trim_to(2)
.eq_case(&Name::from_str("shadowserver.org.").expect("Should parse")) =>
{
Ok(Some(Scanners::Shadowserver))
}
&_ => Ok(None),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_detect_scanner_from_name() {
let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap();
assert_eq!(
detect_scanner_from_name(&ptr).unwrap(),
Some(Scanners::Shadowserver)
);
}
#[test]
fn test_detect_scanner() {
let cname_ptr = Name::from_str("111.0-24.197.62.64.in-addr.arpa.").unwrap();
let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap();
assert_eq!(
detect_scanner(&ResolvedResult {
query: cname_ptr,
result: Some(ptr),
error: None
})
.unwrap(),
Some(Scanners::Shadowserver)
);
}
}

View File

@ -5,10 +5,11 @@ use diesel::mysql::MysqlValue;
use diesel::serialize; use diesel::serialize;
use diesel::serialize::IsNull; use diesel::serialize::IsNull;
use diesel::sql_types::Text; use diesel::sql_types::Text;
use hickory_resolver::Name;
use rocket::request::FromParam; use rocket::request::FromParam;
use std::str::FromStr;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use std::fmt;
use std::io::Write; use std::io::Write;
#[derive(Debug, Clone, Copy, FromSqlRow, PartialEq)] #[derive(Debug, Clone, Copy, FromSqlRow, PartialEq)]
@ -20,35 +21,45 @@ pub enum Scanners {
InternetMeasurement, InternetMeasurement,
} }
pub trait IsStatic { pub trait ScannerMethods {
fn is_static(self: &Self) -> bool; fn is_static(self: &Self) -> bool;
fn static_file_name(self: &Self) -> Option<&str>;
fn funny_name(self: &Self) -> &str;
} }
impl IsStatic for Scanners { impl ScannerMethods for Scanners {
fn is_static(self: &Self) -> bool { fn is_static(self: &Self) -> bool {
match self { match self {
Scanners::Censys => true, Self::Censys => true,
Scanners::InternetMeasurement => true, Self::InternetMeasurement => true,
_ => false, _ => false,
} }
} }
fn static_file_name(self: &Self) -> Option<&str> {
match self {
Self::Censys => Some("censys.txt"),
Self::InternetMeasurement => Some("internet-measurement.com.txt"),
_ => None,
}
}
fn funny_name(self: &Self) -> &str {
match self {
Self::Stretchoid => "stretchoid agent",
Self::Binaryedge => "binaryedge ninja",
Self::Censys => "Censys node",
Self::InternetMeasurement => "internet measurement probe",
Self::Shadowserver => "cloudy shadowserver",
}
}
} }
impl FromParam<'_> for Scanners { impl FromParam<'_> for Scanners {
type Error = String; type Error = String;
fn from_param(param: &'_ str) -> Result<Self, Self::Error> { fn from_param(param: &'_ str) -> Result<Self, Self::Error> {
match param { param.try_into()
"stretchoid" => Ok(Scanners::Stretchoid),
"binaryedge" => Ok(Scanners::Binaryedge),
"shadowserver" => Ok(Scanners::Shadowserver),
"stretchoid.txt" => Ok(Scanners::Stretchoid),
"binaryedge.txt" => Ok(Scanners::Binaryedge),
"shadowserver.txt" => Ok(Scanners::Shadowserver),
"censys.txt" => Ok(Scanners::Censys),
"internet-measurement.com.txt" => Ok(Scanners::InternetMeasurement),
v => Err(format!("Unknown value: {v}")),
}
} }
} }
@ -59,48 +70,29 @@ impl<'de> Deserialize<'de> for Scanners {
{ {
let s = <Vec<String>>::deserialize(deserializer)?; let s = <Vec<String>>::deserialize(deserializer)?;
let k: &str = s[0].as_str(); let k: &str = s[0].as_str();
match k { match k.try_into() {
"stretchoid" => Ok(Scanners::Stretchoid), Ok(scanners) => Ok(scanners),
"binaryedge" => Ok(Scanners::Binaryedge), Err(v) => Err(serde::de::Error::custom(format!("Unknown value: {}", v))),
"shadowserver" => Ok(Scanners::Shadowserver),
"stretchoid.txt" => Ok(Scanners::Stretchoid),
"binaryedge.txt" => Ok(Scanners::Binaryedge),
"shadowserver.txt" => Ok(Scanners::Shadowserver),
"censys.txt" => Ok(Scanners::Censys),
"internet-measurement.com.txt" => Ok(Scanners::InternetMeasurement),
v => Err(serde::de::Error::custom(format!(
"Unknown value: {}",
v.to_string()
))),
} }
} }
} }
impl fmt::Display for Scanners { impl ToString for Scanners {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn to_string(&self) -> String {
write!( match self {
f, Self::Stretchoid => "stretchoid",
"{}", Self::Binaryedge => "binaryedge",
match self { Self::Censys => "censys",
Self::Stretchoid => "stretchoid", Self::InternetMeasurement => "internet-measurement.com",
Self::Binaryedge => "binaryedge", Self::Shadowserver => "shadowserver",
Self::Censys => "censys", }
Self::InternetMeasurement => "internet-measurement.com", .to_string()
Self::Shadowserver => "shadowserver",
}
)
} }
} }
impl serialize::ToSql<Text, Mysql> for Scanners { impl serialize::ToSql<Text, Mysql> for Scanners {
fn to_sql(&self, out: &mut serialize::Output<Mysql>) -> serialize::Result { fn to_sql(&self, out: &mut serialize::Output<Mysql>) -> serialize::Result {
match *self { out.write_all(self.to_string().as_bytes())?;
Self::Stretchoid => out.write_all(b"stretchoid")?,
Self::Binaryedge => out.write_all(b"binaryedge")?,
Self::Censys => out.write_all(b"censys")?,
Self::InternetMeasurement => out.write_all(b"internet-measurement.com")?,
Self::Shadowserver => out.write_all(b"shadowserver")?,
};
Ok(IsNull::No) Ok(IsNull::No)
} }
@ -118,16 +110,81 @@ impl deserialize::FromSql<Text, Mysql> for Scanners {
} }
} }
// Used for FromSql & FromParam & Deserialize
impl TryInto<Scanners> for &str { impl TryInto<Scanners> for &str {
type Error = String; type Error = String;
fn try_into(self) -> Result<Scanners, Self::Error> { fn try_into(self) -> Result<Scanners, Self::Error> {
match self { match self.replace(".txt", "").as_str() {
"stretchoid" => Ok(Scanners::Stretchoid), "stretchoid" => Ok(Scanners::Stretchoid),
"binaryedge" => Ok(Scanners::Binaryedge), "binaryedge" => Ok(Scanners::Binaryedge),
"internet-measurement.com" => Ok(Scanners::InternetMeasurement), "internet-measurement.com" => Ok(Scanners::InternetMeasurement),
"shadowserver" => Ok(Scanners::Shadowserver), "shadowserver" => Ok(Scanners::Shadowserver),
"censys" => Ok(Scanners::Censys),
value => Err(format!("Invalid value: {value}")), value => Err(format!("Invalid value: {value}")),
} }
} }
} }
// Used by the DNS logic
impl TryInto<Scanners> for Name {
type Error = String;
fn try_into(self) -> Result<Scanners, Self::Error> {
match self {
ref name
if name
.trim_to(2)
.eq_case(&Name::from_str("binaryedge.ninja.").expect("Should parse")) =>
{
Ok(Scanners::Binaryedge)
}
ref name
if name
.trim_to(2)
.eq_case(&Name::from_str("stretchoid.com.").expect("Should parse")) =>
{
Ok(Scanners::Stretchoid)
}
ref name
if name
.trim_to(2)
.eq_case(&Name::from_str("shadowserver.org.").expect("Should parse")) =>
{
Ok(Scanners::Shadowserver)
}
ref name => Err(format!("Invalid hostname: {name}")),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::str::FromStr;
#[test]
fn test_detect_scanner_from_name() {
let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap();
let res: Result<Scanners, String> = ptr.try_into();
assert_eq!(res.unwrap(), Scanners::Shadowserver);
}
#[test]
fn test_detect_scanner() {
let cname_ptr = Name::from_str("111.0-24.197.62.64.in-addr.arpa.").unwrap();
let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap();
assert_eq!(
detect_scanner(&ResolvedResult {
query: cname_ptr,
result: Some(ptr),
error: None
})
.unwrap(),
Some(Scanners::Shadowserver)
);
}
}

View File

@ -16,9 +16,9 @@ pub fn get_dns_rr() -> RoundrobinWeight<Vec<IpAddr>> {
IpAddr::from_str("9.9.9.10").unwrap(), IpAddr::from_str("9.9.9.10").unwrap(),
IpAddr::from_str("2.56.220.2").unwrap(), // G-Core DNS IpAddr::from_str("2.56.220.2").unwrap(), // G-Core DNS
IpAddr::from_str("95.85.95.85").unwrap(), // G-Core DNS IpAddr::from_str("95.85.95.85").unwrap(), // G-Core DNS
IpAddr::from_str("193.110.81.0").unwrap(), // dns0.eu AS50902 IpAddr::from_str("193.110.81.0").unwrap(), // dns0.eu AS50902
IpAddr::from_str("185.253.5.0").unwrap(), // dns0.eu AS50902 IpAddr::from_str("185.253.5.0").unwrap(), // dns0.eu AS50902
IpAddr::from_str("74.82.42.42").unwrap(), // Hurricane Electric [AS6939] IpAddr::from_str("74.82.42.42").unwrap(), // Hurricane Electric [AS6939]
]; ];
let mut rr: RoundrobinWeight<Vec<IpAddr>> = RoundrobinWeight::new(); let mut rr: RoundrobinWeight<Vec<IpAddr>> = RoundrobinWeight::new();