Unlimited CIDR parsing

This commit is contained in:
2026-06-29 04:53:53 -03:00
parent 9bd945cc51
commit 0f1c867771
+184 -32
View File
@@ -21,9 +21,15 @@ async fn main() -> Result<()> {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let config = Config::from_env_and_args()?; let config = Config::from_env_and_args()?;
if matches!(config.mode, Mode::Local | Mode::Coordinator) {
assert_local_cidrs(&config.cidrs)?; // Valida todos os CIDRs, filtrando os não roteáveis
} let valid_cidrs = validate_and_filter_cidrs(&config.cidrs)?;
// Atualiza a config com apenas CIDRs válidos e roteáveis
let config = Config {
cidrs: valid_cidrs,
..config
};
let database = Arc::new(Database::open(&config.database_url).await?); let database = Arc::new(Database::open(&config.database_url).await?);
let ui = Arc::new(TerminalUi::new(stdout_is_terminal())); let ui = Arc::new(TerminalUi::new(stdout_is_terminal()));
@@ -51,68 +57,182 @@ async fn main() -> Result<()> {
Ok(()) Ok(())
} }
fn assert_local_cidrs(cidrs: &[String]) -> Result<()> { /// Valida todos os CIDRs e remove os que são de redes privadas/não roteáveis
fn validate_and_filter_cidrs(cidrs: &[String]) -> Result<Vec<String>> {
if cidrs.is_empty() { if cidrs.is_empty() {
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
"nenhum CIDR foi informado. Use SCAN_CIDRS, SCAN_CIDR_FILE ou --cidrs/--cidr-file" "nenhum CIDR foi informado. Use SCAN_CIDRS, SCAN_CIDR_FILE ou --cidrs/--cidr-file"
)); ));
} }
let mut valid_cidrs = Vec::new();
let mut skipped_count = 0;
for cidr in cidrs { for cidr in cidrs {
assert_local_cidr_inner(cidr)?; match validate_cidr(cidr) {
Ok(CidrType::Routable) => {
valid_cidrs.push(cidr.clone());
}
Ok(CidrType::Private) => {
eprintln!("️ Ignorando CIDR privado/não roteável: {cidr}");
skipped_count += 1;
}
Err(err) => {
eprintln!("⚠️ CIDR inválido ignorado: {cidr} ({err})");
}
}
} }
Ok(()) if skipped_count > 0 {
eprintln!("️ Total de {} CIDR(s) privados/não roteáveis ignorados", skipped_count);
}
if valid_cidrs.is_empty() {
return Err(anyhow::anyhow!(
"nenhum CIDR público roteável encontrado. Todos os CIDRs fornecidos são privados ou inválidos"
));
}
Ok(valid_cidrs)
} }
fn assert_local_cidr_inner(cidr: &str) -> Result<()> { #[derive(Debug, PartialEq)]
enum CidrType {
Routable,
Private,
}
/// Valida um CIDR e determina se é roteável ou privado
fn validate_cidr(cidr: &str) -> Result<CidrType> {
let (network, prefix) = cidr let (network, prefix) = cidr
.split_once('/') .split_once('/')
.ok_or_else(|| anyhow::anyhow!("CIDR inválido: {cidr}"))?; .ok_or_else(|| anyhow::anyhow!("formato CIDR inválido: {cidr}"))?;
let prefix: u8 = prefix let prefix: u8 = prefix
.parse() .parse()
.map_err(|_| anyhow::anyhow!("máscara de sub-rede inválida: {prefix}"))?; .map_err(|_| anyhow::anyhow!("máscara de sub-rede inválida: {prefix}"))?;
if prefix > 32 { if prefix > 32 {
return Err(anyhow::anyhow!("máscara de sub-rede inválida: {prefix}")); return Err(anyhow::anyhow!("máscara de sub-rede deve ser <= 32: {prefix}"));
} }
let ip: std::net::Ipv4Addr = network let ip: std::net::Ipv4Addr = network
.parse() .parse()
.map_err(|_| anyhow::anyhow!("endereço IP inválido: {network}"))?; .map_err(|_| anyhow::anyhow!("endereço IP inválido: {network}"))?;
// Verifica se algum IP na faixa é roteável
// Para /24 ou menores, verificamos o primeiro IP
// Para ranges maiores, verificamos se a faixa toda é privada
let start = u32::from(ip) & cidr_mask(prefix); let start = u32::from(ip) & cidr_mask(prefix);
if is_routable_ip(start) {
return Ok(CidrType::Routable);
}
// Se o primeiro IP é privado, verifica o último
let end = if prefix == 32 { let end = if prefix == 32 {
start start
} else { } else {
start | !cidr_mask(prefix) start | !cidr_mask(prefix)
}; };
let private_ranges = [ if is_routable_ip(end) {
( return Ok(CidrType::Routable);
u32::from(std::net::Ipv4Addr::new(10, 0, 0, 0)),
u32::from(std::net::Ipv4Addr::new(10, 255, 255, 255)),
),
(
u32::from(std::net::Ipv4Addr::new(172, 16, 0, 0)),
u32::from(std::net::Ipv4Addr::new(172, 31, 255, 255)),
),
(
u32::from(std::net::Ipv4Addr::new(192, 168, 0, 0)),
u32::from(std::net::Ipv4Addr::new(192, 168, 255, 255)),
),
];
let allowed = private_ranges
.iter()
.any(|(range_start, range_end)| start >= *range_start && end <= *range_end);
if !allowed {
return Err(anyhow::anyhow!(
"CIDR fora de redes locais permitidas: {cidr}. Use 10.0.0.0/8, 172.16.0.0/12 ou 192.168.0.0/16"
));
} }
Ok(()) // Ambos são privados, então o CIDR é privado
Ok(CidrType::Private)
}
/// Verifica se um endereço IPv4 (como u32) é roteável na internet pública
fn is_routable_ip(addr: u32) -> bool {
let octets = [
((addr >> 24) & 0xFF) as u8,
((addr >> 16) & 0xFF) as u8,
((addr >> 8) & 0xFF) as u8,
(addr & 0xFF) as u8,
];
let first = octets[0];
let second = octets[1];
let third = octets[2];
// 0.0.0.0/8 - Reservado (endereço não especificado)
if first == 0 {
return false;
}
// 10.0.0.0/8 - Privado (redes internas grandes)
if first == 10 {
return false;
}
// 100.64.0.0/10 - CGNAT (compartilhado por operadoras)
if first == 100 && second >= 64 && second <= 127 {
return false;
}
// 127.0.0.0/8 - Loopback
if first == 127 {
return false;
}
// 169.254.0.0/16 - Link-local (APIPA)
if first == 169 && second == 254 {
return false;
}
// 172.16.0.0/12 - Privado (redes internas médias)
if first == 172 && second >= 16 && second <= 31 {
return false;
}
// 192.0.0.0/24 - Reservado (usos especiais IETF)
if first == 192 && second == 0 && third == 0 {
return false;
}
// 192.0.2.0/24 - Documentação (TEST-NET-1)
if first == 192 && second == 0 && third == 2 {
return false;
}
// 192.88.99.0/24 - Reservado (IPv6 to IPv4 relay)
if first == 192 && second == 88 && third == 99 {
return false;
}
// 192.168.0.0/16 - Privado (redes domésticas/escritórios)
if first == 192 && second == 168 {
return false;
}
// 198.18.0.0/15 - Testes de benchmark
if first == 198 && (second == 18 || second == 19) {
return false;
}
// 198.51.100.0/24 - Documentação (TEST-NET-2)
if first == 198 && second == 51 && third == 100 {
return false;
}
// 203.0.113.0/24 - Documentação (TEST-NET-3)
if first == 203 && second == 0 && third == 113 {
return false;
}
// 224.0.0.0/4 - Multicast
if first >= 224 && first <= 239 {
return false;
}
// 240.0.0.0/4 - Reservado (uso futuro e especial)
if first >= 240 {
return false;
}
true
} }
fn cidr_mask(prefix: u8) -> u32 { fn cidr_mask(prefix: u8) -> u32 {
@@ -122,3 +242,35 @@ fn cidr_mask(prefix: u8) -> u32 {
u32::MAX << (32 - prefix) u32::MAX << (32 - prefix)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_routable_ips() {
assert!(is_routable_ip(u32::from(std::net::Ipv4Addr::new(1, 2, 3, 4))));
assert!(is_routable_ip(u32::from(std::net::Ipv4Addr::new(8, 8, 8, 8))));
assert!(is_routable_ip(u32::from(std::net::Ipv4Addr::new(203, 0, 114, 1))));
}
#[test]
fn test_private_ips() {
assert!(!is_routable_ip(u32::from(std::net::Ipv4Addr::new(10, 0, 0, 1))));
assert!(!is_routable_ip(u32::from(std::net::Ipv4Addr::new(172, 16, 0, 1))));
assert!(!is_routable_ip(u32::from(std::net::Ipv4Addr::new(192, 168, 1, 1))));
assert!(!is_routable_ip(u32::from(std::net::Ipv4Addr::new(127, 0, 0, 1))));
}
#[test]
fn test_cidr_validation() {
assert_eq!(validate_cidr("1.2.3.0/24").unwrap(), CidrType::Routable);
assert_eq!(validate_cidr("8.8.8.8/32").unwrap(), CidrType::Routable);
assert_eq!(validate_cidr("192.168.1.0/24").unwrap(), CidrType::Private);
assert_eq!(validate_cidr("10.0.0.0/8").unwrap(), CidrType::Private);
assert!(validate_cidr("invalid").is_err());
assert!(validate_cidr("1.2.3.4/33").is_err());
assert!(validate_cidr("999.999.999.999/24").is_err());
}
}