diff options
Diffstat (limited to 'ext/node/ops/blocklist.rs')
-rw-r--r-- | ext/node/ops/blocklist.rs | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/ext/node/ops/blocklist.rs b/ext/node/ops/blocklist.rs new file mode 100644 index 000000000..ce32c14ba --- /dev/null +++ b/ext/node/ops/blocklist.rs @@ -0,0 +1,290 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::cell::RefCell; +use std::collections::HashSet; +use std::net::IpAddr; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; +use std::net::SocketAddr; + +use deno_core::anyhow::anyhow; +use deno_core::anyhow::bail; +use deno_core::error::AnyError; +use deno_core::op2; +use deno_core::OpState; + +use ipnetwork::IpNetwork; +use ipnetwork::Ipv4Network; +use ipnetwork::Ipv6Network; +use serde::Serialize; + +pub struct BlockListResource { + blocklist: RefCell<BlockList>, +} + +#[derive(Serialize)] +struct SocketAddressSerialization(String, String); + +#[op2(fast)] +pub fn op_socket_address_parse( + state: &mut OpState, + #[string] addr: &str, + #[smi] port: u16, + #[string] family: &str, +) -> Result<bool, AnyError> { + let ip = addr.parse::<IpAddr>()?; + let parsed: SocketAddr = SocketAddr::new(ip, port); + let parsed_ip_str = parsed.ip().to_string(); + let family_correct = family.eq_ignore_ascii_case("ipv4") && parsed.is_ipv4() + || family.eq_ignore_ascii_case("ipv6") && parsed.is_ipv6(); + + if family_correct { + let family_is_lowercase = family[..3].chars().all(char::is_lowercase); + if family_is_lowercase && parsed_ip_str == addr { + Ok(true) + } else { + state.put::<SocketAddressSerialization>(SocketAddressSerialization( + parsed_ip_str, + family.to_lowercase(), + )); + Ok(false) + } + } else { + Err(anyhow!("Invalid address")) + } +} + +#[op2] +#[serde] +pub fn op_socket_address_get_serialization( + state: &mut OpState, +) -> Result<SocketAddressSerialization, AnyError> { + Ok(state.take::<SocketAddressSerialization>()) +} + +#[op2] +#[cppgc] +pub fn op_blocklist_new() -> BlockListResource { + let blocklist = BlockList::new(); + BlockListResource { + blocklist: RefCell::new(blocklist), + } +} + +#[op2(fast)] +pub fn op_blocklist_add_address( + #[cppgc] wrap: &BlockListResource, + #[string] addr: &str, +) -> Result<(), AnyError> { + wrap.blocklist.borrow_mut().add_address(addr) +} + +#[op2(fast)] +pub fn op_blocklist_add_range( + #[cppgc] wrap: &BlockListResource, + #[string] start: &str, + #[string] end: &str, +) -> Result<bool, AnyError> { + wrap.blocklist.borrow_mut().add_range(start, end) +} + +#[op2(fast)] +pub fn op_blocklist_add_subnet( + #[cppgc] wrap: &BlockListResource, + #[string] addr: &str, + #[smi] prefix: u8, +) -> Result<(), AnyError> { + wrap.blocklist.borrow_mut().add_subnet(addr, prefix) +} + +#[op2(fast)] +pub fn op_blocklist_check( + #[cppgc] wrap: &BlockListResource, + #[string] addr: &str, + #[string] r#type: &str, +) -> Result<bool, AnyError> { + wrap.blocklist.borrow().check(addr, r#type) +} + +struct BlockList { + rules: HashSet<IpNetwork>, +} + +impl BlockList { + pub fn new() -> Self { + BlockList { + rules: HashSet::new(), + } + } + + fn map_addr_add_network(&mut self, addr: IpAddr, prefix: Option<u8>) { + match addr { + IpAddr::V4(addr) => { + self.rules.insert(IpNetwork::V4( + Ipv4Network::new(addr, prefix.unwrap_or(32)).unwrap(), + )); + self.rules.insert(IpNetwork::V6( + Ipv6Network::new(addr.to_ipv6_mapped(), prefix.unwrap_or(128)) + .unwrap(), + )); + } + IpAddr::V6(addr) => { + if let Some(ipv4_mapped) = addr.to_ipv4_mapped() { + self.rules.insert(IpNetwork::V4( + Ipv4Network::new(ipv4_mapped, prefix.unwrap_or(32)).unwrap(), + )); + } + self.rules.insert(IpNetwork::V6( + Ipv6Network::new(addr, prefix.unwrap_or(128)).unwrap(), + )); + } + }; + } + + pub fn add_address(&mut self, address: &str) -> Result<(), AnyError> { + let ip: IpAddr = address.parse()?; + self.map_addr_add_network(ip, None); + Ok(()) + } + + pub fn add_range( + &mut self, + start: &str, + end: &str, + ) -> Result<bool, AnyError> { + let start_ip: IpAddr = start.parse()?; + let end_ip: IpAddr = end.parse()?; + + match (start_ip, end_ip) { + (IpAddr::V4(start), IpAddr::V4(end)) => { + let start_u32: u32 = start.into(); + let end_u32: u32 = end.into(); + if end_u32 < start_u32 { + // Indicates invalid range. + return Ok(false); + } + for ip in start_u32..=end_u32 { + let addr: Ipv4Addr = ip.into(); + self.map_addr_add_network(IpAddr::V4(addr), None); + } + } + (IpAddr::V6(start), IpAddr::V6(end)) => { + let start_u128: u128 = start.into(); + let end_u128: u128 = end.into(); + if end_u128 < start_u128 { + // Indicates invalid range. + return Ok(false); + } + for ip in start_u128..=end_u128 { + let addr: Ipv6Addr = ip.into(); + self.map_addr_add_network(IpAddr::V6(addr), None); + } + } + _ => bail!("IP version mismatch between start and end addresses"), + } + Ok(true) + } + + pub fn add_subnet(&mut self, addr: &str, prefix: u8) -> Result<(), AnyError> { + let ip: IpAddr = addr.parse()?; + self.map_addr_add_network(ip, Some(prefix)); + Ok(()) + } + + pub fn check(&self, addr: &str, r#type: &str) -> Result<bool, AnyError> { + let addr: IpAddr = addr.parse()?; + let family = r#type.to_lowercase(); + if family == "ipv4" && addr.is_ipv4() || family == "ipv6" && addr.is_ipv6() + { + Ok(self.rules.iter().any(|net| net.contains(addr))) + } else { + Err(anyhow!("Invalid address")) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_address() { + // Single IPv4 address + let mut block_list = BlockList::new(); + block_list.add_address("192.168.0.1").unwrap(); + assert!(block_list.check("192.168.0.1", "ipv4").unwrap()); + assert!(block_list.check("::ffff:c0a8:1", "ipv6").unwrap()); + + // Single IPv6 address + let mut block_list = BlockList::new(); + block_list.add_address("2001:db8::1").unwrap(); + assert!(block_list.check("2001:db8::1", "ipv6").unwrap()); + assert!(!block_list.check("192.168.0.1", "ipv4").unwrap()); + } + + #[test] + fn test_add_range() { + // IPv4 range + let mut block_list = BlockList::new(); + block_list.add_range("192.168.0.1", "192.168.0.3").unwrap(); + assert!(block_list.check("192.168.0.1", "ipv4").unwrap()); + assert!(block_list.check("192.168.0.2", "ipv4").unwrap()); + assert!(block_list.check("192.168.0.3", "ipv4").unwrap()); + assert!(block_list.check("::ffff:c0a8:1", "ipv6").unwrap()); + + // IPv6 range + let mut block_list = BlockList::new(); + block_list.add_range("2001:db8::1", "2001:db8::3").unwrap(); + assert!(block_list.check("2001:db8::1", "ipv6").unwrap()); + assert!(block_list.check("2001:db8::2", "ipv6").unwrap()); + assert!(block_list.check("2001:db8::3", "ipv6").unwrap()); + assert!(!block_list.check("192.168.0.1", "ipv4").unwrap()); + } + + #[test] + fn test_add_subnet() { + // IPv4 subnet + let mut block_list = BlockList::new(); + block_list.add_subnet("192.168.0.0", 24).unwrap(); + assert!(block_list.check("192.168.0.1", "ipv4").unwrap()); + assert!(block_list.check("192.168.0.255", "ipv4").unwrap()); + assert!(block_list.check("::ffff:c0a8:0", "ipv6").unwrap()); + + // IPv6 subnet + let mut block_list = BlockList::new(); + block_list.add_subnet("2001:db8::", 64).unwrap(); + assert!(block_list.check("2001:db8::1", "ipv6").unwrap()); + assert!(block_list.check("2001:db8::ffff", "ipv6").unwrap()); + assert!(!block_list.check("192.168.0.1", "ipv4").unwrap()); + } + + #[test] + fn test_check() { + // Check IPv4 presence + let mut block_list = BlockList::new(); + block_list.add_address("192.168.0.1").unwrap(); + assert!(block_list.check("192.168.0.1", "ipv4").unwrap()); + + // Check IPv6 presence + let mut block_list = BlockList::new(); + block_list.add_address("2001:db8::1").unwrap(); + assert!(block_list.check("2001:db8::1", "ipv6").unwrap()); + + // Check IPv4 not present + let block_list = BlockList::new(); + assert!(!block_list.check("192.168.0.1", "ipv4").unwrap()); + + // Check IPv6 not present + let block_list = BlockList::new(); + assert!(!block_list.check("2001:db8::1", "ipv6").unwrap()); + + // Check invalid IP version + let block_list = BlockList::new(); + assert!(block_list.check("192.168.0.1", "ipv6").is_err()); + + // Check invalid type + let mut block_list = BlockList::new(); + block_list.add_address("192.168.0.1").unwrap(); + assert!(block_list.check("192.168.0.1", "invalid_type").is_err()); + } +} |