use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use crate::{ConnectionInfo, ConnectionState, Error, Protocol, Result};
pub fn walk_connections<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<Vec<ConnectionInfo>> {
let tcp_hashinfo_addr = reader
.symbols()
.symbol_address("tcp_hashinfo")
.ok_or_else(|| Error::MissingKernelSymbol {
name: "tcp_hashinfo".into(),
})?;
let ehash_ptr: u64 = reader.read_field(tcp_hashinfo_addr, "inet_hashinfo", "ehash")?;
let ehash_mask: u32 = reader.read_field(tcp_hashinfo_addr, "inet_hashinfo", "ehash_mask")?;
if ehash_ptr == 0 {
return Ok(Vec::new());
}
let mut connections = Vec::new();
let bucket_count = u64::from(ehash_mask) + 1;
for i in 0..bucket_count {
let bucket_size = reader
.symbols()
.struct_size("inet_ehash_bucket")
.unwrap_or(8);
let bucket_addr = ehash_ptr + i * bucket_size;
let chain_first: u64 = match reader.read_field(bucket_addr, "inet_ehash_bucket", "chain") {
Ok(v) => v,
Err(_) => continue,
};
if chain_first == 0 || chain_first & 1 != 0 {
continue;
}
let mut sk_addr = chain_first;
let mut chain_len = 0;
while sk_addr != 0 && sk_addr & 1 == 0 && chain_len < 1000 {
if let Ok(conn) = read_inet_sock(reader, sk_addr) {
connections.push(conn);
}
sk_addr = match reader.read_pointer(sk_addr, "sock_common", "skc_nulls_node") {
Ok(v) => v,
Err(_) => break,
};
chain_len += 1;
}
}
Ok(connections)
}
fn read_inet_sock<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
sk_addr: u64,
) -> Result<ConnectionInfo> {
let sk_common_off = reader
.symbols()
.field_offset("sock", "__sk_common")
.unwrap_or(0);
let common_addr = sk_addr + sk_common_off;
let daddr: u32 = reader.read_field(common_addr, "sock_common", "skc_daddr")?;
let saddr: u32 = reader.read_field(common_addr, "sock_common", "skc_rcv_saddr")?;
let dport: u16 = reader.read_field(common_addr, "sock_common", "skc_dport")?;
let sport: u16 = reader.read_field(common_addr, "sock_common", "skc_num")?;
let state: u8 = reader.read_field(common_addr, "sock_common", "skc_state")?;
let dport = u16::from_be(dport);
Ok(ConnectionInfo {
protocol: Protocol::Tcp,
local_addr: ipv4_to_string(saddr),
local_port: sport,
remote_addr: ipv4_to_string(daddr),
remote_port: dport,
state: ConnectionState::from_raw(state),
pid: None,
})
}
fn ipv4_to_string(addr: u32) -> String {
let bytes = addr.to_le_bytes();
format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3])
}
pub fn walk_connections6<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<Vec<ConnectionInfo>> {
let tcp6_hashinfo_addr = match reader.symbols().symbol_address("tcp6_hashinfo") {
Some(a) => a,
None => return Ok(Vec::new()),
};
let ehash_ptr: u64 = reader.read_field(tcp6_hashinfo_addr, "inet_hashinfo", "ehash")?;
let ehash_mask: u32 = reader.read_field(tcp6_hashinfo_addr, "inet_hashinfo", "ehash_mask")?;
if ehash_ptr == 0 {
return Ok(Vec::new());
}
let mut connections = Vec::new();
let bucket_count = u64::from(ehash_mask) + 1;
for i in 0..bucket_count {
let bucket_size = reader
.symbols()
.struct_size("inet_ehash_bucket")
.unwrap_or(8);
let bucket_addr = ehash_ptr + i * bucket_size;
let chain_first: u64 = match reader.read_field(bucket_addr, "inet_ehash_bucket", "chain") {
Ok(v) => v,
Err(_) => continue,
};
if chain_first == 0 || chain_first & 1 != 0 {
continue;
}
let mut sk_addr = chain_first;
let mut chain_len = 0;
while sk_addr != 0 && sk_addr & 1 == 0 && chain_len < 1000 {
if let Ok(conn) = read_inet6_sock(reader, sk_addr) {
connections.push(conn);
}
sk_addr = match reader.read_pointer(sk_addr, "sock_common", "skc_nulls_node") {
Ok(v) => v,
Err(_) => break,
};
chain_len += 1;
}
}
Ok(connections)
}
fn read_inet6_sock<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
sk_addr: u64,
) -> Result<ConnectionInfo> {
let sk_common_off = reader
.symbols()
.field_offset("sock", "__sk_common")
.unwrap_or(0);
let common_addr = sk_addr + sk_common_off;
let daddr_off = reader
.symbols()
.field_offset("sock_common", "skc_v6_daddr")
.unwrap_or(8);
let saddr_off = reader
.symbols()
.field_offset("sock_common", "skc_v6_rcv_saddr")
.unwrap_or(24);
let daddr_bytes = reader.read_bytes(common_addr + daddr_off, 16)?;
let saddr_bytes = reader.read_bytes(common_addr + saddr_off, 16)?;
let mut daddr = [0u8; 16];
let mut saddr = [0u8; 16];
daddr.copy_from_slice(&daddr_bytes);
saddr.copy_from_slice(&saddr_bytes);
let dport: u16 = reader.read_field(common_addr, "sock_common", "skc_dport")?;
let sport: u16 = reader.read_field(common_addr, "sock_common", "skc_num")?;
let state: u8 = reader.read_field(common_addr, "sock_common", "skc_state")?;
Ok(ConnectionInfo {
protocol: Protocol::Tcp6,
local_addr: ipv6_to_string(&saddr),
local_port: sport,
remote_addr: ipv6_to_string(&daddr),
remote_port: u16::from_be(dport),
state: ConnectionState::from_raw(state),
pid: None,
})
}
pub(crate) fn ipv6_to_string(addr: &[u8; 16]) -> String {
use std::net::Ipv6Addr;
let mut groups = [0u16; 8];
for (i, chunk) in addr.chunks_exact(2).enumerate() {
groups[i] = u16::from_be_bytes([chunk[0], chunk[1]]);
}
Ipv6Addr::from(groups).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
use memf_core::vas::{TranslationMode, VirtualAddressSpace};
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
#[test]
fn walk_ipv6_no_symbol_returns_empty() {
let isf = IsfBuilder::new().build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new().build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = walk_connections6(&reader).unwrap();
assert!(result.is_empty());
}
#[test]
fn ipv6_loopback_formats_correctly() {
let addr = [0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
assert_eq!(ipv6_to_string(&addr), "::1");
}
#[test]
fn ipv6_full_address_formats_correctly() {
let addr = [0x20u8, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
assert_eq!(ipv6_to_string(&addr), "2001:db8::1");
}
fn make_net_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
let isf = IsfBuilder::new()
.add_struct("inet_hashinfo", 64)
.add_field("inet_hashinfo", "ehash", 0, "pointer")
.add_field("inet_hashinfo", "ehash_mask", 8, "unsigned int")
.add_struct("inet_ehash_bucket", 8)
.add_field("inet_ehash_bucket", "chain", 0, "pointer")
.add_struct("sock_common", 64)
.add_field("sock_common", "skc_nulls_node", 0, "pointer")
.add_field("sock_common", "skc_daddr", 8, "unsigned int")
.add_field("sock_common", "skc_rcv_saddr", 12, "unsigned int")
.add_field("sock_common", "skc_dport", 16, "unsigned short")
.add_field("sock_common", "skc_num", 18, "unsigned short")
.add_field("sock_common", "skc_state", 20, "unsigned char")
.add_struct("sock", 256)
.add_field("sock", "__sk_common", 0, "sock_common")
.add_symbol("tcp_hashinfo", vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(vaddr, paddr, flags::WRITABLE)
.write_phys(paddr, data)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn walk_single_connection() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
let ehash_addr = vaddr + 0x100;
data[0..8].copy_from_slice(&ehash_addr.to_le_bytes());
data[8..12].copy_from_slice(&0u32.to_le_bytes());
let sock_addr = vaddr + 0x200;
data[0x100..0x108].copy_from_slice(&sock_addr.to_le_bytes());
data[0x200..0x208].copy_from_slice(&1u64.to_le_bytes()); let daddr: u32 = u32::from_le_bytes([192, 168, 1, 100]);
data[0x208..0x20C].copy_from_slice(&daddr.to_le_bytes());
let saddr: u32 = u32::from_le_bytes([10, 0, 0, 1]);
data[0x20C..0x210].copy_from_slice(&saddr.to_le_bytes());
data[0x210..0x212].copy_from_slice(&443u16.to_be_bytes());
data[0x212..0x214].copy_from_slice(&54321u16.to_le_bytes());
data[0x214] = 1;
let reader = make_net_reader(&data, vaddr, paddr);
let conns = walk_connections(&reader).unwrap();
assert_eq!(conns.len(), 1);
assert_eq!(conns[0].protocol, Protocol::Tcp);
assert_eq!(conns[0].local_addr, "10.0.0.1");
assert_eq!(conns[0].local_port, 54321);
assert_eq!(conns[0].remote_addr, "192.168.1.100");
assert_eq!(conns[0].remote_port, 443);
assert_eq!(conns[0].state, ConnectionState::Established);
}
#[test]
fn empty_hash_table() {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
data[0..8].copy_from_slice(&0u64.to_le_bytes());
data[8..12].copy_from_slice(&0u32.to_le_bytes());
let reader = make_net_reader(&data, vaddr, paddr);
let conns = walk_connections(&reader).unwrap();
assert!(conns.is_empty());
}
#[test]
fn ipv4_formatting() {
assert_eq!(
ipv4_to_string(u32::from_le_bytes([127, 0, 0, 1])),
"127.0.0.1"
);
assert_eq!(
ipv4_to_string(u32::from_le_bytes([192, 168, 1, 1])),
"192.168.1.1"
);
}
#[test]
fn missing_tcp_hashinfo_returns_missing_kernel_symbol() {
let isf = IsfBuilder::new().build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new().build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = walk_connections(&reader);
assert!(
matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "tcp_hashinfo"),
"expected MissingKernelSymbol {{name: \"tcp_hashinfo\"}}, got {result:?}"
);
}
}