1use memf_core::object_reader::ObjectReader;
7use memf_format::PhysicalMemoryProvider;
8
9use crate::{ConnectionInfo, ConnectionState, Error, Protocol, Result};
10
11pub fn walk_connections<P: PhysicalMemoryProvider>(
13 reader: &ObjectReader<P>,
14) -> Result<Vec<ConnectionInfo>> {
15 let tcp_hashinfo_addr = reader
16 .symbols()
17 .symbol_address("tcp_hashinfo")
18 .ok_or_else(|| Error::MissingKernelSymbol {
19 name: "tcp_hashinfo".into(),
20 })?;
21
22 let ehash_ptr: u64 = reader.read_field(tcp_hashinfo_addr, "inet_hashinfo", "ehash")?;
23 let ehash_mask: u32 = reader.read_field(tcp_hashinfo_addr, "inet_hashinfo", "ehash_mask")?;
24
25 if ehash_ptr == 0 {
26 return Ok(Vec::new());
27 }
28
29 let mut connections = Vec::new();
30 let bucket_count = u64::from(ehash_mask) + 1;
31
32 for i in 0..bucket_count {
33 let bucket_size = reader
34 .symbols()
35 .struct_size("inet_ehash_bucket")
36 .unwrap_or(8);
37 let bucket_addr = ehash_ptr + i * bucket_size;
38
39 let chain_first: u64 = match reader.read_field(bucket_addr, "inet_ehash_bucket", "chain") {
40 Ok(v) => v,
41 Err(_) => continue,
42 };
43
44 if chain_first == 0 || chain_first & 1 != 0 {
46 continue;
47 }
48
49 let mut sk_addr = chain_first;
50 let mut chain_len = 0;
51 while sk_addr != 0 && sk_addr & 1 == 0 && chain_len < 1000 {
52 if let Ok(conn) = read_inet_sock(reader, sk_addr) {
53 connections.push(conn);
54 }
55
56 sk_addr = match reader.read_pointer(sk_addr, "sock_common", "skc_nulls_node") {
57 Ok(v) => v,
58 Err(_) => break,
59 };
60 chain_len += 1;
61 }
62 }
63
64 Ok(connections)
65}
66
67fn read_inet_sock<P: PhysicalMemoryProvider>(
68 reader: &ObjectReader<P>,
69 sk_addr: u64,
70) -> Result<ConnectionInfo> {
71 let sk_common_off = reader
72 .symbols()
73 .field_offset("sock", "__sk_common")
74 .unwrap_or(0);
75 let common_addr = sk_addr + sk_common_off;
76
77 let daddr: u32 = reader.read_field(common_addr, "sock_common", "skc_daddr")?;
78 let saddr: u32 = reader.read_field(common_addr, "sock_common", "skc_rcv_saddr")?;
79 let dport: u16 = reader.read_field(common_addr, "sock_common", "skc_dport")?;
80 let sport: u16 = reader.read_field(common_addr, "sock_common", "skc_num")?;
81 let state: u8 = reader.read_field(common_addr, "sock_common", "skc_state")?;
82
83 let dport = u16::from_be(dport);
85
86 Ok(ConnectionInfo {
87 protocol: Protocol::Tcp,
88 local_addr: ipv4_to_string(saddr),
89 local_port: sport,
90 remote_addr: ipv4_to_string(daddr),
91 remote_port: dport,
92 state: ConnectionState::from_raw(state),
93 pid: None,
94 })
95}
96
97fn ipv4_to_string(addr: u32) -> String {
98 let bytes = addr.to_le_bytes();
99 format!("{}.{}.{}.{}", bytes[0], bytes[1], bytes[2], bytes[3])
100}
101
102pub fn walk_connections6<P: PhysicalMemoryProvider>(
106 reader: &ObjectReader<P>,
107) -> Result<Vec<ConnectionInfo>> {
108 let tcp6_hashinfo_addr = match reader.symbols().symbol_address("tcp6_hashinfo") {
109 Some(a) => a,
110 None => return Ok(Vec::new()),
111 };
112
113 let ehash_ptr: u64 = reader.read_field(tcp6_hashinfo_addr, "inet_hashinfo", "ehash")?;
114 let ehash_mask: u32 = reader.read_field(tcp6_hashinfo_addr, "inet_hashinfo", "ehash_mask")?;
115
116 if ehash_ptr == 0 {
117 return Ok(Vec::new());
118 }
119
120 let mut connections = Vec::new();
121 let bucket_count = u64::from(ehash_mask) + 1;
122
123 for i in 0..bucket_count {
124 let bucket_size = reader
125 .symbols()
126 .struct_size("inet_ehash_bucket")
127 .unwrap_or(8);
128 let bucket_addr = ehash_ptr + i * bucket_size;
129
130 let chain_first: u64 = match reader.read_field(bucket_addr, "inet_ehash_bucket", "chain") {
131 Ok(v) => v,
132 Err(_) => continue,
133 };
134
135 if chain_first == 0 || chain_first & 1 != 0 {
136 continue;
137 }
138
139 let mut sk_addr = chain_first;
140 let mut chain_len = 0;
141 while sk_addr != 0 && sk_addr & 1 == 0 && chain_len < 1000 {
142 if let Ok(conn) = read_inet6_sock(reader, sk_addr) {
143 connections.push(conn);
144 }
145 sk_addr = match reader.read_pointer(sk_addr, "sock_common", "skc_nulls_node") {
146 Ok(v) => v,
147 Err(_) => break,
148 };
149 chain_len += 1;
150 }
151 }
152
153 Ok(connections)
154}
155
156fn read_inet6_sock<P: PhysicalMemoryProvider>(
157 reader: &ObjectReader<P>,
158 sk_addr: u64,
159) -> Result<ConnectionInfo> {
160 let sk_common_off = reader
161 .symbols()
162 .field_offset("sock", "__sk_common")
163 .unwrap_or(0);
164 let common_addr = sk_addr + sk_common_off;
165
166 let daddr_off = reader
168 .symbols()
169 .field_offset("sock_common", "skc_v6_daddr")
170 .unwrap_or(8);
171 let saddr_off = reader
172 .symbols()
173 .field_offset("sock_common", "skc_v6_rcv_saddr")
174 .unwrap_or(24);
175
176 let daddr_bytes = reader.read_bytes(common_addr + daddr_off, 16)?;
177 let saddr_bytes = reader.read_bytes(common_addr + saddr_off, 16)?;
178
179 let mut daddr = [0u8; 16];
180 let mut saddr = [0u8; 16];
181 daddr.copy_from_slice(&daddr_bytes);
182 saddr.copy_from_slice(&saddr_bytes);
183
184 let dport: u16 = reader.read_field(common_addr, "sock_common", "skc_dport")?;
185 let sport: u16 = reader.read_field(common_addr, "sock_common", "skc_num")?;
186 let state: u8 = reader.read_field(common_addr, "sock_common", "skc_state")?;
187
188 Ok(ConnectionInfo {
189 protocol: Protocol::Tcp6,
190 local_addr: ipv6_to_string(&saddr),
191 local_port: sport,
192 remote_addr: ipv6_to_string(&daddr),
193 remote_port: u16::from_be(dport),
194 state: ConnectionState::from_raw(state),
195 pid: None,
196 })
197}
198
199pub(crate) fn ipv6_to_string(addr: &[u8; 16]) -> String {
200 use std::net::Ipv6Addr;
201 let mut groups = [0u16; 8];
202 for (i, chunk) in addr.chunks_exact(2).enumerate() {
203 groups[i] = u16::from_be_bytes([chunk[0], chunk[1]]);
204 }
205 Ipv6Addr::from(groups).to_string()
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
212 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
213 use memf_symbols::isf::IsfResolver;
214 use memf_symbols::test_builders::IsfBuilder;
215
216 #[test]
217 fn walk_ipv6_no_symbol_returns_empty() {
218 let isf = IsfBuilder::new().build_json();
219 let resolver = IsfResolver::from_value(&isf).unwrap();
220 let (cr3, mem) = PageTableBuilder::new().build();
221 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
222 let reader = ObjectReader::new(vas, Box::new(resolver));
223 let result = walk_connections6(&reader).unwrap();
224 assert!(result.is_empty());
225 }
226
227 #[test]
228 fn ipv6_loopback_formats_correctly() {
229 let addr = [0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
230 assert_eq!(ipv6_to_string(&addr), "::1");
231 }
232
233 #[test]
234 fn ipv6_full_address_formats_correctly() {
235 let addr = [0x20u8, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
236 assert_eq!(ipv6_to_string(&addr), "2001:db8::1");
237 }
238
239 fn make_net_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
240 let isf = IsfBuilder::new()
241 .add_struct("inet_hashinfo", 64)
242 .add_field("inet_hashinfo", "ehash", 0, "pointer")
243 .add_field("inet_hashinfo", "ehash_mask", 8, "unsigned int")
244 .add_struct("inet_ehash_bucket", 8)
245 .add_field("inet_ehash_bucket", "chain", 0, "pointer")
246 .add_struct("sock_common", 64)
247 .add_field("sock_common", "skc_nulls_node", 0, "pointer")
248 .add_field("sock_common", "skc_daddr", 8, "unsigned int")
249 .add_field("sock_common", "skc_rcv_saddr", 12, "unsigned int")
250 .add_field("sock_common", "skc_dport", 16, "unsigned short")
251 .add_field("sock_common", "skc_num", 18, "unsigned short")
252 .add_field("sock_common", "skc_state", 20, "unsigned char")
253 .add_struct("sock", 256)
254 .add_field("sock", "__sk_common", 0, "sock_common")
255 .add_symbol("tcp_hashinfo", vaddr)
256 .build_json();
257
258 let resolver = IsfResolver::from_value(&isf).unwrap();
259 let (cr3, mem) = PageTableBuilder::new()
260 .map_4k(vaddr, paddr, flags::WRITABLE)
261 .write_phys(paddr, data)
262 .build();
263 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
264 ObjectReader::new(vas, Box::new(resolver))
265 }
266
267 #[test]
268 fn walk_single_connection() {
269 let vaddr: u64 = 0xFFFF_8000_0010_0000;
270 let paddr: u64 = 0x0080_0000;
271 let mut data = vec![0u8; 4096];
272
273 let ehash_addr = vaddr + 0x100;
274 data[0..8].copy_from_slice(&ehash_addr.to_le_bytes());
275 data[8..12].copy_from_slice(&0u32.to_le_bytes());
276
277 let sock_addr = vaddr + 0x200;
278 data[0x100..0x108].copy_from_slice(&sock_addr.to_le_bytes());
279
280 data[0x200..0x208].copy_from_slice(&1u64.to_le_bytes()); let daddr: u32 = u32::from_le_bytes([192, 168, 1, 100]);
283 data[0x208..0x20C].copy_from_slice(&daddr.to_le_bytes());
284 let saddr: u32 = u32::from_le_bytes([10, 0, 0, 1]);
285 data[0x20C..0x210].copy_from_slice(&saddr.to_le_bytes());
286 data[0x210..0x212].copy_from_slice(&443u16.to_be_bytes());
287 data[0x212..0x214].copy_from_slice(&54321u16.to_le_bytes());
288 data[0x214] = 1; let reader = make_net_reader(&data, vaddr, paddr);
291 let conns = walk_connections(&reader).unwrap();
292
293 assert_eq!(conns.len(), 1);
294 assert_eq!(conns[0].protocol, Protocol::Tcp);
295 assert_eq!(conns[0].local_addr, "10.0.0.1");
296 assert_eq!(conns[0].local_port, 54321);
297 assert_eq!(conns[0].remote_addr, "192.168.1.100");
298 assert_eq!(conns[0].remote_port, 443);
299 assert_eq!(conns[0].state, ConnectionState::Established);
300 }
301
302 #[test]
303 fn empty_hash_table() {
304 let vaddr: u64 = 0xFFFF_8000_0010_0000;
305 let paddr: u64 = 0x0080_0000;
306 let mut data = vec![0u8; 4096];
307 data[0..8].copy_from_slice(&0u64.to_le_bytes());
308 data[8..12].copy_from_slice(&0u32.to_le_bytes());
309
310 let reader = make_net_reader(&data, vaddr, paddr);
311 let conns = walk_connections(&reader).unwrap();
312 assert!(conns.is_empty());
313 }
314
315 #[test]
316 fn ipv4_formatting() {
317 assert_eq!(
318 ipv4_to_string(u32::from_le_bytes([127, 0, 0, 1])),
319 "127.0.0.1"
320 );
321 assert_eq!(
322 ipv4_to_string(u32::from_le_bytes([192, 168, 1, 1])),
323 "192.168.1.1"
324 );
325 }
326
327 #[test]
328 fn missing_tcp_hashinfo_returns_missing_kernel_symbol() {
329 let isf = IsfBuilder::new().build_json();
330 let resolver = IsfResolver::from_value(&isf).unwrap();
331 let (cr3, mem) = PageTableBuilder::new().build();
332 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
333 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
334 let result = walk_connections(&reader);
335 assert!(
336 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "tcp_hashinfo"),
337 "expected MissingKernelSymbol {{name: \"tcp_hashinfo\"}}, got {result:?}"
338 );
339 }
340}