feat: harden proxy_dns hooks and improve ipv6 compatibility
This commit is contained in:
1 parent
904d6ad3e1
commit
7611cd8a58
4 files changed
+274
-72
No files matched your search
@@ -250,6 +250,8 @@ proxychains4 --group jp curl https://ifconfig.me
|
||||
|
||||
IPv6 notes:
|
||||
- IPv6 targets are supported through SOCKS5/HTTP proxy hops.
|
||||
- With `proxy_dns` enabled, hostname lookups avoid system-DNS fallback paths.
|
||||
- `getaddrinfo(AF_INET6)` is handled via IPv4-mapped fake addresses for compatibility.
|
||||
- SOCKS4/4a does not support IPv6 target addresses.
|
||||
|
||||
Validation and discovery:
|
||||
|
||||
+150
-56
@@ -50,7 +50,8 @@ impl HookState {
|
||||
/// Global hook state
|
||||
static HOOK_STATE: OnceLock<HookState> = OnceLock::new();
|
||||
/// Track addrinfo/sockaddr allocations created by our getaddrinfo hook.
|
||||
static CUSTOM_ADDRINFO_ALLOCATIONS: OnceLock<Mutex<HashMap<usize, usize>>> = OnceLock::new();
|
||||
static CUSTOM_ADDRINFO_ALLOCATIONS: OnceLock<Mutex<HashMap<usize, CustomAddrinfoAllocation>>> =
|
||||
OnceLock::new();
|
||||
|
||||
thread_local! {
|
||||
static GETHOSTBYNAME_STORE: RefCell<Option<Box<HostentStore>>> = const { RefCell::new(None) };
|
||||
@@ -63,7 +64,13 @@ struct HostentStore {
|
||||
hostent: libc::hostent,
|
||||
}
|
||||
|
||||
fn custom_alloc_map() -> &'static Mutex<HashMap<usize, usize>> {
|
||||
#[derive(Clone, Copy)]
|
||||
struct CustomAddrinfoAllocation {
|
||||
sockaddr_ptr: usize,
|
||||
family: libc::c_int,
|
||||
}
|
||||
|
||||
fn custom_alloc_map() -> &'static Mutex<HashMap<usize, CustomAddrinfoAllocation>> {
|
||||
CUSTOM_ADDRINFO_ALLOCATIONS.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
@@ -93,6 +100,106 @@ unsafe fn write_hostname_to_buf(host: *mut c_char, hostlen: libc::socklen_t, hos
|
||||
0
|
||||
}
|
||||
|
||||
unsafe fn store_fake_addrinfo_result(
|
||||
fake_ip: std::net::Ipv4Addr,
|
||||
service_port: u16,
|
||||
hints: *const libc::addrinfo,
|
||||
requested_family: libc::c_int,
|
||||
res: *mut *mut libc::addrinfo,
|
||||
) -> c_int {
|
||||
match requested_family {
|
||||
libc::AF_INET => {
|
||||
let mut sockaddr: libc::sockaddr_in = std::mem::zeroed();
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
|
||||
{
|
||||
sockaddr.sin_len = std::mem::size_of::<libc::sockaddr_in>() as u8;
|
||||
}
|
||||
sockaddr.sin_family = libc::AF_INET as libc::sa_family_t;
|
||||
sockaddr.sin_port = service_port.to_be();
|
||||
sockaddr.sin_addr = libc::in_addr {
|
||||
s_addr: u32::from_ne_bytes(fake_ip.octets()),
|
||||
};
|
||||
|
||||
let sockaddr_ptr = Box::into_raw(Box::new(sockaddr));
|
||||
let mut ai: libc::addrinfo = std::mem::zeroed();
|
||||
ai.ai_flags = if hints.is_null() { 0 } else { (*hints).ai_flags };
|
||||
ai.ai_family = libc::AF_INET;
|
||||
ai.ai_socktype = if hints.is_null() {
|
||||
libc::SOCK_STREAM
|
||||
} else {
|
||||
(*hints).ai_socktype
|
||||
};
|
||||
ai.ai_protocol = if hints.is_null() {
|
||||
libc::IPPROTO_TCP
|
||||
} else {
|
||||
(*hints).ai_protocol
|
||||
};
|
||||
ai.ai_addrlen = std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t;
|
||||
ai.ai_addr = sockaddr_ptr as *mut libc::sockaddr;
|
||||
ai.ai_canonname = std::ptr::null_mut();
|
||||
ai.ai_next = std::ptr::null_mut();
|
||||
|
||||
let ai_ptr = Box::into_raw(Box::new(ai));
|
||||
*res = ai_ptr;
|
||||
custom_alloc_map().lock().insert(
|
||||
ai_ptr as usize,
|
||||
CustomAddrinfoAllocation {
|
||||
sockaddr_ptr: sockaddr_ptr as usize,
|
||||
family: libc::AF_INET,
|
||||
},
|
||||
);
|
||||
0
|
||||
}
|
||||
libc::AF_INET6 => {
|
||||
let mut mapped = [0u8; 16];
|
||||
mapped[10] = 0xff;
|
||||
mapped[11] = 0xff;
|
||||
mapped[12..16].copy_from_slice(&fake_ip.octets());
|
||||
|
||||
let mut sockaddr: libc::sockaddr_in6 = std::mem::zeroed();
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
|
||||
{
|
||||
sockaddr.sin6_len = std::mem::size_of::<libc::sockaddr_in6>() as u8;
|
||||
}
|
||||
sockaddr.sin6_family = libc::AF_INET6 as libc::sa_family_t;
|
||||
sockaddr.sin6_port = service_port.to_be();
|
||||
sockaddr.sin6_addr = libc::in6_addr { s6_addr: mapped };
|
||||
sockaddr.sin6_scope_id = 0;
|
||||
|
||||
let sockaddr_ptr = Box::into_raw(Box::new(sockaddr));
|
||||
let mut ai: libc::addrinfo = std::mem::zeroed();
|
||||
ai.ai_flags = if hints.is_null() { 0 } else { (*hints).ai_flags };
|
||||
ai.ai_family = libc::AF_INET6;
|
||||
ai.ai_socktype = if hints.is_null() {
|
||||
libc::SOCK_STREAM
|
||||
} else {
|
||||
(*hints).ai_socktype
|
||||
};
|
||||
ai.ai_protocol = if hints.is_null() {
|
||||
libc::IPPROTO_TCP
|
||||
} else {
|
||||
(*hints).ai_protocol
|
||||
};
|
||||
ai.ai_addrlen = std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t;
|
||||
ai.ai_addr = sockaddr_ptr as *mut libc::sockaddr;
|
||||
ai.ai_canonname = std::ptr::null_mut();
|
||||
ai.ai_next = std::ptr::null_mut();
|
||||
|
||||
let ai_ptr = Box::into_raw(Box::new(ai));
|
||||
*res = ai_ptr;
|
||||
custom_alloc_map().lock().insert(
|
||||
ai_ptr as usize,
|
||||
CustomAddrinfoAllocation {
|
||||
sockaddr_ptr: sockaddr_ptr as usize,
|
||||
family: libc::AF_INET6,
|
||||
},
|
||||
);
|
||||
0
|
||||
}
|
||||
_ => libc::EAI_FAMILY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the hook library
|
||||
pub fn init_hooks(config: Config) -> Result<()> {
|
||||
// Initialize original functions first
|
||||
@@ -161,12 +268,24 @@ pub unsafe fn hook_connect(
|
||||
}
|
||||
|
||||
// Apply DNAT if configured
|
||||
let (final_ip, final_port) = state.config.apply_dnat_ip(&target_ip, target_port);
|
||||
let (dnat_ip, final_port) = state.config.apply_dnat_ip(&target_ip, target_port);
|
||||
|
||||
// Check if this is a fake IP (needs remote DNS resolution)
|
||||
let target_domain = match final_ip {
|
||||
IpAddr::V4(v4) if is_fake_ip(&v4) => state.dns_resolver.get_hostname(&v4),
|
||||
_ => None,
|
||||
// Check if this is a fake IP (needs remote DNS resolution). For IPv6-mapped
|
||||
// fake addresses, collapse to IPv4 so SOCKS4a path can still use the domain.
|
||||
let (final_ip, target_domain) = match dnat_ip {
|
||||
IpAddr::V4(v4) if is_fake_ip(&v4) => (IpAddr::V4(v4), state.dns_resolver.get_hostname(&v4)),
|
||||
IpAddr::V6(v6) => {
|
||||
if let Some(v4) = v6.to_ipv4_mapped() {
|
||||
if is_fake_ip(&v4) {
|
||||
(IpAddr::V4(v4), state.dns_resolver.get_hostname(&v4))
|
||||
} else {
|
||||
(IpAddr::V6(v6), None)
|
||||
}
|
||||
} else {
|
||||
(IpAddr::V6(v6), None)
|
||||
}
|
||||
}
|
||||
_ => (dnat_ip, None),
|
||||
};
|
||||
|
||||
// Check if proxy DNS is enabled and we have a domain
|
||||
@@ -265,65 +384,36 @@ pub unsafe fn hook_getaddrinfo(
|
||||
return original_getaddrinfo(node, service, hints, res);
|
||||
}
|
||||
|
||||
// Preserve behavior for literal IPv4 strings.
|
||||
if hostname.parse::<std::net::Ipv4Addr>().is_ok() {
|
||||
// Preserve behavior for literal IP strings.
|
||||
if hostname.parse::<std::net::IpAddr>().is_ok() {
|
||||
return original_getaddrinfo(node, service, hints, res);
|
||||
}
|
||||
|
||||
// Respect non-IPv4 family hints.
|
||||
if !hints.is_null() {
|
||||
let ai_family = (*hints).ai_family;
|
||||
if ai_family != libc::AF_INET && ai_family != libc::AF_UNSPEC {
|
||||
return original_getaddrinfo(node, service, hints, res);
|
||||
let requested_family = if hints.is_null() {
|
||||
libc::AF_INET
|
||||
} else {
|
||||
match (*hints).ai_family {
|
||||
libc::AF_UNSPEC => libc::AF_INET,
|
||||
libc::AF_INET => libc::AF_INET,
|
||||
libc::AF_INET6 => libc::AF_INET6,
|
||||
_ => return libc::EAI_FAMILY,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Generate fake IP
|
||||
let fake_ip = match state.dns_resolver.resolve(&hostname) {
|
||||
Ok(ip) => ip,
|
||||
Err(e) => {
|
||||
error!("Failed to resolve {} in hook_getaddrinfo: {}", hostname, e);
|
||||
return original_getaddrinfo(node, service, hints, res);
|
||||
return libc::EAI_NONAME;
|
||||
}
|
||||
};
|
||||
|
||||
let service_port = parse_service_port(service);
|
||||
|
||||
let mut sockaddr: libc::sockaddr_in = std::mem::zeroed();
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
|
||||
{
|
||||
sockaddr.sin_len = std::mem::size_of::<libc::sockaddr_in>() as u8;
|
||||
let ret = store_fake_addrinfo_result(fake_ip, service_port, hints, requested_family, res);
|
||||
if ret != 0 {
|
||||
return ret;
|
||||
}
|
||||
sockaddr.sin_family = libc::AF_INET as libc::sa_family_t;
|
||||
sockaddr.sin_port = service_port.to_be();
|
||||
sockaddr.sin_addr = libc::in_addr {
|
||||
s_addr: u32::from_ne_bytes(fake_ip.octets()),
|
||||
};
|
||||
|
||||
let sockaddr_ptr = Box::into_raw(Box::new(sockaddr));
|
||||
let mut ai: libc::addrinfo = std::mem::zeroed();
|
||||
ai.ai_flags = if hints.is_null() { 0 } else { (*hints).ai_flags };
|
||||
ai.ai_family = libc::AF_INET;
|
||||
ai.ai_socktype = if hints.is_null() {
|
||||
libc::SOCK_STREAM
|
||||
} else {
|
||||
(*hints).ai_socktype
|
||||
};
|
||||
ai.ai_protocol = if hints.is_null() {
|
||||
libc::IPPROTO_TCP
|
||||
} else {
|
||||
(*hints).ai_protocol
|
||||
};
|
||||
ai.ai_addrlen = std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t;
|
||||
ai.ai_addr = sockaddr_ptr as *mut libc::sockaddr;
|
||||
ai.ai_canonname = std::ptr::null_mut();
|
||||
ai.ai_next = std::ptr::null_mut();
|
||||
|
||||
let ai_ptr = Box::into_raw(Box::new(ai));
|
||||
*res = ai_ptr;
|
||||
custom_alloc_map()
|
||||
.lock()
|
||||
.insert(ai_ptr as usize, sockaddr_ptr as usize);
|
||||
|
||||
debug!("Assigned fake IP {} for {}", fake_ip, hostname);
|
||||
|
||||
@@ -361,8 +451,8 @@ pub unsafe fn hook_gethostbyname(name: *const c_char) -> *mut libc::hostent {
|
||||
return original_gethostbyname(name);
|
||||
}
|
||||
|
||||
// Preserve behavior for literal IPv4 strings.
|
||||
if hostname.parse::<std::net::Ipv4Addr>().is_ok() {
|
||||
// Preserve behavior for literal IP strings.
|
||||
if hostname.parse::<std::net::IpAddr>().is_ok() {
|
||||
return original_gethostbyname(name);
|
||||
}
|
||||
|
||||
@@ -371,7 +461,7 @@ pub unsafe fn hook_gethostbyname(name: *const c_char) -> *mut libc::hostent {
|
||||
Ok(ip) => ip,
|
||||
Err(e) => {
|
||||
error!("Failed to resolve {} in hook_gethostbyname: {}", hostname, e);
|
||||
return original_gethostbyname(name);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -415,8 +505,12 @@ pub unsafe fn hook_freeaddrinfo(res: *mut libc::addrinfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(sockaddr_ptr) = custom_alloc_map().lock().remove(&(res as usize)) {
|
||||
let _ = Box::from_raw(sockaddr_ptr as *mut libc::sockaddr_in);
|
||||
if let Some(alloc) = custom_alloc_map().lock().remove(&(res as usize)) {
|
||||
if alloc.family == libc::AF_INET6 {
|
||||
let _ = Box::from_raw(alloc.sockaddr_ptr as *mut libc::sockaddr_in6);
|
||||
} else {
|
||||
let _ = Box::from_raw(alloc.sockaddr_ptr as *mut libc::sockaddr_in);
|
||||
}
|
||||
let _ = Box::from_raw(res);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! chain on the original socket handle.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::ffi::{c_void, CStr, CString};
|
||||
use std::mem::{self, ManuallyDrop};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::os::windows::io::FromRawSocket;
|
||||
@@ -17,13 +17,14 @@ use tracing::{debug, error, info, warn};
|
||||
use windows::Win32::Networking::WinSock::{
|
||||
ADDRINFOA, AF_INET, IN_ADDR, IN_ADDR_0, IPPROTO_TCP, SOCKADDR, SOCKADDR_IN, SOCK_STREAM,
|
||||
SOCKET_ERROR, WSAEALREADY, WSAECONNREFUSED, WSAEFAULT, WSAEINPROGRESS, WSAEINVAL,
|
||||
WSAHOST_NOT_FOUND,
|
||||
WSAEWOULDBLOCK, WSAGetLastError, WSASetLastError,
|
||||
};
|
||||
|
||||
use crate::config::{ChainType, Config, ProxyData, ProxyState};
|
||||
use crate::dns::{is_fake_ip, DnsResolver};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::net::{get_ip_from_sockaddr, get_port_from_sockaddr};
|
||||
use crate::net::{get_ip_from_sockaddr, get_ipaddr_from_sockaddr, get_port_from_sockaddr};
|
||||
use crate::proxy::{tunnel_through_proxy, TargetAddress};
|
||||
|
||||
use super::interpose_windows::{
|
||||
@@ -253,27 +254,37 @@ pub unsafe extern "system" fn hook_connect_impl(
|
||||
return original_connect(sock, addr, len);
|
||||
}
|
||||
|
||||
let target_ip = match get_ip_from_sockaddr(addr) {
|
||||
let target_ip = match get_ipaddr_from_sockaddr(addr) {
|
||||
Some(ip) => ip,
|
||||
None => return original_connect(sock, addr, len),
|
||||
};
|
||||
let target_port = get_port_from_sockaddr(addr);
|
||||
|
||||
if state.config.should_bypass(&target_ip) {
|
||||
if state.config.should_bypass_ip(&target_ip) {
|
||||
return original_connect(sock, addr, len);
|
||||
}
|
||||
|
||||
let (final_ip, final_port) = state.config.apply_dnat(&target_ip, target_port);
|
||||
let target_domain = if is_fake_ip(&final_ip) {
|
||||
state.dns_resolver.get_hostname(&final_ip)
|
||||
} else {
|
||||
None
|
||||
let (dnat_ip, final_port) = state.config.apply_dnat_ip(&target_ip, target_port);
|
||||
let (final_ip, target_domain) = match dnat_ip {
|
||||
IpAddr::V4(v4) if is_fake_ip(&v4) => (IpAddr::V4(v4), state.dns_resolver.get_hostname(&v4)),
|
||||
IpAddr::V6(v6) => {
|
||||
if let Some(v4) = v6.to_ipv4_mapped() {
|
||||
if is_fake_ip(&v4) {
|
||||
(IpAddr::V4(v4), state.dns_resolver.get_hostname(&v4))
|
||||
} else {
|
||||
(IpAddr::V6(v6), None)
|
||||
}
|
||||
} else {
|
||||
(IpAddr::V6(v6), None)
|
||||
}
|
||||
}
|
||||
_ => (dnat_ip, None),
|
||||
};
|
||||
|
||||
let target = if let Some(domain) = target_domain {
|
||||
TargetAddress::from_both(IpAddr::V4(final_ip), domain)
|
||||
TargetAddress::from_both(final_ip, domain)
|
||||
} else {
|
||||
TargetAddress::from_ip(IpAddr::V4(final_ip))
|
||||
TargetAddress::from_ip(final_ip)
|
||||
};
|
||||
|
||||
let max_attempts = state.config.max_chain_retries.max(1);
|
||||
@@ -340,7 +351,7 @@ pub unsafe extern "system" fn hook_getaddrinfo_impl(
|
||||
if !phints.is_null() {
|
||||
let hints = &*(phints as *const ADDRINFOA);
|
||||
if hints.ai_family != 0 && hints.ai_family != AF_INET.0 as i32 {
|
||||
return original_getaddrinfo(pnode, pservice, phints, ppresult);
|
||||
return WSAHOST_NOT_FOUND.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +360,7 @@ pub unsafe extern "system" fn hook_getaddrinfo_impl(
|
||||
Err(_) => return original_getaddrinfo(pnode, pservice, phints, ppresult),
|
||||
};
|
||||
|
||||
if hostname.parse::<Ipv4Addr>().is_ok() {
|
||||
if hostname.parse::<IpAddr>().is_ok() {
|
||||
return original_getaddrinfo(pnode, pservice, phints, ppresult);
|
||||
}
|
||||
|
||||
@@ -361,7 +372,7 @@ pub unsafe extern "system" fn hook_getaddrinfo_impl(
|
||||
Ok(ip) => ip,
|
||||
Err(e) => {
|
||||
error!("Failed to resolve {} in hook_getaddrinfo_impl: {}", hostname, e);
|
||||
return original_getaddrinfo(pnode, pservice, phints, ppresult);
|
||||
return WSAHOST_NOT_FOUND.0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -414,7 +425,54 @@ pub unsafe extern "system" fn hook_freeaddrinfo_impl(pres: *mut c_void) {
|
||||
/// Windows gethostbyname hook implementation.
|
||||
#[cfg(windows)]
|
||||
pub unsafe extern "system" fn hook_gethostbyname_impl(name: *const i8) -> *mut c_void {
|
||||
original_gethostbyname(name)
|
||||
let state = match get_hook_state() {
|
||||
Some(s) if s.config.proxy_dns => s,
|
||||
_ => return original_gethostbyname(name),
|
||||
};
|
||||
|
||||
if name.is_null() {
|
||||
WSASetLastError(WSAEINVAL.0);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let hostname = match CStr::from_ptr(name).to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
WSASetLastError(WSAEINVAL.0);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
if hostname.parse::<IpAddr>().is_ok() {
|
||||
return original_gethostbyname(name);
|
||||
}
|
||||
|
||||
if crate::dns::lookup_in_hosts(hostname).is_some() {
|
||||
return original_gethostbyname(name);
|
||||
}
|
||||
|
||||
let fake_ip = match state.dns_resolver.resolve(hostname) {
|
||||
Ok(ip) => ip,
|
||||
Err(e) => {
|
||||
error!("Failed to resolve {} in hook_gethostbyname_impl: {}", hostname, e);
|
||||
WSASetLastError(WSAHOST_NOT_FOUND.0);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
let fake_ip_cstr = match CString::new(fake_ip.to_string()) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
WSASetLastError(WSAHOST_NOT_FOUND.0);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
let result = original_gethostbyname(fake_ip_cstr.as_ptr());
|
||||
if result.is_null() {
|
||||
WSASetLastError(WSAHOST_NOT_FOUND.0);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Windows getnameinfo hook implementation.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! This module provides Windows-specific implementations for socket operations
|
||||
//! using the Winsock2 API.
|
||||
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
/// Get the port from a SOCKADDR structure (Windows-specific)
|
||||
#[cfg(windows)]
|
||||
@@ -18,6 +18,10 @@ pub fn get_port_from_sockaddr(addr: *const core::ffi::c_void) -> u16 {
|
||||
// sin_port: u16 (2 bytes) - network byte order
|
||||
let port_ptr = (addr as *const u8).add(2) as *const u16;
|
||||
u16::from_be(*port_ptr)
|
||||
} else if sa_family == 23 {
|
||||
// SOCKADDR_IN6 has the same port offset.
|
||||
let port_ptr = (addr as *const u8).add(2) as *const u16;
|
||||
u16::from_be(*port_ptr)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
@@ -49,6 +53,28 @@ pub fn get_ip_from_sockaddr(addr: *const core::ffi::c_void) -> Option<Ipv4Addr>
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the IP address (IPv4/IPv6) from a SOCKADDR structure.
|
||||
#[cfg(windows)]
|
||||
pub fn get_ipaddr_from_sockaddr(addr: *const core::ffi::c_void) -> Option<IpAddr> {
|
||||
unsafe {
|
||||
let sa_family = *(addr as *const u16);
|
||||
if sa_family == 2 {
|
||||
let ip_ptr = (addr as *const u8).add(4);
|
||||
let ip_bytes = [*ip_ptr, *ip_ptr.add(1), *ip_ptr.add(2), *ip_ptr.add(3)];
|
||||
Some(IpAddr::V4(Ipv4Addr::from(ip_bytes)))
|
||||
} else if sa_family == 23 {
|
||||
// SOCKADDR_IN6 layout:
|
||||
// sin6_family (2), sin6_port (2), sin6_flowinfo (4), sin6_addr (16), sin6_scope_id (4)
|
||||
let ip_ptr = (addr as *const u8).add(8);
|
||||
let mut octets = [0u8; 16];
|
||||
std::ptr::copy_nonoverlapping(ip_ptr, octets.as_mut_ptr(), 16);
|
||||
Some(IpAddr::V6(Ipv6Addr::from(octets)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a TCP socket (Windows-specific)
|
||||
pub fn create_tcp_socket() -> std::io::Result<std::net::TcpStream> {
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
@@ -126,4 +152,26 @@ mod tests {
|
||||
assert!(is_localhost(&Ipv4Addr::new(127, 0, 0, 1)));
|
||||
assert!(!is_localhost(&Ipv4Addr::new(192, 168, 1, 1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_ipv6_sockaddr_layout() {
|
||||
// Build a minimal SOCKADDR_IN6 byte layout.
|
||||
let mut raw = [0u8; 28];
|
||||
raw[0] = 23; // AF_INET6 low byte
|
||||
raw[1] = 0;
|
||||
raw[2] = 0x13; // port 4919 in network byte order
|
||||
raw[3] = 0x37;
|
||||
raw[8..24].copy_from_slice(&[
|
||||
0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
]);
|
||||
|
||||
let addr_ptr = raw.as_ptr() as *const core::ffi::c_void;
|
||||
assert_eq!(get_port_from_sockaddr(addr_ptr), 4919);
|
||||
assert_eq!(
|
||||
get_ipaddr_from_sockaddr(addr_ptr),
|
||||
Some(IpAddr::V6(Ipv6Addr::new(
|
||||
0x2001, 0x0db8, 0, 0, 0, 0, 0, 1
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user