13 Commits

15 changed files with 4873 additions and 248 deletions

View File

@ -5,7 +5,7 @@ target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
#Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

4569
snow-scanner/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "snow-scanner"
version = "1.0.0"
version = "1.1.0"
authors = ["William Desportes <williamdes@wdes.fr>"]
edition = "2021"
rust-version = "1.81.0" # MSRV

View File

@ -5,5 +5,39 @@ This project name is inspired by the Netflix series "The Snowpiercer"
## Run it
```sh
SERVER_ADDRESS="127.0.0.1:8777" \
DB_URL="mysql://db-user:db-pass@db-server/db-snow-scanner" \
STATIC_DATA_DIR="$PWD/../data" \
cargo run --release
```
## Run in production
The env file located at `/etc/snow-scanner/.env`:
```env
# Your public IP
SERVER_ADDRESS="[2a10:ffff:ff:ff:fff::1]:80"
DB_URL="mysql://db-user:db-pass@db-server/db-snow-scanner"
STATIC_DATA_DIR="/usr/share/snow-scanner/data"
# Adjust this
ROCKET_LOG_LEVEL="debug"
ROCKET_PROFILE="debug"
# Setup TLS
ROCKET_TLS='{certs="/etc/ssl/certs/cert.pem",key="/etc/ssl/private/key.pem", mutual={ca_certs="/etc/ssl/certs/cloudflare.crt",mandatory=true}}'
```
## Make a new release
Pre-requirements: `cargo install cargo-deb`
- Bump `Cargo.toml`
- Commit it
- Run `cd snow-scanner`
- Run `cargo update`
- Commit the changes to the lockfile
- Run tests `cargo test`
- Run `cargo deb`
- Run `./debian/upload.sh target/debian/snow-scanner_1.1.0-1_amd64.deb`
- Run `git tag -a -s -m "snow-scanner/1.1.0" snow-scanner/1.1.0`
- Check `debdiff ../../snow-scanner_1.0.0-1_amd64.deb target/debian/snow-scanner_1.1.0-1_amd64.deb`

21
snow-scanner/debian/postinst Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
# postinst script for snow-scanner
set -eu
if [ "$1" = "configure" ]; then
if ! getent passwd | grep -q "^snow-scanner:"; then
useradd --shell /bin/sh snow-scanner
fi
if [ ! -d /etc/snow-scanner ]; then
mkdir /etc/snow-scanner
chown snow-scanner:snow-scanner /etc/snow-scanner
chmod 770 /etc/snow-scanner
fi
fi
#DEBHELPER#
exit 0

View File

@ -1,6 +1,8 @@
[Unit]
Description=Snow scanner worker
After=network.target
After=snow-scanner.service
Requires=snow-scanner.service
[Service]
Type=simple

View File

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

View File

