/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/node/ops/tcp_wrap.rs
913 строк
29 KB
nathanwhitbot
fix(node): stop active read on stream close to avoid leaking Socket/TCPWrap (#35898)
09 июл 2026, 21:58
Не верифицирован
09 июл 2026, 21:58
7f8b903
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. // // TCPWrap -- TCP handle inheriting from LibUvStreamWrap. // // Follows the TTY pattern: inherits read/write/shutdown from the base class, // only implements TCP-specific ops (bind, listen, connect, accept, etc.). use std::cell::Cell; use std::cell::RefCell; use std::net::ToSocketAddrs; use std::rc::Rc; use deno_core::CppgcInherits; use deno_core::GarbageCollected; use deno_core::OpState; use deno_core::ResourceId; use deno_core::error::ResourceError; use deno_core::op2; use deno_core::uv_compat; use deno_core::uv_compat::UvConnect; use deno_core::uv_compat::UvLoop; use deno_core::uv_compat::UvStream; use deno_core::uv_compat::UvTcp; use deno_core::v8; use deno_net::io::TcpStreamResource; use deno_permissions::PermissionsContainer; use socket2::SockAddr as Socket2SockAddr; use crate::ops::handle_wrap::AsyncWrap; use crate::ops::handle_wrap::Handle; use crate::ops::handle_wrap::HandleWrap; use crate::ops::handle_wrap::OwnedPtr; use crate::ops::handle_wrap::ProviderType; use crate::ops::stream_wrap::LibUvStreamWrap; use crate::ops::stream_wrap::clone_context_from_uv_loop; #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(i32)] enum SocketType { Socket = 0, Server = 1, } // -- libuv callbacks (called from the event loop) -- /// Macro to set up a v8 scope from a uv stream's handle data and call a JS /// callback. The stream's `data` must point to a valid `StreamHandleData`. /// /// # Safety /// The caller must ensure `$stream` is a valid `uv_stream_t` pointer whose /// `data` field points to a live `StreamHandleData` allocation. /// Obtain a V8 scope and the JS handle object for a libuv stream, then /// execute `$body`. Always expands inside `unsafe extern "C"` callbacks. macro_rules! with_js_handle { ($stream:expr, |$scope:ident, $this:ident| $body:block) => {{ let Some(handle_data_ptr) = LibUvStreamWrap::stable_handle_data($stream) else { return; }; // SAFETY: handle_data_ptr is non-null and points to a live StreamHandleData. let handle_data = unsafe { handle_data_ptr.as_ref() }; // SAFETY: isolate pointer was stored during set_js_handle and is valid // for the lifetime of the stream. let isolate_ptr = unsafe { *handle_data.isolate.get() }; if isolate_ptr.is_null() { return; } // SAFETY: isolate_ptr is a valid raw isolate pointer stored during // set_js_handle. let mut isolate = unsafe { v8::Isolate::from_raw_isolate_ptr(isolate_ptr) }; // SAFETY: $stream is valid per caller contract; loop_ is set by libuv. let loop_ptr = unsafe { (*$stream).loop_ }; // SAFETY: loop_ptr comes from a valid uv stream whose loop has been // registered with a V8 context. let context = unsafe { clone_context_from_uv_loop(&mut isolate, loop_ptr) }; v8::scope!(let handle_scope, &mut isolate); let context_local = v8::Local::new(handle_scope, context); let $scope = &mut v8::ContextScope::new(handle_scope, context_local); // SAFETY: js_handle was stored via set_js_handle and is valid while // the stream is alive. let Some(js_global) = (unsafe { (*handle_data.js_handle.get()).to_global($scope) }) else { return; }; let $this: v8::Local<v8::Object> = v8::Local::new($scope, js_global); $body }}; } /// Connection callback for `uv_listen`. Fires `this.onconnection(status)` on /// the server handle's JS object. /// /// # Safety /// Must only be called by libuv as a `uv_connection_cb`. `server` must be a /// valid `uv_stream_t` whose `data` points to a live `StreamHandleData`. #[allow( unused_unsafe, clippy::undocumented_unsafe_blocks, reason = "macro expands unsafe blocks inside unsafe fn" )] pub(crate) unsafe extern "C" fn server_connection_cb( server: *mut UvStream, status: i32, ) { with_js_handle!(server, |scope, this| { let key = v8::String::new(scope, "onconnection").unwrap(); if let Some(onconnection) = this.get(scope, key.into()) && let Ok(func) = v8::Local::<v8::Function>::try_from(onconnection) { let status_val: v8::Local<v8::Value> = v8::Integer::new(scope, status).into(); func.call(scope, this.into(), &[status_val]); } }); } // Wraps a UvConnect request together with the JS req object (TCPConnectWrap) // so both stay alive until the callback fires. #[repr(C)] struct ConnectReqData { uv_req: UvConnect, js_req: v8::Global<v8::Object>, } /// Connect callback for `uv_tcp_connect`. Fires `req.oncomplete(status, /// handle, req, readable, writable)` matching Node.js ConnectionWrap::AfterConnect. /// /// # Safety /// Must only be called by libuv as a `uv_connect_cb`. `req` must point to a /// `ConnectReqData` allocated via `Box::into_raw`. #[allow( unused_unsafe, clippy::undocumented_unsafe_blocks, reason = "macro expands unsafe blocks inside unsafe fn" )] unsafe extern "C" fn connect_cb(req: *mut UvConnect, status: i32) { // SAFETY: req points to a ConnectReqData allocated via Box::into_raw // in the connect() op. We reclaim ownership here. let stream = unsafe { (*req).handle as *mut UvStream }; let req_data = unsafe { Box::from_raw(req as *mut ConnectReqData) }; let js_req_global = req_data.js_req; with_js_handle!(stream, |scope, this| { let js_req = v8::Local::new(scope, &js_req_global); let oncomplete_key = v8::String::new(scope, "oncomplete").unwrap(); if let Some(oncomplete) = js_req.get(scope, oncomplete_key.into()) && let Ok(func) = v8::Local::<v8::Function>::try_from(oncomplete) { let status_val: v8::Local<v8::Value> = v8::Integer::new(scope, status).into(); let readable: v8::Local<v8::Value> = v8::Boolean::new(scope, status == 0).into(); let writable: v8::Local<v8::Value> = v8::Boolean::new(scope, status == 0).into(); func.call( scope, js_req.into(), &[status_val, this.into(), js_req.into(), readable, writable], ); } }); } // -- TCPWrap struct -- #[derive(CppgcInherits)] #[cppgc_inherits_from(LibUvStreamWrap)] #[repr(C)] pub struct TCPWrap { base: LibUvStreamWrap, handle: Cell<Option<OwnedPtr<UvTcp>>>, socket_type: Cell<SocketType>, /// Permission token from DNS lookup. When set, connect() checks /// permissions against the original hostname, but only for an address /// that is among the token's resolved IPs. net_perm_token: RefCell<Option<deno_net::ops::NetPermToken>>, } // SAFETY: TCPWrap is a cppgc-managed object; the GC traces it via the base field. unsafe impl GarbageCollected for TCPWrap { fn get_name(&self) -> &'static std::ffi::CStr { c"TCP" } fn trace(&self, visitor: &mut v8::cppgc::Visitor) { self.base.trace(visitor); } } impl Drop for TCPWrap { fn drop(&mut self) { self.base.detach_stream(); } } impl TCPWrap { fn new(socket_type: SocketType, op_state: &mut OpState) -> Self { let loop_ = &**op_state.borrow::<Box<UvLoop>>() as *const UvLoop as *mut UvLoop; let tcp = OwnedPtr::from_box(Box::<UvTcp>::new_uninit()); // SAFETY: loop_ and tcp are valid pointers for uv_tcp_init let err = unsafe { uv_compat::uv_tcp_init(loop_, tcp.as_mut_ptr().cast()) }; if err == 0 { // SAFETY: uv_tcp_init succeeded, memory is initialized let tcp = unsafe { tcp.cast::<UvTcp>() }; let provider = if socket_type == SocketType::Server { ProviderType::TcpServerWrap } else { ProviderType::TcpWrap }; let base = LibUvStreamWrap::new( HandleWrap::create( AsyncWrap::create(op_state, provider as i32), Some(Handle::New(tcp.as_ptr().cast())), ), -1, // fd not known until bind/connect tcp.as_ptr().cast(), ); // SAFETY: tcp pointer is valid; setting data field for libuv callbacks unsafe { (*tcp.as_mut_ptr()).data = base.handle_data_ptr(); } Self { base, handle: Cell::new(Some(tcp)), socket_type: Cell::new(socket_type), net_perm_token: RefCell::new(None), } } else { // Error path - create with null handle let provider = if socket_type == SocketType::Server { ProviderType::TcpServerWrap } else { ProviderType::TcpWrap }; // SAFETY: tcp was allocated via OwnedPtr::from_box but uv_tcp_init // failed, so the memory is uninitialized. Free it without dropping. unsafe { let layout = std::alloc::Layout::new::<UvTcp>(); std::alloc::dealloc(tcp.as_mut_ptr() as *mut u8, layout); std::mem::forget(tcp); } Self { base: LibUvStreamWrap::new( HandleWrap::create( AsyncWrap::create(op_state, provider as i32), None, ), -1, std::ptr::null(), ), handle: Cell::new(None), socket_type: Cell::new(socket_type), net_perm_token: RefCell::new(None), } } } fn tcp_ptr(&self) -> *mut UvTcp { // Briefly take the `OwnedPtr` out of the cell to read its raw address, then // put it straight back so ownership is unchanged. match self.handle.take() { Some(h) => { let ptr = h.as_mut_ptr(); self.handle.set(Some(h)); ptr } None => std::ptr::null_mut(), } } /// Detach the underlying TCP handle, transferring ownership of the `UvTcp` /// allocation to the caller. After this the `TCPWrap` no longer owns or /// frees the handle, so exactly one owner (the caller) remains responsible /// for freeing it. Returns null if the handle was already detached or never /// allocated. pub fn detach(&self) -> *mut UvTcp { self.base.detach_stream(); match self.handle.take() { Some(h) => h.into_raw(), None => std::ptr::null_mut(), } } /// Get the underlying uv_stream_t pointer. Used by TLSWrap to attach /// to the TCP stream for encrypted I/O. pub fn stream_ptr(&self) -> *mut UvStream { self.base.stream_ptr() } /// Decide which host to check `--allow-net` against when connecting to /// `address`. If a `node:dns.lookup()` token was installed and `address` /// is one of the IPs that lookup resolved, the original hostname is used /// (so e.g. `--allow-net=example.com` authorizes example.com's real IPs). /// Otherwise the literal `address` is checked. This prevents a token /// obtained for one host from being grafted onto an unrelated IP via a /// custom `lookup` callback, and mirrors `op_net_connect_tcp`. fn net_perm_check_host(&self, address: &str) -> String { match &*self.net_perm_token.borrow() { Some(token) => token.check_host(address).to_string(), None => address.to_string(), } } fn bind_inner( &self, state: &mut OpState, address: &str, port: i32, flags: u32, ) -> Result<i32, deno_permissions::PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_net(&(address, Some(port as u16)), "node:net.listen()")?; let addr_str = format!("{}:{}", address, port); let socket_addr = match addr_str.to_socket_addrs() { Ok(mut addrs) => match addrs.next() { Some(addr) => addr, None => return Ok(-1), }, Err(_) => return Ok(-1), }; // SAFETY: tcp is valid; socket2 SockAddr is properly initialized from // a resolved std::net::SocketAddr. unsafe { let tcp = self.tcp_ptr(); if tcp.is_null() { return Ok(-1); } let sock_addr = Socket2SockAddr::from(socket_addr); Ok(uv_compat::uv_tcp_bind( tcp, sock_addr.as_ptr() as *const _, #[allow(clippy::unnecessary_cast, reason = "depends on platform")] { sock_addr.len() as u32 }, flags, )) } } } // -- ops -- #[op2(inherit = LibUvStreamWrap)] impl TCPWrap { #[constructor] #[cppgc] fn new_tcp( #[smi] socket_type: i32, op_state: &mut OpState, #[this] this: v8::Global<v8::Object>, scope: &mut v8::PinScope, ) -> TCPWrap { let st = if socket_type == 1 { SocketType::Server } else { SocketType::Socket }; let tcp = TCPWrap::new(st, op_state); // Store the JS handle so callbacks (connect, read, etc.) can find it. tcp.base.set_js_handle(this, scope); tcp } #[fast] fn open(&self, state: &mut OpState, #[smi] fd: i32) -> i32 { if fd < 0 { return uv_compat::UV_EBADF; } // See `FdTable::begin_uv_adopt` for the duplicate-fd policy (stdio and // inherited extra stdio fds may be adopted — the latter covers an // inherited TCP socket claimed via net.Socket({ fd }) or // server.listen({ fd }); other tracked fds are rejected). let Some(was_inherited) = state.borrow::<deno_io::FdTable>().begin_uv_adopt(fd) else { return -libc::EEXIST; }; let tcp = self.tcp_ptr(); if tcp.is_null() { return uv_compat::UV_EBADF; } // SAFETY: tcp handle is valid (null-checked above); fd is validated above. // Platform-specific non-blocking setup and uv_tcp_open are safe with valid args. let ret = unsafe { #[cfg(unix)] { let flags = libc::fcntl(fd, libc::F_GETFL); if flags != -1 { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK); } // Match Node: a wrap constructed with `new TCP(SERVER)` opens the // fd as a listener; `new TCP(SOCKET)` as a connected stream. Node's // libuv layer doesn't autodetect; it uses the wrap's intent to // decide how to register the fd, then `listen()` on a listening fd // is a no-op at the kernel level. if self.socket_type.get() == SocketType::Server { uv_compat::uv_tcp_open_listener(tcp, fd) } else { uv_compat::uv_tcp_open(tcp, fd) } } #[cfg(windows)] { use windows_sys::Win32::Networking::WinSock::FIONBIO; use windows_sys::Win32::Networking::WinSock::ioctlsocket; let mut nonblocking: u32 = 1; ioctlsocket(fd as usize, FIONBIO, &mut nonblocking); uv_compat::uv_tcp_open(tcp, fd) } }; // Track the adopted fd so it can't be re-adopted by another wrap and is // dropped from the FdTable on close. if ret == 0 { state .borrow_mut::<deno_io::FdTable>() .finish_uv_adopt(fd, was_inherited); self.base.set_fd(fd); } ret } /// Override the base close to drop the `FdTable` entry registered by /// `open()` before `uv_close` frees the fd. A no-op for sockets that were /// never adopted via `open()`; their fd stays -1. #[reentrant] fn close( &self, op_state: Rc<RefCell<OpState>>, #[this] this: v8::Global<v8::Object>, scope: &mut v8::PinScope<'_, '_>, #[scoped] cb: Option<v8::Global<v8::Function>>, ) -> Result<(), ResourceError> { let fd = self.base.get_fd(); if fd >= 0 { op_state .borrow_mut() .borrow_mut::<deno_io::FdTable>() .remove(fd); // Clear the cached fd so the removal is one-shot. `close_handle()` // guards against double frees of the libuv handle, but a second // `close()` would still re-run this TCP-specific removal and could // drop an unrelated FdTable entry if the OS has since reused the fd // number. self.base.set_fd(-1); } // Stop any active read before clearing the JS handle so the read-callback // registry drops its strong `Global<this>` and the wrapper can be GC'd // after close. The stream pointer is still valid here (close_handle runs // afterwards). self.base.read_stop_internal(); self.base.clear_js_handle(); self .base .handle_wrap() .close_handle(op_state, this, scope, cb) } #[fast] fn socket_type_for_ipc(&self) -> i32 { // Match Node's `net.Native` IPC handle type: the receiver needs to know // whether to reopen the fd as a connected stream (`uv_tcp_open`) or as a // listening socket (`uv_tcp_open_listener`). 0 = SOCKET, 1 = SERVER, to // mirror the constructor argument. match self.socket_type.get() { SocketType::Server => 1, SocketType::Socket => 0, } } #[fast] fn fd_for_ipc(&self) -> i32 { #[cfg(unix)] { let tcp = self.tcp_ptr(); if tcp.is_null() { return -1; } // SAFETY: tcp is valid (null-checked above). unsafe { uv_compat::uv_tcp_fd_for_ipc(tcp) } } // Windows IPC handle passing doesn't use SCM_RIGHTS-style fd transfer; // returning -1 surfaces "not supported" to the JS handle-passing path. #[cfg(not(unix))] -1 } #[fast] fn open_from_rid(&self, state: &mut OpState, #[smi] rid: ResourceId) -> i32 { let tcp = self.tcp_ptr(); if tcp.is_null() { return -1; } let fd = state .resource_table .get::<TcpStreamResource>(rid) .ok() .and_then(|r| r.dup_raw_fd()); match fd { // SAFETY: tcp is valid (null-checked above); fd is a valid dup'd descriptor. Some(fd) => unsafe { uv_compat::uv_tcp_open(tcp, fd) }, None => -1, } } #[nofast] fn bind( &self, state: &mut OpState, #[string] address: &str, #[smi] port: i32, ) -> Result<i32, deno_permissions::PermissionCheckError> { self.bind_inner(state, address, port, 0) } #[nofast] #[rename("bindWithFlags")] fn bind_with_flags( &self, state: &mut OpState, #[string] address: &str, #[smi] port: i32, #[smi] flags: u32, ) -> Result<i32, deno_permissions::PermissionCheckError> { self.bind_inner(state, address, port, flags) } #[nofast] fn bind6( &self, state: &mut OpState, #[string] address: &str, #[smi] port: i32, #[smi] flags: u32, ) -> Result<i32, deno_permissions::PermissionCheckError> { state .borrow_mut::<PermissionsContainer>() .check_net(&(address, Some(port as u16)), "node:net.listen()")?; let addr_str = format!("{}:{}", address, port); let socket_addr = match addr_str.to_socket_addrs() { Ok(mut addrs) => match addrs.next() { Some(addr) => addr, None => return Ok(-1), }, Err(_) => return Ok(-1), }; // SAFETY: tcp is valid; socket2 SockAddr is properly initialized. unsafe { let tcp = self.tcp_ptr(); if tcp.is_null() { return Ok(-1); } let sock_addr = Socket2SockAddr::from(socket_addr); Ok(uv_compat::uv_tcp_bind( tcp, sock_addr.as_ptr() as *const _, #[allow(clippy::unnecessary_cast, reason = "on some platforms")] { sock_addr.len() as u32 }, flags, )) } } #[fast] fn listen(&self, #[smi] backlog: i32) -> i32 { let tcp = self.tcp_ptr(); if tcp.is_null() { return -1; } // SAFETY: tcp is valid (null-checked above); cast to uv_stream_t is // safe because uv_tcp_t embeds uv_stream_t at offset 0. unsafe { let stream = tcp as *mut UvStream; uv_compat::uv_listen(stream, backlog, Some(server_connection_cb)) } } #[fast] fn accept(&self, #[cppgc] client: &TCPWrap) -> i32 { let server_tcp = self.tcp_ptr(); let client_tcp = client.tcp_ptr(); if server_tcp.is_null() || client_tcp.is_null() { return -1; } // SAFETY: both tcp pointers are valid (null-checked above); cast to // uv_stream_t is safe per uv_tcp_t layout. unsafe { uv_compat::uv_accept( server_tcp as *mut UvStream, client_tcp as *mut UvStream, ) } } /// Take the underlying TCP stream from this handle and place it in the /// resource table as a `TcpStreamResource`. This detaches the stream /// from libuv. Returns the resource ID of the stream, which can then /// be passed to ext/http ops (e.g. for WebSocket upgrade). fn take_stream( &self, state: &mut OpState, ) -> Result<ResourceId, deno_error::JsErrorBox> { let tcp = self.tcp_ptr(); if tcp.is_null() { return Err(deno_error::JsErrorBox::generic("TCP handle is closed")); } // SAFETY: tcp is valid (null-checked above) let tcp_stream = unsafe { (*tcp).take_stream() }.ok_or_else(|| { deno_error::JsErrorBox::generic( "TCP handle has no active stream - already taken or not connected", ) })?; let (read_half, write_half) = tcp_stream.into_split(); let resource = TcpStreamResource::new((read_half, write_half)); Ok(state.resource_table.add(resource)) } #[fast] fn set_no_delay(&self, enable: bool) -> i32 { let tcp = self.tcp_ptr(); if tcp.is_null() { return -1; } // SAFETY: tcp is valid (null-checked above). unsafe { uv_compat::uv_tcp_nodelay(tcp, enable as i32) } } /// Enable/disable `SO_KEEPALIVE`. `delay` is the idle time in seconds /// before the first keepalive probe (Node passes seconds here). Matches /// Node's `TCPWrap::SetKeepAlive`, which libraries such as `tedious` /// rely on to keep tunneled/long-lived connections from being reaped. #[fast] #[rename("setKeepAlive")] fn set_keep_alive(&self, enable: bool, #[smi] delay: i32) -> i32 { let tcp = self.tcp_ptr(); if tcp.is_null() { return -1; } let delay = delay.max(0) as u32; // SAFETY: tcp is valid (null-checked above). unsafe { uv_compat::uv_tcp_keepalive(tcp, enable as i32, delay) } } /// Set SO_LINGER to 0 so the next close sends RST instead of FIN. #[fast] fn reset(&self) -> i32 { let tcp = self.tcp_ptr(); if tcp.is_null() { return -1; } // SAFETY: tcp is valid (null-checked above). unsafe { uv_compat::uv_tcp_reset(tcp) } } /// Store the DNS-lookup permission token for later permission checks. /// We keep both the original hostname and the set of IPs that lookup /// resolved, so connect() can check against the hostname only for an /// address the token actually resolved (see `net_perm_check_host`), /// matching Node.js netPermToken behavior. #[nofast] fn set_net_perm_token(&self, #[cppgc] token: &deno_net::ops::NetPermToken) { *self.net_perm_token.borrow_mut() = Some(deno_net::ops::NetPermToken { hostname: token.hostname.clone(), port: token.port, resolved_ips: token.resolved_ips.clone(), }); } /// Connect to an address. Takes (req, address, port) where req is a /// TCPConnectWrap with oncomplete callback, matching Node.js API. #[nofast] fn connect( &self, state: &mut OpState, js_req: v8::Local<v8::Object>, #[string] address: &str, #[smi] port: i32, scope: &mut v8::PinScope, ) -> Result<i32, deno_permissions::PermissionCheckError> { // If a hostname was stored from DNS lookup, check permissions against // the original hostname instead of the resolved IP address, but only // when `address` is one of the token's resolved IPs. let check_host = self.net_perm_check_host(address); state.borrow_mut::<PermissionsContainer>().check_net( &(check_host.as_str(), Some(port as u16)), "node:net.connect()", )?; let addr_str = format!("{}:{}", address, port); let socket_addr = match addr_str.to_socket_addrs() { Ok(mut addrs) => match addrs.next() { Some(addr) => addr, None => return Ok(-1), }, Err(_) => return Ok(-1), }; // Post-resolution deny check: verify the resolved IP is not denied. // This prevents numeric hostname aliases (e.g. 2130706433, 0x7f000001) // from bypassing --deny-net rules that target the resolved IP. state .borrow_mut::<PermissionsContainer>() .check_net_resolved( &socket_addr.ip(), socket_addr.port(), "node:net.connect()", )?; let tcp = self.tcp_ptr(); if tcp.is_null() { return Ok(-1); } let sock_addr = Socket2SockAddr::from(socket_addr); let js_req_global = v8::Global::new(scope, js_req); let mut connect_req = Box::new(ConnectReqData { uv_req: uv_compat::new_connect(), js_req: js_req_global, }); let req_ptr = &mut connect_req.uv_req as *mut UvConnect; let _ = Box::into_raw(connect_req); // SAFETY: tcp is valid (null-checked above); req_ptr is a valid // heap-allocated UvConnect; sock_addr is properly initialized. // connect_req is leaked and will be reclaimed in connect_cb. let ret = unsafe { uv_compat::uv_tcp_connect( req_ptr, tcp, sock_addr.as_ptr() as *const _, Some(connect_cb), ) }; if ret != 0 { // SAFETY: uv_tcp_connect failed synchronously; reclaim the request. unsafe { let _ = Box::from_raw(req_ptr as *mut ConnectReqData); } } Ok(ret) } /// Connect to an IPv6 address. uv_tcp_connect handles both v4 and v6. #[nofast] fn connect6( &self, state: &mut OpState, js_req: v8::Local<v8::Object>, #[string] address: &str, #[smi] port: i32, scope: &mut v8::PinScope, ) -> Result<i32, deno_permissions::PermissionCheckError> { let check_host = self.net_perm_check_host(address); state.borrow_mut::<PermissionsContainer>().check_net( &(check_host.as_str(), Some(port as u16)), "node:net.connect()", )?; let addr_str = format!("{}:{}", address, port); let socket_addr = match addr_str.to_socket_addrs() { Ok(mut addrs) => match addrs.next() { Some(addr) => addr, None => return Ok(-1), }, Err(_) => return Ok(-1), }; // Post-resolution deny check for connect6 as well. state .borrow_mut::<PermissionsContainer>() .check_net_resolved( &socket_addr.ip(), socket_addr.port(), "node:net.connect()", )?; let tcp = self.tcp_ptr(); if tcp.is_null() { return Ok(-1); } let sock_addr = Socket2SockAddr::from(socket_addr); let js_req_global = v8::Global::new(scope, js_req); let mut connect_req = Box::new(ConnectReqData { uv_req: uv_compat::new_connect(), js_req: js_req_global, }); let req_ptr = &mut connect_req.uv_req as *mut UvConnect; let _ = Box::into_raw(connect_req); // SAFETY: same as connect() above. let ret = unsafe { uv_compat::uv_tcp_connect( req_ptr, tcp, sock_addr.as_ptr() as *const _, Some(connect_cb), ) }; if ret != 0 { // SAFETY: uv_tcp_connect failed synchronously; reclaim the request. unsafe { let _ = Box::from_raw(req_ptr as *mut ConnectReqData); } } Ok(ret) } /// Populates the output object with remote address info. Returns 0 on /// success, negative error code on failure. Matches Node.js API: /// `handle.getpeername(out)` where out gets {address, port, family}. #[nofast] fn getpeername( &self, out: v8::Local<v8::Object>, scope: &mut v8::PinScope, ) -> i32 { let tcp = self.tcp_ptr(); if tcp.is_null() { return uv_compat::UV_EBADF; } // SAFETY: tcp is valid (null-checked above); storage is properly sized. unsafe { let mut storage = std::mem::MaybeUninit::<socket2::SockAddr>::uninit(); let mut len = std::mem::size_of::<socket2::SockAddr>() as i32; let ret = uv_compat::uv_tcp_getpeername( tcp, storage.as_mut_ptr() as *mut _, &mut len, ); if ret != 0 { return ret; } populate_sockaddr_object(scope, out, &storage.assume_init()); 0 } } /// Populates the output object with local address info. Returns 0 on /// success, negative error code on failure. Matches Node.js API: /// `handle.getsockname(out)` where out gets {address, port, family}. #[nofast] fn getsockname( &self, out: v8::Local<v8::Object>, scope: &mut v8::PinScope, ) -> i32 { let tcp = self.tcp_ptr(); if tcp.is_null() { return uv_compat::UV_EBADF; } // SAFETY: tcp is valid (null-checked above); storage is properly sized. unsafe { let mut storage = std::mem::MaybeUninit::<socket2::SockAddr>::uninit(); let mut len = std::mem::size_of::<socket2::SockAddr>() as i32; let ret = uv_compat::uv_tcp_getsockname( tcp, storage.as_mut_ptr() as *mut _, &mut len, ); if ret != 0 { return ret; } populate_sockaddr_object(scope, out, &storage.assume_init()); 0 } } } // -- helpers -- /// Populate a JS object with {address, port, family} from a socket2::SockAddr. fn populate_sockaddr_object( scope: &mut v8::PinScope, obj: v8::Local<v8::Object>, sock_addr: &socket2::SockAddr, ) { let (address, port, family) = if let Some(addr) = sock_addr.as_socket_ipv4() { (addr.ip().to_string(), addr.port(), "IPv4") } else if let Some(addr) = sock_addr.as_socket_ipv6() { (addr.ip().to_string(), addr.port(), "IPv6") } else { return; }; let addr_key = v8::String::new(scope, "address").unwrap(); let addr_val = v8::String::new(scope, &address).unwrap(); obj.set(scope, addr_key.into(), addr_val.into()); let port_key = v8::String::new(scope, "port").unwrap(); let port_val = v8::Integer::new(scope, port as i32); obj.set(scope, port_key.into(), port_val.into()); let family_key = v8::String::new(scope, "family").unwrap(); let family_val = v8::String::new(scope, family).unwrap(); obj.set(scope, family_key.into(), family_val.into()); }