use super::proc::list_procs;
use crate::{
connections::{ConnectState, NetworkState, Protocol},
error::LuminationError,
};
use libc::sysctl;
use log::error;
use nom::{
bytes::complete::take,
number::complete::{be_u16, be_u32, be_u128, le_u8, le_u32},
};
use std::net::{Ipv4Addr, Ipv6Addr};
pub(crate) fn list_tcp_udp() -> Result<Vec<ConnectState>, LuminationError> {
let mut conns = list_connections();
list_procs(&mut conns)?;
Ok(conns)
}
fn list_connections() -> Vec<ConnectState> {
let mut tcp_oid = vec![4, 2, 6, 173];
let mut udp_oid = vec![4, 2, 17, 106];
let mut conns = Vec::new();
#[allow(unsafe_code)]
unsafe {
let mut val_len = 0;
let status = sysctl(
tcp_oid.as_mut_ptr(),
tcp_oid.len() as u32,
std::ptr::null_mut(),
&mut val_len,
std::ptr::null_mut(),
0,
);
if status != 0 {
error!(
"[lumination] Failed to get tcp socket data size. Wanted status 0, got {status}"
);
return conns;
}
let mut val: Vec<libc::c_uchar> = vec![0; val_len];
let mut new_val_len = val_len;
let status = sysctl(
tcp_oid.as_mut_ptr(),
tcp_oid.len() as u32,
val.as_mut_ptr().cast::<libc::c_void>(),
&mut new_val_len,
std::ptr::null_mut(),
0,
);
if status != 0 {
error!("[lumination] Failed to get tcp socket data. Wanted status 0, got {status}");
return conns;
}
let tcp_conns = match parse_socket_options(&val, &Protocol::Tcp) {
Ok((_, results)) => results,
Err(err) => {
error!("[lumination] Failed to parse tcp socket data: {err:?}");
return conns;
}
};
conns = tcp_conns;
let mut val_len = 0;
let status = sysctl(
udp_oid.as_mut_ptr(),
udp_oid.len() as u32,
std::ptr::null_mut(),
&mut val_len,
std::ptr::null_mut(),
0,
);
if status != 0 {
error!(
"[lumination] Failed to get udp socket data size. Wanted status 0, got {status}"
);
return conns;
}
let mut val: Vec<libc::c_uchar> = vec![0; val_len];
let mut new_val_len = val_len;
let status = sysctl(
udp_oid.as_mut_ptr(),
udp_oid.len() as u32,
val.as_mut_ptr().cast::<libc::c_void>(),
&mut new_val_len,
std::ptr::null_mut(),
0,
);
if status != 0 {
error!("[lumination] Failed to get udp socket data. Wanted status 0, got {status}");
return conns;
}
let mut udp_conns = match parse_socket_options(&val, &Protocol::Udp) {
Ok((_, results)) => results,
Err(err) => {
error!("[lumination] Failed to parse udp socket data: {err:?}");
return conns;
}
};
conns.append(&mut udp_conns);
}
conns
}
fn parse_socket_options<'a>(
data: &'a [u8],
protocol: &Protocol,
) -> nom::IResult<&'a [u8], Vec<ConnectState>> {
let xpingen_size: u8 = 24;
if data.len() < xpingen_size as usize {
return Ok((&[], Vec::new()));
}
let (mut remaining, _xpingen) = take(xpingen_size)(data)?;
let min_size = 616;
let inpcb = 16;
let tcpcb = 32;
let socket = 1;
let rcvbuf = 2;
let sndbuf = 4;
let stats = 8;
let mut conn = ConnectState {
protocol: protocol.clone(),
local_address: String::new(),
local_port: 0,
remote_address: String::new(),
remote_port: 0,
state: NetworkState::Unknown,
pid: 0,
process_name: String::new(),
};
let mut conns = Vec::new();
while remaining.len() > min_size {
let (input, length) = le_u32(remaining)?;
let adjust = 4;
if length <= adjust || length as usize > input.len() {
break;
}
let (data, xso_data) = take(length - adjust)(input)?;
remaining = data;
let (input, xso_type) = le_u32(xso_data)?;
if xso_type == inpcb {
parse_inpcb(input, &mut conn)?;
} else if xso_type == socket {
parse_socket(input, &mut conn)?;
if protocol == &Protocol::Udp {
conns.push(conn.clone());
conn = ConnectState {
protocol: protocol.clone(),
local_address: String::new(),
local_port: 0,
remote_address: String::new(),
remote_port: 0,
state: NetworkState::Unknown,
pid: 0,
process_name: String::new(),
};
}
} else if xso_type == tcpcb {
parse_tcpbp(input, &mut conn)?;
conns.push(conn.clone());
conn = ConnectState {
protocol: protocol.clone(),
local_address: String::new(),
local_port: 0,
remote_address: String::new(),
remote_port: 0,
state: NetworkState::Unknown,
pid: 0,
process_name: String::new(),
};
let (input, _padding) = take(size_of::<u32>())(remaining)?;
remaining = input;
} else if xso_type != sndbuf && xso_type != rcvbuf && xso_type != stats {
break;
}
}
Ok((&[], conns))
}
fn parse_inpcb<'a>(data: &'a [u8], conn: &mut ConnectState) -> nom::IResult<&'a [u8], ()> {
let (input, _xi_inpp) = take(size_of::<u64>())(data)?;
let (input, remote_port) = be_u16(input)?;
let (input, local_port) = be_u16(input)?;
let (input, _inp_ppcb) = take(size_of::<u64>())(input)?;
let (input, _inp_gencnt) = take(size_of::<u64>())(input)?;
let (input, _flags) = take(size_of::<u32>())(input)?;
let (input, _flow) = take(size_of::<u32>())(input)?;
let (input, is_ipv4) = le_u8(input)?;
let (input, _ttl) = le_u8(input)?;
let (input, _padding) = le_u8(input)?;
let (input, _protocol) = le_u8(input)?;
let padding: u8 = 12;
let (input, _padding) = take(padding)(input)?;
let (input, remote_address) = if is_ipv4 == 1 {
let (input, remote_ip) = be_u32(input)?;
let ip = Ipv4Addr::from_bits(remote_ip).to_string();
let (input, _padding) = take(padding)(input)?;
(input, ip)
} else {
let ip6_padding: u8 = 4;
let (input, _padding) = take(ip6_padding)(input)?;
let (input, remote_ip) = be_u128(input)?;
let ip = Ipv6Addr::from_bits(remote_ip).to_string();
(input, ip)
};
let (_input, local_address) = if is_ipv4 == 1 {
let (input, src_ip) = be_u32(input)?;
let ip = Ipv4Addr::from_bits(src_ip).to_string();
(input, ip)
} else {
let ip6_padding: u8 = 4;
let (input, _padding) = take(ip6_padding)(input)?;
let (input, src_ip) = be_u128(input)?;
let ip = Ipv6Addr::from_bits(src_ip).to_string();
(input, ip)
};
conn.local_address = local_address;
conn.remote_address = remote_address;
conn.local_port = local_port;
conn.remote_port = remote_port;
Ok((&[], ()))
}
fn parse_socket<'a>(data: &'a [u8], conn: &mut ConnectState) -> nom::IResult<&'a [u8], ()> {
let (input, _xso_so) = take(size_of::<u64>())(data)?;
let (input, _so_type) = take(size_of::<u32>())(input)?;
let (input, _so_options) = take(size_of::<u32>())(input)?;
let (input, _linger) = take(size_of::<u16>())(input)?;
let (input, _state) = take(size_of::<u16>())(input)?;
let (input, _pcb) = take(size_of::<u64>())(input)?;
let (input, _protocol) = take(size_of::<u32>())(input)?;
let (input, _family) = take(size_of::<u32>())(input)?;
let (input, _qlen) = take(size_of::<u16>())(input)?;
let (input, _incqlen) = take(size_of::<u16>())(input)?;
let (input, _qlimit) = take(size_of::<u16>())(input)?;
let (input, _timeo) = take(size_of::<u16>())(input)?;
let (input, _error) = take(size_of::<u16>())(input)?;
let padding_size: u8 = 14;
let (input, _padding) = take(padding_size)(input)?;
let (input, pid) = le_u32(input)?;
conn.pid = pid;
let (input, _oobmark) = take(size_of::<u32>())(input)?;
let (_input, _uid) = take(size_of::<u32>())(input)?;
Ok((&[], ()))
}
fn parse_tcpbp<'a>(data: &'a [u8], conn: &mut ConnectState) -> nom::IResult<&'a [u8], ()> {
let (input, _segq) = take(size_of::<u64>())(data)?;
let (input, _dupacks) = take(size_of::<u32>())(input)?;
let timer_size: u8 = 16;
let (input, _timers) = take(timer_size)(input)?;
let (_input, state) = le_u32(input)?;
conn.state = get_state(&state);
Ok((&[], ()))
}
pub(crate) fn get_state(state: &u32) -> NetworkState {
match state {
0 => NetworkState::Close,
1 => NetworkState::Listen,
2 => NetworkState::SynSent,
3 => NetworkState::SynRecv,
4 => NetworkState::Established,
5 => NetworkState::CloseWait,
6 => NetworkState::FinWait,
7 => NetworkState::Closing,
8 => NetworkState::LastAck,
9 => NetworkState::FinWait2,
10 => NetworkState::TimeWait,
_ => NetworkState::Unknown,
}
}
#[cfg(test)]
mod tests {
use crate::{
connections::{ConnectState, NetworkState, Protocol},
macos::net::{list_connections, list_tcp_udp, parse_inpcb, parse_socket, parse_tcpbp},
};
#[test]
fn test_list_connections() {
let status = list_connections();
assert!(status.len() > 3);
}
#[test]
fn test_list_tcp_udp() {
let status = list_tcp_udp().unwrap();
assert!(status.len() > 3);
}
#[test]
fn test_parse_socket() {
let mut conn = ConnectState {
protocol: Protocol::Unknown,
local_address: String::new(),
local_port: 0,
remote_address: String::new(),
remote_port: 0,
state: NetworkState::Unknown,
pid: 0,
process_name: String::new(),
};
let test = [
58, 122, 159, 24, 13, 235, 222, 183, 1, 0, 0, 0, 8, 0, 0, 0, 0, 0, 2, 1, 237, 47, 58,
223, 62, 23, 108, 159, 6, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 245, 1, 0, 0, 11, 88, 1, 0, 0, 0, 0, 0, 212, 117, 11, 0, 0, 0, 0,
0, 129, 0, 0, 0, 0, 9, 0, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
parse_socket(&test, &mut conn).unwrap();
assert_eq!(conn.pid, 88075);
}
#[test]
fn test_parse_tcpbp() {
let test = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 217, 176, 0, 0, 0, 0, 0, 0,
4, 0, 0, 0, 228, 3, 0, 84, 0, 0, 0, 0, 168, 115, 12, 16, 168, 115, 12, 16, 168, 115,
12, 16, 168, 115, 12, 16, 244, 192, 91, 87, 168, 115, 12, 16, 12, 99, 12, 16, 158, 219,
90, 87, 26, 193, 91, 87, 119, 186, 93, 87, 0, 0, 2, 0, 26, 193, 91, 87, 0, 152, 0, 0,
43, 73, 0, 0, 0, 192, 255, 63, 180, 5, 0, 0, 29, 99, 226, 11, 45, 98, 226, 11, 0, 0, 0,
0, 251, 111, 12, 16, 12, 1, 0, 0, 168, 5, 0, 0, 114, 4, 0, 0, 133, 0, 0, 0, 0, 0, 0, 0,
30, 0, 0, 0, 5, 0, 0, 0, 0, 152, 0, 0, 0, 0, 0, 0, 0, 0, 9, 6, 6, 9, 0, 0, 104, 2, 233,
76, 29, 99, 226, 11, 26, 193, 91, 87, 0, 0, 0, 0, 0, 0, 0, 0, 168, 115, 12, 16, 0, 0,
0, 0, 0, 192, 255, 63,
];
let mut conn = ConnectState {
protocol: Protocol::Unknown,
local_address: String::new(),
local_port: 0,
remote_address: String::new(),
remote_port: 0,
state: NetworkState::Unknown,
pid: 0,
process_name: String::new(),
};
parse_tcpbp(&test, &mut conn).unwrap();
assert_eq!(conn.state, NetworkState::Established);
}
#[test]
fn test_parse_inpcb() {
let test = [
108, 251, 232, 180, 25, 4, 226, 130, 1, 187, 194, 143, 130, 20, 83, 17, 187, 161, 30,
74, 228, 11, 0, 0, 0, 0, 0, 0, 64, 8, 128, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 140, 82, 114, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 1,
208, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181, 122, 46, 131, 22, 0, 0, 0,
];
let mut conn = ConnectState {
protocol: Protocol::Unknown,
local_address: String::new(),
local_port: 0,
remote_address: String::new(),
remote_port: 0,
state: NetworkState::Unknown,
pid: 0,
process_name: String::new(),
};
parse_inpcb(&test, &mut conn).unwrap();
assert_eq!(conn.local_address, "192.168.1.208");
assert_eq!(conn.local_port, 49807);
assert_eq!(conn.remote_address, "140.82.114.26");
assert_eq!(conn.remote_port, 443);
}
}