@ -33,13 +33,16 @@ use rocket_ws::WebSocket;
use server::Server;
use weighted_rs::Weight;
use snow_scanner_worker::detection::{
detect_scanner, get_dns_client, get_dns_server_config, validate_ip,
};
use snow_scanner_worker::modules::{Network, WorkerMessages};
use snow_scanner_worker::scanners::IsStatic;
use snow_scanner_worker::scanners::Scanners;
use snow_scanner_worker::scanners::ScannerMethods;
use snow_scanner_worker::utils::get_dns_rr;
use snow_scanner_worker::{
detection::{get_dns_client, get_dns_server_config, validate_ip},
scanners::STRETCHOID,
};
use snow_scanner_worker::{
modules::{Network, WorkerMessages},
scanners::{ScannerData, ScannerNode},
};
use std::net::SocketAddr;
use std::{
@ -125,14 +128,14 @@ impl FromFormField<'_> for SafeIpAddr {
async fn handle_ip(
query_address: IpAddr,
) -> Result<(IpAddr, Option<Scanners>, ResolvedResult), ()> {
let ptr_result: Result<ResolvedResult, ()> = std::thread::spawn(move || {
) -> Result<(IpAddr, Option<ScannerNode>, ResolvedResult), String> {
let ptr_result: Result<ResolvedResult, String> = std::thread::spawn(move || {
let mut rr_dns_servers = get_dns_rr();
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) {
res
} else {
return Err(());
return Err("Resolving error".to_string());
};
Ok(ptr_result)
})
@ -140,17 +143,20 @@ async fn handle_ip(
.unwrap();
match ptr_result {
Ok(result) => match detect_scanner(&result) {
Ok(Some(scanner_type)) => {
if !validate_ip(query_address) {
error!("Invalid IP address: {query_address}");
return Err(());
Ok(result) => {
let scanner: Result<ScannerNode, String> = result.query.clone().try_into();
match scanner {
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),
}
}
@ -243,7 +249,7 @@ async fn handle_scan(
let msg = EventBusWriterEvent::BroadcastMessage(
WorkerMessages::DoWorkRequest {
neworks: vec![Network(cidr)],
networks: vec![Network(cidr)],
}
.into(),
);
@ -262,45 +268,20 @@ pub struct ReportParams {
ip: SafeIpAddr,
}
fn reply_contents_for_scanner_found(scanner: Scanner) -> HtmlContents {
HtmlContents(match scanner.scanner_name {
Scanners::Binaryedge => match scanner.last_checked_at {
Some(date) => format!(
"Reported a binaryedge ninja! <b>{}</b> known as {} since {date}.",
scanner.ip,
scanner.ip_ptr.unwrap_or("".to_string())
),
None => format!(
"Reported a binaryedge ninja! <b>{}</b> known as {}.",
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"),
fn reply_contents_for_scanner_found(scanner: Scanner, scanner_type: ScannerData) -> HtmlContents {
HtmlContents(match scanner.last_checked_at {
Some(date) => format!(
"Reported a {}! <b>{}</b> known as {} since {date}.",
scanner_type.funny_name,
scanner.ip,
scanner.ip_ptr.unwrap_or("".to_string())
),
None => format!(
"Reported a {}! <b>{}</b> known as {}.",
scanner_type.funny_name,
scanner.ip,
scanner.ip_ptr.unwrap_or("".to_string())
),
})
}
@ -310,13 +291,16 @@ async fn handle_report(mut db: DbConn, form: Form<ReportParams>) -> MultiReply {
Ok((query_address, scanner_type, result)) => match scanner_type {
Some(scanner_type) => match Scanner::find_or_new(
query_address,
scanner_type,
scanner_type.info,
result.result.clone(),
&mut db,
)
.await
{
Ok(scanner) => MultiReply::Content(reply_contents_for_scanner_found(scanner)),
Ok(scanner) => MultiReply::Content(reply_contents_for_scanner_found(
scanner,
scanner_type.info,
)),
Err(err) => MultiReply::Error(ServerError(format!(
"The IP {} resolved as {} could not be saved, server error: {err}.",
form.ip.addr,
@ -389,7 +373,7 @@ async fn handle_get_collection(
#[get("/scanners/<scanner_name>")]
async fn handle_list_scanners(
mut db: DbConn,
scanner_name: Scanners,
scanner_name: ScannerNode,
app_configs: &State<AppConfigs>,
) -> MultiReply {
let static_data_dir: String = app_configs.static_data_dir.clone();
@ -397,13 +381,11 @@ async fn handle_list_scanners(
let mut path: PathBuf = PathBuf::new();
path.push(static_data_dir);
path.push("scanners");
path.push(match scanner_name {
Scanners::Stretchoid | Scanners::Binaryedge | Scanners::Shadowserver => {
panic!("This should not happen")
}
Scanners::Censys => "censys.txt".to_string(),
Scanners::InternetMeasurement => "internet-measurement.com.txt".to_string(),
});
path.push(
scanner_name
.static_file_name()
.expect("Static files should have a static file name"),
);
return match NamedFile::open(path).await {
Ok(file) => MultiReply::FileContents(file),
@ -411,7 +393,7 @@ async fn handle_list_scanners(
};
}
let scanners_list = match Scanner::list_names(scanner_name, &mut db).await {
let scanners_list = match Scanner::list_names(scanner_name.info, &mut db).await {
Ok(data) => Ok(data),
Err(err) => Err(err),
};
@ -537,7 +519,7 @@ async fn report_counts<'a>(rocket: Rocket<rocket::Build>) -> Rocket<rocket::Buil
span_error!("failed to connect to MySQL database" => error!("{e}"));
panic!("aborting launch");
});
match Scanner::list_names(Scanners::Stretchoid, &mut DbConnection(conn)).await {
match Scanner::list_names(STRETCHOID, &mut DbConnection(conn)).await {
Ok(d) => info!("Found {} Stretchoid scanners", d.len()),
Err(err) => error!("Unable to fetch Stretchoid scanners: {err}"),
}

View File

@ -1,9 +1,10 @@
use std::net::IpAddr;
use crate::{DbConn, Scanners};
use crate::DbConn;
use chrono::{NaiveDateTime, Utc};
use hickory_resolver::Name;
use rocket_db_pools::diesel::{dsl::insert_into, prelude::*, result::Error as DieselError};
use snow_scanner_worker::scanners::ScannerData;
use crate::schema::scan_tasks::dsl::scan_tasks;
use crate::schema::scanners::dsl::scanners;
@ -14,7 +15,7 @@ use crate::schema::scanners::dsl::scanners;
pub struct Scanner {
pub ip: String,
pub ip_type: u8,
pub scanner_name: Scanners,
pub scanner_name: String,
pub ip_ptr: Option<String>,
pub created_at: NaiveDateTime,
pub updated_at: Option<NaiveDateTime>,
@ -25,7 +26,7 @@ pub struct Scanner {
impl Scanner {
pub async fn find_or_new(
query_address: IpAddr,
scanner_name: Scanners,
scanner_data: ScannerData<'static>,
ptr: Option<Name>,
conn: &mut DbConn,
) -> Result<Scanner, DieselError> {
@ -45,7 +46,7 @@ impl Scanner {
Scanner {
ip: query_address.to_string(),
ip_type: ip_type,
scanner_name: scanner_name.clone(),
scanner_name: scanner_data.value.to_string(),
ip_ptr: match ptr {
Some(ptr) => Some(ptr.to_string()),
None => None,
@ -79,15 +80,16 @@ impl Scanner {
}
pub async fn list_names(
scanner_name: Scanners,
scanner_data: ScannerData<'static>,
conn: &mut DbConn,
) -> Result<Vec<String>, DieselError> {
use crate::schema::scanners;
use crate::schema::scanners::ip;
use crate::schema::scanners::scanner_name;
scanners
.select(ip)
.filter(scanners::scanner_name.eq(scanner_name.to_string()))
.filter(scanner_name.eq(scanner_data.value))
.order((scanners::ip_type.desc(), scanners::created_at.desc()))
.load::<String>(conn)
.await

View File

@ -271,7 +271,7 @@ impl<'a> Worker<'a> {
Ok(())
}
WorkerMessages::GetWorkRequest {} => {
worker_reply = Some(WorkerMessages::DoWorkRequest { neworks: vec![] });
worker_reply = Some(WorkerMessages::DoWorkRequest { networks: vec![] });
Ok(())
}
WorkerMessages::DoWorkRequest { .. } | WorkerMessages::Invalid { .. } => {

View File

@ -1,12 +1,8 @@
use std::net::IpAddr;
use std::str::FromStr;
use std::time::Duration;
use crate::scanners::Scanners;
use dns_ptr_resolver::ResolvedResult;
use hickory_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts};
use hickory_resolver::{Name, Resolver};
use hickory_resolver::Resolver;
use crate::ip_addr::is_global_hardcoded;
@ -33,68 +29,3 @@ pub fn validate_ip(ip: IpAddr) -> bool {
}
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

@ -15,7 +15,7 @@ pub enum WorkerMessages {
#[serde(rename = "get_work")]
GetWorkRequest {},
#[serde(rename = "do_work")]
DoWorkRequest { neworks: Vec<Network> },
DoWorkRequest { networks: Vec<Network> },
#[serde(rename = "scanner_found")]
ScannerFoundResponse { name: String, address: IpAddr },
#[serde(rename = "")]
@ -95,25 +95,25 @@ mod tests {
#[test]
fn deserialize_do_work_empty() {
let data = "{\"type\":\"do_work\",\"request\":{\"neworks\":[]}}";
let data = "{\"type\":\"do_work\",\"request\":{\"networks\":[]}}";
let result: WorkerMessages = data.to_string().into();
assert_eq!(
result,
WorkerMessages::DoWorkRequest {
neworks: [].to_vec()
networks: [].to_vec()
}
);
}
#[test]
fn deserialize_do_work() {
let data = "{\"type\":\"do_work\",\"request\":{\"neworks\":[\"127.0.0.0/31\"]}}";
let data = "{\"type\":\"do_work\",\"request\":{\"networks\":[\"127.0.0.0/31\"]}}";
let result: WorkerMessages = data.to_string().into();
let cidr: IpCidr = IpCidr::from_str("127.0.0.0/31").unwrap();
assert_eq!(
result,
WorkerMessages::DoWorkRequest {
neworks: [Network(cidr)].to_vec()
networks: [Network(cidr)].to_vec()
}
);
}

View File

@ -5,112 +5,150 @@ use diesel::mysql::MysqlValue;
use diesel::serialize;
use diesel::serialize::IsNull;
use diesel::sql_types::Text;
use hickory_resolver::Name;
use rocket::request::FromParam;
use std::str::FromStr;
use serde::{Deserialize, Deserializer};
use std::fmt;
use std::io::Write;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScannerData<'a> {
pub static_file_name: Option<&'a str>,
pub funny_name: &'a str,
pub display_name: &'a str,
pub value: &'a str,
pub dns_prefix: Option<&'a str>,
}
pub const STRETCHOID: ScannerData = ScannerData {
static_file_name: None,
funny_name: "stretchoid agent",
display_name: "stretchoid",
value: "stretchoid",
dns_prefix: Some("stretchoid.com."),
};
pub const BINARYEDGE: ScannerData = ScannerData {
static_file_name: None,
funny_name: "binaryedge ninja",
display_name: "binaryedge",
value: "binaryedge",
dns_prefix: Some("binaryedge.ninja."),
};
pub const SHADOWSERVER: ScannerData = ScannerData {
static_file_name: None,
funny_name: "cloudy shadowserver",
display_name: "shadowserver",
value: "shadowserver",
dns_prefix: Some("shadowserver.org."),
};
pub fn get_scanners() -> Vec<ScannerData<'static>> {
vec![
STRETCHOID,
BINARYEDGE,
SHADOWSERVER,
ScannerData {
static_file_name: Some("censys.txt"),
funny_name: "Censys node",
display_name: "censys",
value: "censys",
dns_prefix: None,
},
ScannerData {
static_file_name: Some("internet-measurement.com.txt"),
funny_name: "internet measurement probe",
display_name: "internet-measurement.com",
value: "internet-measurement.com",
dns_prefix: None,
},
ScannerData {
static_file_name: Some("anssi.txt"),
funny_name: "French ANSSI probe",
display_name: "anssi",
value: "anssi",
dns_prefix: None,
},
]
}
pub type ScannerNode = ScannersWrapper<ScannerData<'static>>;
#[derive(Debug, Clone, Copy, FromSqlRow, PartialEq)]
pub enum Scanners {
Stretchoid,
Binaryedge,
Shadowserver,
Censys,
InternetMeasurement,
pub struct ScannersWrapper<ScannerData> {
pub info: ScannerData,
}
pub trait IsStatic {
pub trait ScannerMethods {
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 ScannerNode {
fn is_static(self: &Self) -> bool {
match self {
Scanners::Censys => true,
Scanners::InternetMeasurement => true,
_ => false,
}
self.static_file_name().is_some()
}
fn static_file_name(self: &Self) -> Option<&str> {
self.info.static_file_name
}
fn funny_name(self: &Self) -> &str {
self.info.funny_name
}
}
impl FromParam<'_> for Scanners {
impl FromParam<'_> for ScannerNode {
type Error = String;
fn from_param(param: &'_ str) -> Result<Self, Self::Error> {
match param {
"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}")),
}
param.try_into()
}
}
impl<'de> Deserialize<'de> for Scanners {
impl<'de> Deserialize<'de> for ScannerNode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <Vec<String>>::deserialize(deserializer)?;
let k: &str = s[0].as_str();
match k {
"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(serde::de::Error::custom(format!(
"Unknown value: {}",
v.to_string()
))),
match k.try_into() {
Ok(scanners) => Ok(scanners),
Err(v) => Err(serde::de::Error::custom(format!("Unknown value: {}", v))),
}
}
}
impl fmt::Display for Scanners {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Stretchoid => "stretchoid",
Self::Binaryedge => "binaryedge",
Self::Censys => "censys",
Self::InternetMeasurement => "internet-measurement.com",
Self::Shadowserver => "shadowserver",
}
)
impl ToString for ScannerNode {
fn to_string(&self) -> String {
let res: &str = (*self).into();
res.to_string()
}
}
impl serialize::ToSql<Text, Mysql> for Scanners {
impl Into<&str> for ScannerNode {
fn into(self) -> &'static str {
self.info.display_name
}
}
impl serialize::ToSql<Text, Mysql> for ScannerNode {
fn to_sql(&self, out: &mut serialize::Output<Mysql>) -> serialize::Result {
match *self {
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")?,
};
let res: &str = (*self).into();
out.write_all(res.as_bytes())?;
Ok(IsNull::No)
}
}
impl deserialize::FromSql<Text, Mysql> for Scanners {
impl deserialize::FromSql<Text, Mysql> for ScannerNode {
fn from_sql(bytes: MysqlValue) -> deserialize::Result<Self> {
let value = <String as deserialize::FromSql<Text, Mysql>>::from_sql(bytes)?;
let value = &value as &str;
let value: Result<Scanners, String> = value.try_into();
let value: Result<ScannerNode, String> = value.try_into();
match value {
Ok(d) => Ok(d),
Err(err) => Err(err.into()),
@ -118,16 +156,54 @@ impl deserialize::FromSql<Text, Mysql> for Scanners {
}
}
impl TryInto<Scanners> for &str {
// Used for FromSql & FromParam & Deserialize
impl TryInto<ScannerNode> for &str {
type Error = String;
fn try_into(self) -> Result<Scanners, Self::Error> {
match self {
"stretchoid" => Ok(Scanners::Stretchoid),
"binaryedge" => Ok(Scanners::Binaryedge),
"internet-measurement.com" => Ok(Scanners::InternetMeasurement),
"shadowserver" => Ok(Scanners::Shadowserver),
value => Err(format!("Invalid value: {value}")),
fn try_into(self) -> Result<ScannerNode, Self::Error> {
let value: String = self.replace(".txt", "").as_str().to_string();
match get_scanners()
.iter()
.find(|scanner| scanner.value.eq(&value))
{
Some(scanner) => Ok(ScannersWrapper { info: *scanner }),
None => Err(format!("Invalid value: {value}")),
}
}
}
// Used by the DNS logic
impl TryInto<ScannerNode> for Name {
type Error = String;
fn try_into(self) -> Result<ScannerNode, Self::Error> {
let short_name = self.trim_to(2);
match get_scanners()
.iter()
.filter(|scanner| scanner.dns_prefix.is_some())
.find(|scanner| {
short_name.eq_case(
&Name::from_str(scanner.dns_prefix.expect("Should have a DNS prefix"))
.expect("Should parse"),
)
}) {
Some(scanner) => Ok(ScannersWrapper { info: *scanner }),
None => Err(format!("Invalid hostname: {self}")),
}
}
}
#[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<ScannerNode, String> = ptr.try_into();
assert_eq!(res.unwrap().info, 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("2.56.220.2").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("185.253.5.0").unwrap(), // dns0.eu AS50902
IpAddr::from_str("74.82.42.42").unwrap(), // Hurricane Electric [AS6939]
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("74.82.42.42").unwrap(), // Hurricane Electric [AS6939]
];
let mut rr: RoundrobinWeight<Vec<IpAddr>> = RoundrobinWeight::new();

View File

@ -2,10 +2,9 @@ use std::{env, net::IpAddr};
use chrono::{Duration, NaiveDateTime, Utc};
use cidr::IpCidr;
use detection::detect_scanner;
use dns_ptr_resolver::{get_ptr, ResolvedResult};
use log2::*;
use scanners::Scanners;
use scanners::ScannerNode;
use tungstenite::stream::MaybeTlsStream;
use tungstenite::{connect, Error, Message, WebSocket};
use weighted_rs::Weight;
@ -160,14 +159,16 @@ impl Worker {
for addr in addresses {
let client = get_dns_client(&get_dns_server_config(&rr_dns_servers.next().unwrap()));
match get_ptr(addr, client) {
Ok(result) => match detect_scanner(&result) {
Ok(Some(scanner_name)) => {
self.report_detection(scanner_name, addr, result);
}
Ok(None) => {}
Ok(result) => {
let scanner: Result<ScannerNode, String> = result.query.clone().try_into();
Err(err) => error!("Error detecting for {addr}: {:?}", err),
},
match scanner {
Ok(scanner_name) => {
self.report_detection(scanner_name, addr, result);
}
Err(err) => error!("Error detecting for {addr}: {:?}", err),
}
}
Err(_) => {
//debug!("Error processing {addr}: {err}")
}
@ -180,7 +181,12 @@ impl Worker {
}
}
fn report_detection(&mut self, scanner_name: Scanners, addr: IpAddr, result: ResolvedResult) {
fn report_detection(
&mut self,
scanner_name: ScannerNode,
addr: IpAddr,
result: ResolvedResult,
) {
info!("Detected {:?} for {addr}", scanner_name);
let request = WorkerMessages::ScannerFoundResponse {
name: result.result.unwrap().to_string(),
@ -195,9 +201,9 @@ impl Worker {
pub fn receive_request(&mut self, server_request: WorkerMessages) -> &Worker {
match server_request {
WorkerMessages::DoWorkRequest { neworks } => {
info!("Work request received for neworks: {:?}", neworks);
for cidr in neworks {
WorkerMessages::DoWorkRequest { networks } => {
info!("Work request received for networks: {:?}", networks);
for cidr in networks {
let cidr = cidr.0;
self.work_on_cidr(cidr);
}