Skip to content

Commit 0eec937

Browse files
authored
Add: Host discovery for large IPv6 networks (#2163)
* Add: handle large ipv6 networks With this patch, the host discovery for large ipv6 networks is introduced together with a new alive test method (int 32). Instead of creating a host list from an IPv6 network address and mask (e.g. 5858::0/64), which would take years to scan and it woudl consume all the RAM, it just call a new function in gvm-libs to perform an ICMPv6 to a multicast IPv6 address (ff02::1). Later, the found host are set as target and they will be handle as before. To avoid a double alive test, they are just considered alive. For now, it only supports only one IPv6 network address. This means it can not be combined with other networks or unicast addresses. Also, it is required that the local network has ICMPv6 enabled, otherwise the discovery won't work. * add the new alive method * remove checks for alive methods * 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 34cb958 commit 0eec937

File tree

10 files changed

+133
-20
lines changed

10 files changed

+133
-20
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/crates/greenbone-scanner-framework/src/models/target.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub enum AliveTestMethods {
4646
Arp = 0x04,
4747
ConsiderAlive = 0x08,
4848
TcpSyn = 0x10,
49+
HostDiscoveryIpv6 = 0x20,
4950
}
5051

5152
#[derive(Debug, thiserror::Error)]
@@ -62,6 +63,7 @@ impl AsRef<str> for AliveTestMethods {
6263
AliveTestMethods::Arp => "arp",
6364
AliveTestMethods::ConsiderAlive => "consider_alive",
6465
AliveTestMethods::TcpSyn => "tcp_syn",
66+
AliveTestMethods::HostDiscoveryIpv6 => "host_discovery_ipv6",
6567
}
6668
}
6769
}
@@ -76,6 +78,7 @@ impl TryFrom<u8> for AliveTestMethods {
7678
0x04 => Ok(AliveTestMethods::Arp),
7779
0x08 => Ok(AliveTestMethods::ConsiderAlive),
7880
0x10 => Ok(AliveTestMethods::TcpSyn),
81+
0x20 => Ok(AliveTestMethods::HostDiscoveryIpv6),
7982
_ => Err(AliveTestMethodsError::InvalidValue(value)),
8083
}
8184
}
@@ -88,6 +91,7 @@ impl From<&str> for AliveTestMethods {
8891
"tcp_syn" => AliveTestMethods::TcpSyn,
8992
"icmp" => AliveTestMethods::Icmp,
9093
"arp" => AliveTestMethods::Arp,
94+
"host_discovery_ipv6" => AliveTestMethods::HostDiscoveryIpv6,
9195
_ => AliveTestMethods::ConsiderAlive,
9296
}
9397
}
@@ -101,6 +105,7 @@ impl Display for AliveTestMethods {
101105
AliveTestMethods::Arp => write!(f, "arp"),
102106
AliveTestMethods::ConsiderAlive => write!(f, "consider_alive"),
103107
AliveTestMethods::TcpSyn => write!(f, "tcp_ping"),
108+
AliveTestMethods::HostDiscoveryIpv6 => write!(f, "host_discovery_ipv6"),
104109
}
105110
}
106111
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
DROP TABLE alive_methods;
2+
3+
CREATE TABLE alive_methods (
4+
id INTEGER,
5+
method TEXT DEFAULT 'icmp',
6+
PRIMARY KEY (id, method),
7+
FOREIGN KEY (id) REFERENCES client_scan_map(id) ON DELETE CASCADE
8+
);

rust/src/alive_test/alive_test.rs

Lines changed: 53 additions & 6 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: &[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::warn!("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,13 +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);
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+
{
413+
alive.insert(alivehost.ip.clone());
414+
println!("{} via {:?}", &alivehost.ip, &alivehost.detectihttp_method);
368415
}
369416
}
370417

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 =
128+
get_source_ipv6(dst).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/openvas/pref_handler.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ where
140140

141141
async fn prepare_plugins_for_openvas(&mut self) -> RedisStorageResult<()> {
142142
let nvts = &self.scan_config.vts;
143-
144143
if nvts.is_empty() {
145144
return Ok(());
146145
}
@@ -270,7 +269,7 @@ where
270269
alive_test |= m as u8;
271270
}
272271

273-
if (1..=31).contains(&alive_test) {
272+
if (1..=32).contains(&alive_test) {
274273
self.redis_connector.push_kb_item(
275274
format!("internal/{}/scanprefs", self.scan_config.scan_id.clone()).as_str(),
276275
format!("{BOREAS_ALIVE_TEST}|||{alive_test}"),

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
}

rust/src/scannerctl/osp/start_scan.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ impl Serialize for Target {
217217
models::AliveTestMethods::Arp => ("arp", "1"),
218218
models::AliveTestMethods::ConsiderAlive => ("consider_alive", "1"),
219219
models::AliveTestMethods::TcpSyn => ("tcp_sync", "1"),
220+
models::AliveTestMethods::HostDiscoveryIpv6 => ("host_discovery_ipv6", "1"),
220221
})
221222
.collect();
222223
if !fields.is_empty() {

src/attack.c

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <gvm/base/prefs.h> /* for prefs_get() */
4141
#include <gvm/boreas/alivedetection.h> /* for start_alive_detection() */
4242
#include <gvm/boreas/boreas_io.h> /* for get_host_from_queue() */
43+
#include <gvm/boreas/cli.h> /* for ipv6 host discovery */
4344
#include <gvm/util/mqtt.h>
4445
#include <gvm/util/nvticache.h> /* for nvticache_t */
4546
#include <pthread.h>
@@ -1197,7 +1198,26 @@ attack_network (struct scan_globals *globals)
11971198
return error;
11981199
}
11991200
/* Init and check Target List */
1200-
hostlist = prefs_get ("TARGET");
1201+
#ifdef FEATURE_HOST_DISCOVERY_IPV6
1202+
alive_test_t alive_test;
1203+
const char *target_aux = prefs_get ("TARGET");
1204+
char *host_found = "";
1205+
1206+
get_alive_test_methods (&alive_test);
1207+
if (alive_test == 32)
1208+
{
1209+
int print_results = 0;
1210+
run_cli_for_ipv6_network (target_aux, &host_found, print_results);
1211+
hostlist = host_found;
1212+
// Consider alive the found hosts, to avoid double check later
1213+
prefs_set("ALIVE_TEST", "8");
1214+
}
1215+
else
1216+
#endif /* FEATURE_HOST_DISCOVERY_IPV6 */
1217+
{
1218+
hostlist = prefs_get ("TARGET");
1219+
}
1220+
12011221
if (hostlist == NULL)
12021222
{
12031223
error = -1;

0 commit comments

Comments
 (0)