use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, UdpSocket};
use std::time::{Duration, Instant};
use super::{Outcome, StatelessScanner, SynTransport};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EngineKind {
Stateless,
Connect,
}
#[must_use]
pub fn choose_engine(
want_stateless: bool,
raw_available: bool,
) -> (EngineKind, Option<&'static str>) {
match (want_stateless, raw_available) {
(true, true) => (EngineKind::Stateless, None),
(true, false) => (
EngineKind::Connect,
Some(
"stateless SYN scan requested but CAP_NET_RAW is unavailable - \
falling back to the connect scanner (orders of magnitude slower). \
Run with CAP_NET_RAW (e.g. `setcap cap_net_raw+ep`) for masscan-class speed.",
),
),
(false, _) => (EngineKind::Connect, None),
}
}
#[must_use]
pub fn local_source_ipv4(dst: Ipv4Addr) -> Option<Ipv4Addr> {
let sock = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).ok()?;
sock.connect(SocketAddrV4::new(dst, 80)).ok()?;
match sock.local_addr().ok()?.ip() {
IpAddr::V4(v4) if !v4.is_unspecified() => Some(v4),
_ => None,
}
}
pub fn run_blocking<T: SynTransport>(
scanner: &mut StatelessScanner,
transport: &mut T,
pps: u64,
grace: Duration,
) -> std::io::Result<Vec<Outcome>> {
let mut out = Vec::new();
let start = Instant::now();
let mut sent: u64 = 0;
while let Some(p) = scanner.next_probe() {
let wait = super::pace(sent, start.elapsed(), pps);
if !wait.is_zero() {
std::thread::sleep(wait);
}
transport.send(p.dst, &p.bytes)?;
sent += 1;
while let Some(pkt) = transport.try_recv()? {
if let Some(o) = scanner.classify(&pkt) {
if let Some(o) = scanner.record(o) {
out.push(o);
}
}
}
}
let deadline = Instant::now() + grace;
while Instant::now() < deadline {
match transport.try_recv()? {
Some(pkt) => {
if let Some(o) = scanner.classify(&pkt) {
if let Some(o) = scanner.record(o) {
out.push(o);
}
}
}
None => std::thread::sleep(Duration::from_millis(1)),
}
}
Ok(out)
}
#[cfg(target_os = "linux")]
pub mod linux {
use super::*;
use socket2::{Domain, Protocol, Socket, Type};
use std::io::Read;
use std::net::SocketAddr;
#[must_use]
pub fn raw_available() -> bool {
Socket::new(
Domain::IPV4,
Type::from(libc::SOCK_RAW),
Some(Protocol::from(libc::IPPROTO_TCP)),
)
.is_ok()
}
pub struct RawSynTransport {
tx: Socket,
rx: Socket,
rx_buf: Vec<u8>,
}
impl RawSynTransport {
pub fn new() -> std::io::Result<Self> {
let tx = Socket::new(
Domain::IPV4,
Type::from(libc::SOCK_RAW),
Some(Protocol::from(libc::IPPROTO_RAW)),
)?;
let rx = Socket::new(
Domain::IPV4,
Type::from(libc::SOCK_RAW),
Some(Protocol::from(libc::IPPROTO_TCP)),
)?;
rx.set_nonblocking(true)?;
rx.set_read_timeout(Some(Duration::from_millis(1)))?;
Ok(Self {
tx,
rx,
rx_buf: vec![0u8; 65_535],
})
}
}
impl SynTransport for RawSynTransport {
fn send(&mut self, dst: SocketAddrV4, ip_tcp: &[u8]) -> std::io::Result<()> {
let to: SocketAddr = SocketAddr::from(SocketAddrV4::new(*dst.ip(), 0));
self.tx.send_to(ip_tcp, &to.into())?;
Ok(())
}
fn try_recv(&mut self) -> std::io::Result<Option<Vec<u8>>> {
match (&self.rx).read(&mut self.rx_buf) {
Ok(0) => Ok(None),
Ok(n) => Ok(Some(self.rx_buf[..n].to_vec())),
Err(e)
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut =>
{
Ok(None)
}
Err(e) => Err(e),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stateless::cookie::SynCookie;
use crate::stateless::MockTransport;
#[test]
fn engine_selection_never_silently_downgrades() {
assert_eq!(choose_engine(true, true), (EngineKind::Stateless, None));
assert_eq!(choose_engine(false, true).0, EngineKind::Connect);
assert_eq!(choose_engine(false, false).0, EngineKind::Connect);
let (kind, warn) = choose_engine(true, false);
assert_eq!(kind, EngineKind::Connect);
assert!(
warn.expect("must warn on forced downgrade")
.contains("CAP_NET_RAW"),
"the downgrade reason must name the missing capability"
);
}
#[test]
fn local_source_ipv4_is_routable_when_resolvable() {
if let Some(ip) = local_source_ipv4(Ipv4Addr::new(1, 1, 1, 1)) {
assert!(!ip.is_unspecified());
}
}
#[test]
fn run_blocking_unlimited_pps_is_deterministic_and_complete() {
let c = SynCookie::with_key([5; 16]);
let open = vec![SocketAddrV4::new(Ipv4Addr::new(1, 1, 1, 1), 443)];
let mut s = StatelessScanner::new(
SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 2), 40000),
vec![Ipv4Addr::new(1, 1, 1, 1), Ipv4Addr::new(9, 9, 9, 9)],
vec![80, 443],
c.clone(),
42,
);
let mut t = MockTransport::new(c, open.clone(), vec![]);
let outs = run_blocking(&mut s, &mut t, 0, Duration::ZERO).unwrap();
let opens: Vec<_> = outs
.iter()
.filter_map(|o| match o {
Outcome::Open(a) => Some(*a),
Outcome::Closed(_) => None,
})
.collect();
assert_eq!(opens, open, "must find exactly the open port, got {outs:?}");
}
}