Skip to content

Commit ed4d8e6

Browse files
committed
Add: host discovery for large ipv6 networks for scannerctl rust implementation
To test this, try with the following command sudo target/debug/scannerctl alivetest --hostdiscovery -t 5858:0:0:0:0:0:1:0/124 -v Ensure you added the IP and network to your local host and ensure there are other alive hosts in the same network. You can try with subnets as well
1 parent 2d5a712 commit ed4d8e6

File tree

5 files changed

+97
-18
lines changed

5 files changed

+97
-18
lines changed

rust/Cargo.lock

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ tar = "0"
134134
bzip2 = "0"
135135
zstd = "0"
136136
rustls-pki-types = { version = "1.14.0", features = ["std"] }
137+
cidr = "0.3.2"
137138

138139
[workspace]
139140
members = ["crates/smoketest", "crates/nasl-function-proc-macro"]

rust/src/alive_test/alive_test.rs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
use crate::alive_test::AliveTestError;
66
use crate::alive_test::arp::forge_arp_request;
7-
use crate::alive_test::icmp::{forge_icmp_v4, forge_icmp_v6, forge_neighbor_solicit};
7+
use crate::alive_test::icmp::{
8+
forge_icmp_v4, forge_icmp_v6, forge_icmp_v6_for_host_discovery, forge_neighbor_solicit,
9+
};
810
use crate::nasl::raw_ip_utils::{
911
raw_ip_utils::{FIX_IPV6_HEADER_LENGTH, send_v4_packet, send_v6_packet},
1012
tcp_ping::{FILTER_PORT, forge_tcp_ping_ipv4, forge_tcp_ping_ipv6},
@@ -20,12 +22,14 @@ use pnet::packet::ip::IpNextHeaderProtocols;
2022
use pnet::packet::ipv6::Ipv6Packet;
2123
use pnet::packet::tcp::TcpPacket;
2224
use std::collections::HashSet;
25+
use std::str::FromStr;
2326
use std::time::Duration;
2427
use tokio::sync::mpsc::{self, Receiver, Sender};
2528
use tokio::time::sleep;
2629

2730
use std::net::IpAddr;
2831

32+
use cidr::Ipv6Cidr;
2933
use pcap::{Active, Capture, Inactive, PacketCodec, PacketStream};
3034
use pnet::packet::{
3135
icmp::{IcmpTypes, *},
@@ -45,7 +49,7 @@ const BITS_PER_BYTE: usize = 8;
4549

4650
struct AliveTestCtlStop;
4751

48-
#[derive(Clone)]
52+
#[derive(Debug, Clone)]
4953
struct AliveHostInfo {
5054
ip: String,
5155
detectihttp_method: AliveTestMethods,
@@ -142,13 +146,13 @@ fn process_ipv6_packet(packet: &[u8]) -> Result<Option<AliveHostInfo>, AliveTest
142146
.ok_or_else(|| {
143147
AliveTestError::CreateIcmpPacketFromWrongBufferSize(packet[..].len() as i64)
144148
})?;
145-
146149
let make_alive_host_ctl = |pkt: Ipv6Packet<'_>, method| {
147150
Ok(Some(AliveHostInfo {
148151
ip: pkt.get_source().to_string(),
149152
detectihttp_method: method,
150153
}))
151154
};
155+
152156
match icmp_pkt.get_icmpv6_type() {
153157
Icmpv6Types::EchoReply => return make_alive_host_ctl(pkt, AliveTestMethods::Icmp),
154158
Icmpv6Types::NeighborAdvert => return make_alive_host_ctl(pkt, AliveTestMethods::Arp),
@@ -217,7 +221,7 @@ async fn capture_task(
217221
tokio::select! {
218222
packet = stream.next() => { // packet is Option<Result<Box>>
219223
if let Some(Ok(data)) = packet && let Ok(Some(alive_host)) = process_packet(&data) {
220-
tx_msg.send(alive_host).await.unwrap()
224+
tx_msg.send(alive_host).await.unwrap()
221225
}
222226
},
223227
ctl = rx_ctl.recv() => {
@@ -231,13 +235,51 @@ async fn capture_task(
231235
Ok(())
232236
}
233237

238+
fn get_host_discovery_ipv6_net(
239+
methods: &Vec<AliveTestMethods>,
240+
target: &HashSet<String>,
241+
) -> Option<Result<Ipv6Cidr, AliveTestError>> {
242+
if methods.contains(&AliveTestMethods::HostDiscoveryIpv6) {
243+
if let Some(t) = target.iter().next()
244+
&& target.len() == 1
245+
{
246+
return Some(
247+
Ipv6Cidr::from_str(t)
248+
.map_err(|e| AliveTestError::InvalidDestinationAddr(e.to_string())),
249+
);
250+
} else {
251+
tracing::debug!("Only one IPv6 network address is allowed for host discovery");
252+
return Some(Err(AliveTestError::InvalidDestinationAddr(
253+
target.iter().next().unwrap().to_string(),
254+
)));
255+
}
256+
}
257+
None
258+
}
259+
234260
async fn send_task(
235261
methods: Vec<AliveTestMethods>,
236262
target: HashSet<String>,
237263
timeout: u64,
238264
tx_ctl: Sender<AliveTestCtlStop>,
239265
) -> Result<(), AliveTestError> {
240266
let mut count = 0;
267+
match get_host_discovery_ipv6_net(&methods, &target) {
268+
Some(Ok(dst)) => {
269+
let icmp = forge_icmp_v6_for_host_discovery(dst)?;
270+
send_v6_packet(icmp)?;
271+
tracing::info!("Started host discovery against {}", dst);
272+
sleep(Duration::from_millis(timeout)).await;
273+
// Send only returns error if the receiver is closed, which only happens when it panics.
274+
tx_ctl.send(AliveTestCtlStop).await.unwrap();
275+
return Ok(());
276+
}
277+
Some(Err(e)) => {
278+
tx_ctl.send(AliveTestCtlStop).await.unwrap();
279+
return Err(e);
280+
}
281+
None => {}
282+
};
241283

242284
let target: HashSet<IpAddr> = target
243285
.into_iter()
@@ -358,14 +400,18 @@ impl Scanner {
358400
let capture_handle = tokio::spawn(capture_task(capture_inactive, rx_ctl, tx_msg));
359401

360402
let timeout = self.timeout.unwrap_or((DEFAULT_TIMEOUT * 1000) as u64);
361-
let methods = self.methods.clone();
362-
let send_handle = tokio::spawn(send_task(methods, trgt, timeout, tx_ctl));
403+
let methods_c = self.methods.clone();
404+
let send_handle = tokio::spawn(send_task(methods_c, trgt, timeout, tx_ctl));
363405

364406
while let Some(alivehost) = rx_msg.recv().await {
365407
if self.target.contains(&alivehost.ip) && !alive.contains(&alivehost.ip) {
366408
alive.insert(alivehost.ip.clone());
367409
println!("{} via {:?}", &alivehost.ip, &alivehost.detectihttp_method);
368-
}
410+
} else if let Some(Ok(dst)) = get_host_discovery_ipv6_net(&self.methods, &self.target)
411+
&& dst.contains(&alivehost.ip.parse::<std::net::Ipv6Addr>().unwrap()) {
412+
alive.insert(alivehost.ip.clone());
413+
println!("{} via {:?}", &alivehost.ip, &alivehost.detectihttp_method);
414+
}
369415
}
370416

371417
send_handle.await.unwrap().unwrap();

rust/src/alive_test/icmp.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::nasl::raw_ip_utils::raw_ip_utils::{
1212
DEFAULT_TTL, FIX_IPV6_HEADER_LENGTH, HEADER_LENGTH, IP_LENGTH, IP_PPRTO_VERSION_IPV4,
1313
IPPROTO_IPV6,
1414
};
15+
use cidr::Ipv6Cidr;
1516
use pnet::packet::icmpv6::Icmpv6Code;
1617
use pnet::packet::icmpv6::Icmpv6Type;
1718
use pnet::packet::icmpv6::ndp::MutableNeighborSolicitPacket;
@@ -84,6 +85,7 @@ fn forge_icmp_v6_packet() -> Vec<u8> {
8485

8586
fn forge_ipv6_packet_for_icmp(
8687
icmp_buf: &mut [u8],
88+
src: Ipv6Addr,
8789
dst: Ipv6Addr,
8890
) -> Result<Ipv6Packet<'static>, AliveTestError> {
8991
let icmp_buf_len = icmp_buf.len();
@@ -94,10 +96,9 @@ fn forge_ipv6_packet_for_icmp(
9496

9597
pkt.set_next_header(IpNextHeaderProtocols::Icmpv6);
9698
pkt.set_hop_limit(DEFAULT_TTL);
97-
pkt.set_source(
98-
get_source_ipv6(dst).map_err(|e| AliveTestError::InvalidDestinationAddr(e.to_string()))?,
99-
);
99+
100100
pkt.set_destination(dst);
101+
pkt.set_source(src);
101102
pkt.set_version(IPPROTO_IPV6);
102103
let icmp_buf_len = icmp_buf.len() as i64;
103104
let mut icmp_pkt = packet::icmpv6::MutableIcmpv6Packet::new(icmp_buf).ok_or(
@@ -118,6 +119,25 @@ fn forge_ipv6_packet_for_icmp(
118119
Ok(Ipv6Packet::owned(ip_buf).unwrap())
119120
}
120121

122+
pub fn forge_icmp_v6_for_host_discovery(
123+
dst: Ipv6Cidr,
124+
) -> Result<Ipv6Packet<'static>, AliveTestError> {
125+
let mut icmp_buf = forge_icmp_v6_packet();
126+
let dst = dst.first().next().unwrap().address(); // first is the network address. We need the host.
127+
let src = get_source_ipv6(dst.into())
128+
.map_err(|e| AliveTestError::InvalidDestinationAddr(e.to_string()))?;
129+
let dst = "ff02::1".parse::<Ipv6Addr>().unwrap();
130+
forge_ipv6_packet_for_icmp(&mut icmp_buf, src, dst)
131+
}
132+
133+
pub fn forge_icmp_v6(dst: Ipv6Addr) -> Result<Ipv6Packet<'static>, AliveTestError> {
134+
let mut icmp_buf = forge_icmp_v6_packet();
135+
let src =
136+
get_source_ipv6(dst).map_err(|e| AliveTestError::InvalidDestinationAddr(e.to_string()))?;
137+
138+
forge_ipv6_packet_for_icmp(&mut icmp_buf, src, dst)
139+
}
140+
121141
pub fn forge_neighbor_solicit(dst_ip: Ipv6Addr) -> Result<Ipv6Packet<'static>, AliveTestError> {
122142
let mut icmp_buf = vec![0; MutableNeighborSolicitPacket::minimum_packet_size()];
123143
let mut icmp_pkt = MutableNeighborSolicitPacket::new(&mut icmp_buf).unwrap();
@@ -127,10 +147,7 @@ pub fn forge_neighbor_solicit(dst_ip: Ipv6Addr) -> Result<Ipv6Packet<'static>, A
127147
icmp_pkt.set_icmpv6_code(Icmpv6Code::new(0u8));
128148
icmp_pkt.set_target_addr(dst_ip);
129149

130-
forge_ipv6_packet_for_icmp(&mut icmp_pkt.packet().to_vec(), dst_ip)
131-
}
132-
133-
pub fn forge_icmp_v6(dst: Ipv6Addr) -> Result<Ipv6Packet<'static>, AliveTestError> {
134-
let mut icmp_buf = forge_icmp_v6_packet();
135-
forge_ipv6_packet_for_icmp(&mut icmp_buf, dst)
150+
let src = get_source_ipv6(dst_ip)
151+
.map_err(|e| AliveTestError::InvalidDestinationAddr(e.to_string()))?;
152+
forge_ipv6_packet_for_icmp(&mut icmp_pkt.packet().to_vec(), src, dst_ip)
136153
}

rust/src/scannerctl/alivetest/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ pub struct AliveTestArgs {
3838
/// ARP ping. Supports both IPv4 and IPv6.
3939
#[clap(long, action=ArgAction::SetTrue)]
4040
arp: bool,
41+
/// Host discovery for large IPv6 network. Only one address network at time is possible and not to be combined with other alive methods.
42+
#[clap(long, action=ArgAction::SetTrue)]
43+
hostdiscovery: bool,
4144
}
4245

4346
pub async fn run(args: AliveTestArgs) -> Result<(), CliError> {
@@ -62,6 +65,11 @@ pub async fn run(args: AliveTestArgs) -> Result<(), CliError> {
6265
methods.push(AliveTestMethods::Arp);
6366
}
6467

68+
if args.hostdiscovery {
69+
methods.clear();
70+
methods.push(AliveTestMethods::HostDiscoveryIpv6);
71+
}
72+
6573
if args.icmp || methods.is_empty() {
6674
methods.push(AliveTestMethods::Icmp);
6775
}

0 commit comments

Comments
 (0)