Skip to content

Commit

Permalink
feat(ext/node): add BlockList & SocketAddress classes (denoland#24229)
Browse files Browse the repository at this point in the history
  • Loading branch information
satyarohith committed Jun 18, 2024
1 parent 4b83ce8 commit 8c4b33d
Show file tree
Hide file tree
Showing 12 changed files with 845 additions and 14 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ext/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ home = "0.5.9"
http.workspace = true
idna = "0.3.0"
indexmap.workspace = true
ipnetwork = "0.20.0"
k256 = "0.13.1"
lazy-regex.workspace = true
libc.workspace = true
Expand Down
10 changes: 10 additions & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,15 @@ deno_core::extension!(deno_node,
deps = [ deno_io, deno_fs ],
parameters = [P: NodePermissions],
ops = [
ops::blocklist::op_socket_address_parse,
ops::blocklist::op_socket_address_get_serialization,

ops::blocklist::op_blocklist_new,
ops::blocklist::op_blocklist_add_address,
ops::blocklist::op_blocklist_add_range,
ops::blocklist::op_blocklist_add_subnet,
ops::blocklist::op_blocklist_check,

ops::buffer::op_is_ascii,
ops::buffer::op_is_utf8,
ops::crypto::op_node_create_decipheriv,
Expand Down Expand Up @@ -489,6 +498,7 @@ deno_core::extension!(deno_node,
"internal_binding/uv.ts",
"internal/assert.mjs",
"internal/async_hooks.ts",
"internal/blocklist.mjs",
"internal/buffer.mjs",
"internal/child_process.ts",
"internal/cli_table.ts",
Expand Down
290 changes: 290 additions & 0 deletions ext/node/ops/blocklist.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}
1 change: 1 addition & 0 deletions ext/node/ops/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

pub mod blocklist;
pub mod buffer;
pub mod crypto;
pub mod fs;
Expand Down
Loading

0 comments on commit 8c4b33d

Please sign in to comment.