diff --git a/src/main.rs b/src/main.rs index 5fad0b8..bdc8cd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,9 +21,15 @@ async fn main() -> Result<()> { dotenvy::dotenv().ok(); 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 ui = Arc::new(TerminalUi::new(stdout_is_terminal())); @@ -51,68 +57,182 @@ async fn main() -> Result<()> { 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> { if cidrs.is_empty() { return Err(anyhow::anyhow!( "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 { - 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 { let (network, prefix) = cidr .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 .parse() .map_err(|_| anyhow::anyhow!("máscara de sub-rede inválida: {prefix}"))?; + 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 .parse() .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); + + if is_routable_ip(start) { + return Ok(CidrType::Routable); + } + + // Se o primeiro IP é privado, verifica o último let end = if prefix == 32 { start } else { start | !cidr_mask(prefix) }; - let private_ranges = [ - ( - 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" - )); + if is_routable_ip(end) { + return Ok(CidrType::Routable); } - 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 { @@ -122,3 +242,35 @@ fn cidr_mask(prefix: u8) -> u32 { 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()); + } +} \ No newline at end of file