feat: initial commit with static pcap sys build

This commit is contained in:
Andrew Rioux
2023-04-28 14:37:07 -04:00
commit 226eefbedd
17 changed files with 2317 additions and 0 deletions

72
pcap-sys/src/error.rs Normal file
View File

@@ -0,0 +1,72 @@
// Copyright (C) 2023 Andrew Rioux
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use errno::Errno;
use std::{
convert::From,
ffi::{self, CStr, CString},
};
#[derive(Debug)]
pub enum Error {
PcapError(CString),
StringParse,
UnknownPacketType(u16),
PacketLengthInvalid,
InvalidPcapFd,
Io(std::io::Error),
Libc(Errno),
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<&[i8; 256]> for Error {
fn from(buf: &[i8; 256]) -> Error {
match CString::new(
buf.iter()
.take_while(|&&v| v != 0)
.map(|&v| v as u8)
.collect::<Vec<_>>(),
) {
Ok(s) => Error::PcapError(s),
Err(_) => Error::StringParse,
}
}
}
impl From<*const i8> for Error {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn from(buf: *const i8) -> Error {
Error::PcapError(unsafe { CStr::from_ptr(buf) }.to_owned())
}
}
impl From<ffi::NulError> for Error {
fn from(_err: ffi::NulError) -> Error {
Error::StringParse
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err)
}
}
impl From<Errno> for Error {
fn from(err: Errno) -> Error {
Error::Libc(err)
}
}

183
pcap-sys/src/ffi.rs Normal file
View File

@@ -0,0 +1,183 @@
// Copyright (C) 2023 Andrew Rioux
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use libc::{c_char, c_int, c_uchar, c_uint, c_ushort, c_void};
pub const DLT_NULL: i32 = 0;
pub const DLT_EN10MB: i32 = 1;
pub const DLT_EN3MB: i32 = 2;
pub const DLT_AX25: i32 = 3;
pub const DLT_PRONET: i32 = 4;
pub const DLT_CHAOS: i32 = 5;
pub const DLT_IEEE802: i32 = 6;
pub const DLT_ARCNET: i32 = 7;
pub const DLT_SLIP: i32 = 8;
pub const DLT_PPP: i32 = 9;
pub const DLT_FDDI: i32 = 10;
pub const PCAP_VERSION_MAJOR: c_int = 2;
pub const PCAP_VERSION_MINOR: c_int = 4;
pub const PCAP_ERRBUF_SIZE: usize = 256;
pub const PCAP_IF_LOOPBACK: c_int = 0x00000001;
pub const PCAP_IF_UP: c_int = 0x00000002;
pub const PCAP_IF_RUNNING: c_int = 0x00000004;
pub const PCAP_IF_WIRELESS: c_int = 0x00000008;
pub const PCAP_IF_CONNECTION_STATUS: c_int = 0x00000030;
pub const PCAP_IF_CONNECTION_STATUS_UNKNOWN: c_int = 0x00000000;
pub const PCAP_IF_CONNECTION_STATUS_CONNECTED: c_int = 0x00000010;
pub const PCAP_IF_CONNECTION_STATUS_DISCONNECTED: c_int = 0x00000020;
pub const PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE: c_int = 0x00000030;
pub const PCAP_ERROR: c_int = -1;
pub const PCAP_ERROR_BREAK: c_int = -2;
pub const PCAP_ERROR_NOT_ACTIVATED: c_int = -3;
pub const PCAP_ERROR_ACTIVATED: c_int = -4;
pub const PCAP_ERROR_NO_SUCH_DEVICE: c_int = -5;
pub const PCAP_ERROR_RFMON_NOTSUP: c_int = -6;
pub const PCAP_ERROR_NOT_RFMON: c_int = -7;
pub const PCAP_ERROR_PERM_DENIED: c_int = -8;
pub const PCAP_ERROR_IFACE_NOT_UP: c_int = -9;
pub const PCAP_ERROR_CANTSET_TSTAMP_TYPE: c_int = 10;
pub const PCAP_ERROR_PROMISC_PERM_DENIED: c_int = -11;
pub const PCAP_ERROR_TSTAMP_PRECISION_NOTSUP: c_int = -12;
pub const PCAP_WARNING: c_int = 1;
pub const PCAP_WARNING_PROMISC_NOTSUP: c_int = 2;
pub const PCAP_WARNING_TSTAMP_TYPE_NOTSUP: c_int = 3;
pub const PCAP_NETMASK_UNKNOWN: u32 = 0xffffffff;
pub const PCAP_CHAR_ENC_LOCAL: c_int = 0x00000000;
pub const PCAP_CHAR_ENC_UTF_8: c_int = 0x00000001;
pub const PCAP_TSTAMP_HOST: c_int = 0;
pub const PCAP_TSTAMP_HOST_LOWPREC: c_int = 1;
pub const PCAP_TSTAMP_HOST_HIPREC: c_int = 2;
pub const PCAP_TSTAMP_ADAPTER: c_int = 3;
pub const PCAP_TSTAMP_ADAPTER_UNSYNCED: c_int = 4;
pub const PCAP_TSTAMP_HOST_HIPREC_UNSYNCED: c_int = 5;
pub const PCAP_TSTAMP_PRECISION_MICRO: c_int = 0;
pub const PCAP_TSTAMP_PRECISION_NANO: c_int = 1;
pub const PCAP_BUF_SIZE: c_int = 1024;
pub const PCAP_SRC_FILE: c_int = 2;
pub const PCAP_SRC_IFLOCAL: c_int = 3;
pub const PCAP_SRC_IFREMOTE: c_int = 4;
pub const PCAP_SRC_FILE_STRING: &[u8] = b"file://";
pub const PCAP_SRC_IF_STRING: &[u8] = b"rpcap://";
pub const PCAP_OPENFLAG_PROMISCUOUS: c_int = 0x00000001;
pub const PCAP_OPENFLAG_DATATX_UDP: c_int = 0x00000002;
pub const PCAP_OPENFLAG_NOCAPTURE_RPCAP: c_int = 0x00000004;
pub const PCAP_OPENFLAG_NOCAPTURE_LOCAL: c_int = 0x00000008;
pub const PCAP_OPENFLAG_MAX_RESPONSIVENESS: c_int = 0x00000010;
pub const RPCAP_RMTAUTH_NULL: c_int = 0;
pub const RPCAP_RMTAUTH_PWD: c_int = 1;
pub const PCAP_SAMP_NOSAMP: c_int = 0;
pub const PCAP_SAMP_1_EVERY_N: c_int = 1;
pub const PCAP_SAMP_FIRST_AFTER_N_MS: c_int = 2;
pub const RPCAP_HOSTLIST_SIZE: c_int = 1024;
pub const BUFSIZ: c_int = 8192;
#[repr(C)]
pub struct PcapDev {
_data: [u8; 0],
}
#[repr(C)]
pub struct SockAddr {
pub sa_family: c_ushort,
pub sa_data: [u8; 14],
}
#[repr(C)]
pub struct PcapAddr {
pub next: *const PcapAddr,
pub addr: *const SockAddr,
pub netmask: *const SockAddr,
pub broadaddr: *const SockAddr,
pub dstaddr: *const SockAddr,
}
#[repr(C)]
pub struct PcapDevIf {
pub next: *const PcapDevIf,
pub name: *const c_char,
pub description: *const c_char,
pub addresses: *const PcapAddr,
pub flags: u32,
}
#[repr(C)]
pub struct bpf_insn {
code: c_ushort,
jt: c_uchar,
jf: c_uchar,
k: u32,
}
#[repr(C)]
pub struct BpfProgram {
pub bf_len: c_uint,
pub bpf_insn: *const bpf_insn,
}
#[repr(C)]
pub struct PktHeader {
pub ts: libc::timeval,
pub caplen: u32,
pub len: u32,
}
// #[link(name = "pcap", kind = "static", modifiers = "+whole-archive")]
extern "C" {
pub fn pcap_findalldevs(pcap_dev_if: *mut *const PcapDevIf, errbuf: *mut c_char) -> c_int;
pub fn pcap_freealldevs(pcap_dev_if: *const PcapDevIf);
pub fn pcap_create(source: *const c_char, errbuf: *mut c_char) -> *mut PcapDev;
pub fn pcap_close(p: *mut PcapDev);
pub fn pcap_set_promisc(dev: *mut PcapDev, promisc: c_int) -> c_int;
pub fn pcap_set_buffer_size(dev: *mut PcapDev, bufsize: c_int) -> c_int;
pub fn pcap_set_timeout(dev: *mut PcapDev, ms: c_int) -> c_int;
pub fn pcap_activate(dev: *mut PcapDev) -> c_int;
pub fn pcap_datalink(dev: *mut PcapDev) -> c_int;
pub fn pcap_geterr(dev: *mut PcapDev) -> *const c_char;
pub fn pcap_lookupnet(
dev: *const c_char,
net: *mut u32,
mask: *mut u32,
errbuf: *mut c_char,
) -> c_int;
pub fn pcap_compile(
dev: *mut PcapDev,
fp: *mut BpfProgram,
filter_exp: *const i8,
optimize: c_int,
mask: u32,
) -> c_int;
pub fn pcap_setfilter(dev: *mut PcapDev, fp: *const BpfProgram) -> c_int;
pub fn pcap_loop(
p: *mut PcapDev,
cnt: c_int,
callback: unsafe extern "C" fn(
user: *mut c_void,
header: *const PktHeader,
data: *const u8,
),
user: *mut c_void,
) -> c_int;
pub fn pcap_breakloop(p: *mut PcapDev);
pub fn pcap_sendpacket(p: *mut PcapDev, buf: *const c_uchar, size: c_int) -> c_int;
pub fn pcap_setnonblock(dev: *mut PcapDev, nonblock: c_int, errbuf: *mut c_char) -> c_int;
pub fn pcap_get_selectable_fd(p: *mut PcapDev) -> c_int;
pub fn pcap_next_ex(
p: *mut PcapDev,
header: *mut *mut PktHeader,
pkt_data: *mut *mut c_char,
) -> c_int;
}

472
pcap-sys/src/lib.rs Normal file
View File

@@ -0,0 +1,472 @@
// Copyright (C) 2023 Andrew Rioux
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::{
ffi::{CStr, CString},
os::fd::{AsRawFd, RawFd},
pin::Pin,
ptr, slice,
task::{self, Poll},
};
pub mod error;
mod ffi;
pub mod packets;
pub mod consts {
pub use super::ffi::{
BUFSIZ, DLT_ARCNET, DLT_AX25, DLT_CHAOS, DLT_EN10MB, DLT_EN3MB, DLT_FDDI, DLT_IEEE802,
DLT_NULL, DLT_PPP, DLT_PRONET, DLT_SLIP, PCAP_BUF_SIZE, PCAP_CHAR_ENC_LOCAL,
PCAP_CHAR_ENC_UTF_8, PCAP_ERRBUF_SIZE, PCAP_ERROR, PCAP_ERROR_ACTIVATED, PCAP_ERROR_BREAK,
PCAP_ERROR_CANTSET_TSTAMP_TYPE, PCAP_ERROR_IFACE_NOT_UP, PCAP_ERROR_NOT_ACTIVATED,
PCAP_ERROR_NOT_RFMON, PCAP_ERROR_NO_SUCH_DEVICE, PCAP_ERROR_PERM_DENIED,
PCAP_ERROR_PROMISC_PERM_DENIED, PCAP_ERROR_RFMON_NOTSUP,
PCAP_ERROR_TSTAMP_PRECISION_NOTSUP, PCAP_IF_CONNECTION_STATUS,
PCAP_IF_CONNECTION_STATUS_CONNECTED, PCAP_IF_CONNECTION_STATUS_DISCONNECTED,
PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE, PCAP_IF_CONNECTION_STATUS_UNKNOWN,
PCAP_IF_LOOPBACK, PCAP_IF_RUNNING, PCAP_IF_UP, PCAP_IF_WIRELESS, PCAP_NETMASK_UNKNOWN,
PCAP_OPENFLAG_DATATX_UDP, PCAP_OPENFLAG_MAX_RESPONSIVENESS, PCAP_OPENFLAG_NOCAPTURE_LOCAL,
PCAP_OPENFLAG_NOCAPTURE_RPCAP, PCAP_OPENFLAG_PROMISCUOUS, PCAP_SAMP_1_EVERY_N,
PCAP_SAMP_FIRST_AFTER_N_MS, PCAP_SAMP_NOSAMP, PCAP_SRC_FILE, PCAP_SRC_FILE_STRING,
PCAP_SRC_IFLOCAL, PCAP_SRC_IFREMOTE, PCAP_SRC_IF_STRING, PCAP_TSTAMP_ADAPTER,
PCAP_TSTAMP_ADAPTER_UNSYNCED, PCAP_TSTAMP_HOST, PCAP_TSTAMP_HOST_HIPREC,
PCAP_TSTAMP_HOST_HIPREC_UNSYNCED, PCAP_TSTAMP_HOST_LOWPREC, PCAP_TSTAMP_PRECISION_MICRO,
PCAP_TSTAMP_PRECISION_NANO, PCAP_VERSION_MAJOR, PCAP_VERSION_MINOR, PCAP_WARNING,
PCAP_WARNING_PROMISC_NOTSUP, PCAP_WARNING_TSTAMP_TYPE_NOTSUP, RPCAP_HOSTLIST_SIZE,
RPCAP_RMTAUTH_NULL, RPCAP_RMTAUTH_PWD,
};
}
use ffi::PcapDevIf;
use futures::ready;
use tokio::io::unix::AsyncFd;
pub struct PcapDevIterator {
dev_if: *const PcapDevIf,
current_dev: *const PcapDevIf,
}
impl PcapDevIterator {
pub fn new() -> error::Result<PcapDevIterator> {
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
let mut dev_if = ptr::null();
if unsafe { ffi::pcap_findalldevs(&mut dev_if, errbuf.as_mut_ptr()) } != 0 {
Err(&errbuf)?;
}
Ok(PcapDevIterator {
dev_if,
current_dev: dev_if,
})
}
}
impl Drop for PcapDevIterator {
fn drop(&mut self) {
unsafe {
ffi::pcap_freealldevs(self.dev_if);
}
}
}
impl std::iter::Iterator for PcapDevIterator {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
if self.current_dev.is_null() {
return None;
}
let current_dev = self.current_dev;
let dev_name = CStr::from_ptr((*current_dev).name)
.to_str()
.unwrap()
.to_string();
self.current_dev = (*current_dev).next;
Some(dev_name)
}
}
}
pub trait State {}
pub trait Activated: State {}
pub trait NotListening: Activated {}
pub trait Listening: Activated {}
pub trait Disabled: State {}
pub enum DevActivated {}
impl State for DevActivated {}
impl Activated for DevActivated {}
impl NotListening for DevActivated {}
pub enum DevDisabled {}
impl State for DevDisabled {}
impl Disabled for DevDisabled {}
pub enum DevListening {}
impl State for DevListening {}
impl Activated for DevListening {}
impl Listening for DevListening {}
pub struct BpfProgram {}
pub struct Interface<T: State> {
dev_name: CString,
dev: *mut ffi::PcapDev,
marker: std::marker::PhantomData<T>,
absorbed: bool,
nonblocking: bool,
}
impl<T: State> Drop for Interface<T> {
fn drop(&mut self) {
if !self.absorbed {
unsafe { ffi::pcap_close(self.dev) };
}
}
}
unsafe impl<T: State> Send for Interface<T> {}
impl<T: State> Interface<T> {
pub fn new(name: &str) -> error::Result<Interface<DevDisabled>> {
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
let dev_name = CString::new(name)?;
let dev = unsafe { ffi::pcap_create(dev_name.as_ptr(), errbuf.as_mut_ptr()) };
if dev.is_null() {
Err(&errbuf)?;
}
Ok(Interface::<DevDisabled> {
dev_name,
dev,
marker: std::marker::PhantomData,
absorbed: false,
nonblocking: false,
})
}
pub fn lookupnet(&self) -> error::Result<(u32, u32)> {
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
let mut net: u32 = 0;
let mut mask: u32 = 0;
if unsafe {
ffi::pcap_lookupnet(
self.dev_name.as_ptr(),
&mut net as *mut u32,
&mut mask as *mut u32,
errbuf.as_mut_ptr(),
)
} == -1
{
Err(error::Error::from(&errbuf))?;
}
Ok((net, mask))
}
pub fn set_non_blocking(&mut self, nonblocking: bool) -> error::Result<()> {
self.nonblocking = nonblocking;
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
if unsafe { ffi::pcap_setnonblock(self.dev, i32::from(nonblocking), errbuf.as_mut_ptr()) }
== -1
{
Err(error::Error::from(&errbuf))?;
}
Ok(())
}
}
impl<T: Disabled> Interface<T> {
pub fn set_promisc(&mut self, promisc: bool) -> error::Result<()> {
if unsafe { ffi::pcap_set_promisc(self.dev, i32::from(promisc)) } != 0 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(())
}
pub fn set_buffer_size(&mut self, bufsize: i32) -> error::Result<()> {
if unsafe { ffi::pcap_set_buffer_size(self.dev, bufsize) } != 0 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(())
}
pub fn set_timeout(&mut self, timeout: i32) -> error::Result<()> {
if unsafe { ffi::pcap_set_timeout(self.dev, timeout) } != 0 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(())
}
pub fn activate(mut self) -> error::Result<Interface<DevActivated>> {
if unsafe { ffi::pcap_activate(self.dev) } != 0 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
self.absorbed = true;
Ok(Interface::<DevActivated> {
dev_name: self.dev_name.clone(),
dev: self.dev,
marker: std::marker::PhantomData,
absorbed: false,
nonblocking: self.nonblocking,
})
}
}
impl<T: Activated> Interface<T> {
pub fn datalink(&self) -> i32 {
unsafe { ffi::pcap_datalink(self.dev) }
}
pub fn set_filter(
&mut self,
filter: &str,
optimize: bool,
mask: Option<u32>,
) -> error::Result<Box<ffi::BpfProgram>> {
let mut bpf = ffi::BpfProgram {
bf_len: 0,
bpf_insn: ptr::null(),
};
let mask = if let Some(m) = mask {
m
} else {
let (_, m) = self.lookupnet()?;
m
};
let filter = CString::new(filter)?;
if unsafe {
ffi::pcap_compile(
self.dev,
&mut bpf as *mut ffi::BpfProgram,
filter.as_ptr(),
i32::from(optimize),
mask,
)
} == -1
{
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
if unsafe { ffi::pcap_setfilter(self.dev, &bpf as *const ffi::BpfProgram) } == -1 {
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(Box::new(bpf))
}
pub fn sendpacket(&self, packet: packets::EthernetPkt) -> error::Result<()> {
dbg!(packet.data.len());
if unsafe {
ffi::pcap_sendpacket(
self.dev,
packet.data.as_ptr(),
packet.data.len().try_into().unwrap(),
)
} == ffi::PCAP_ERROR
{
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
}
Ok(())
}
pub fn next_packet(&mut self) -> error::Result<packets::EthernetPacket> {
let mut header: *mut ffi::PktHeader = ptr::null_mut();
let mut data: *mut libc::c_char = ptr::null_mut();
if unsafe { ffi::pcap_next_ex(self.dev, &mut header as *mut _, &mut data as *mut _) < 1 } {
return unsafe { Err(ffi::pcap_geterr(self.dev))? };
}
let rdata = unsafe { slice::from_raw_parts(data as *mut u8, (*header).caplen as usize) };
if rdata.len() < 24 {
return Err(error::Error::PacketLengthInvalid);
}
Ok(packets::EthernetPkt { data: rdata }.to_owned())
}
}
struct ListenHandler<'a, F>
where
F: Fn(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
{
packet_handler: F,
break_on_fail: bool,
fail_error: Option<error::Error>,
interface: &'a Interface<DevListening>,
}
impl<T: NotListening> Interface<T> {
pub fn listen<F>(
&self,
packet_handler: F,
break_on_fail: bool,
packet_count: i32,
) -> (Option<error::Error>, i32)
where
F: Fn(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
{
unsafe extern "C" fn cback<F>(
user: *mut libc::c_void,
header: *const ffi::PktHeader,
data: *const u8,
) where
F: Fn(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
{
let info = &mut *(user as *mut ListenHandler<F>);
let data = slice::from_raw_parts(data, (*header).caplen as usize);
if data.len() < 14 {
eprintln!(
" * Failed to get full packet, captured {} bytes",
data.len()
);
}
let result = (info.packet_handler)(info.interface, packets::EthernetPkt { data });
match result {
Err(e) => {
eprintln!(" * Packet handle error: {:?}", e);
if info.break_on_fail {
info.fail_error = Some(e);
ffi::pcap_breakloop(info.interface.dev);
}
}
Ok(b) => {
if b {
ffi::pcap_breakloop(info.interface.dev);
}
}
}
}
let interface = Interface {
dev_name: self.dev_name.clone(),
dev: self.dev,
marker: std::marker::PhantomData,
absorbed: true,
nonblocking: self.nonblocking,
};
let mut info = ListenHandler::<F> {
packet_handler,
break_on_fail,
fail_error: None,
interface: &interface,
};
let count = unsafe {
ffi::pcap_loop(
self.dev,
packet_count,
cback::<F>,
&mut info as *mut ListenHandler<F> as *mut libc::c_void,
)
};
(info.fail_error, count)
}
pub fn stream(mut self) -> error::Result<InterfaceStream<DevActivated>> {
self.set_non_blocking(true)?;
Ok(InterfaceStream {
inner: AsyncFd::with_interest(
InternalInterfaceStream::<DevActivated>::new(unsafe { std::mem::transmute(self) })?,
tokio::io::Interest::READABLE,
)?,
})
}
}
struct InternalInterfaceStream<T: Activated> {
interface: Interface<T>,
fd: RawFd,
}
impl<T: Activated> InternalInterfaceStream<T> {
fn new(interface: Interface<T>) -> error::Result<InternalInterfaceStream<T>> {
let fd = unsafe { ffi::pcap_get_selectable_fd(interface.dev) };
if fd == -1 {
return Err(error::Error::InvalidPcapFd);
}
Ok(Self { interface, fd })
}
}
impl<T: Activated> AsRawFd for InternalInterfaceStream<T> {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
pub struct InterfaceStream<T: Activated> {
inner: AsyncFd<InternalInterfaceStream<T>>,
}
impl<T: Activated> InterfaceStream<T> {
pub fn sendpacket(&mut self, packet: packets::EthernetPkt) -> error::Result<()> {
self.inner.get_mut().interface.sendpacket(packet)
}
}
impl<T: Activated> Unpin for InterfaceStream<T> {}
impl<T: Activated> futures::Stream for InterfaceStream<T> {
type Item = error::Result<packets::EthernetPacket>;
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
let stream = Pin::into_inner(self);
loop {
let mut guard = ready!(stream.inner.poll_read_ready_mut(cx))?;
match guard.try_io(|inner| match inner.get_mut().interface.next_packet() {
Ok(p) => Ok(Ok(p)),
Err(e) => Ok(Err(e)),
}) {
Ok(result) => {
return Poll::Ready(Some(result?));
}
Err(_would_block) => continue,
}
}
}
}

346
pcap-sys/src/packets.rs Normal file
View File

@@ -0,0 +1,346 @@
// Copyright (C) 2023 Andrew Rioux
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::error;
use std::{
net::Ipv4Addr,
sync::atomic::{AtomicU16, Ordering},
};
pub struct EthernetPkt<'a> {
pub(crate) data: &'a [u8],
}
impl<'a> EthernetPkt<'a> {
pub fn destination_address(&self) -> &[u8; 6] {
self.data[0..6].try_into().unwrap()
}
pub fn source_address(&self) -> &[u8; 6] {
self.data[6..12].try_into().unwrap()
}
pub fn ether_type(&self) -> u16 {
u16::from_be_bytes(self.data[12..14].try_into().unwrap())
}
pub fn get_layer3_pkt(&'_ self) -> error::Result<Layer3Pkt<'_>> {
if self.data.len() < 14 {
return Err(error::Error::PacketLengthInvalid);
}
match self.ether_type() {
0x0800 => {
if self.data.len() < 34 {
return Err(error::Error::PacketLengthInvalid);
}
let pkt = IPv4Pkt {
data: &self.data[14..],
};
if self.data.len() < (14 + pkt.total_length()).into() {
return Err(error::Error::PacketLengthInvalid);
}
Ok(Layer3Pkt::IPv4Pkt(pkt))
}
p => Err(error::Error::UnknownPacketType(p)),
}
}
pub fn to_owned(&self) -> EthernetPacket {
EthernetPacket {
data: self.data.to_vec(),
}
}
}
pub enum Layer3Pkt<'a> {
IPv4Pkt(IPv4Pkt<'a>),
}
pub struct IPv4Pkt<'a> {
data: &'a [u8],
}
impl<'a> IPv4Pkt<'a> {
pub fn version(&self) -> u8 {
4
}
pub fn header_len(&self) -> u8 {
4 * (0x0F & self.data[0])
}
pub fn type_of_service(&self) -> u8 {
self.data[1] >> 2
}
pub fn ecn(&self) -> u8 {
self.data[1] & 0x03
}
pub fn total_length(&self) -> u16 {
u16::from_be_bytes(self.data[2..4].try_into().unwrap())
}
pub fn id(&self) -> u16 {
u16::from_be_bytes(self.data[4..6].try_into().unwrap())
}
pub fn dont_fragment(&self) -> bool {
self.data[6] & 0x40 != 0
}
pub fn more_fragments(&self) -> bool {
self.data[6] & 0x80 != 0
}
pub fn fragment_offset(&self) -> u16 {
u16::from_be_bytes([self.data[6] & 0x1F, self.data[7]]) * 8
}
pub fn ttl(&self) -> u8 {
self.data[8]
}
pub fn protocol(&self) -> u8 {
self.data[9]
}
pub fn header_checksum(&self) -> u16 {
u16::from_be_bytes(self.data[10..12].try_into().unwrap())
}
pub fn source_ip(&self) -> Ipv4Addr {
<&[u8] as TryInto<[u8; 4]>>::try_into(&self.data[12..16])
.unwrap()
.into()
}
pub fn dest_ip(&self) -> Ipv4Addr {
<&[u8] as TryInto<[u8; 4]>>::try_into(&self.data[16..20])
.unwrap()
.into()
}
pub fn to_owned(&self) -> IPv4Packet {
IPv4Packet {
data: self.data.to_vec(),
}
}
pub fn get_layer4_packet(&self) -> error::Result<Layer4Pkt<'a>> {
match self.protocol() {
17 => Ok(Layer4Pkt::UDP(UDPPkt {
data: &self.data[(self.header_len() as usize)..],
})),
p => Err(error::Error::UnknownPacketType(p.into())),
}
}
}
pub enum Layer4Pkt<'a> {
UDP(UDPPkt<'a>),
}
impl<'a> Layer4Pkt<'a> {
pub fn len(&self) -> u16 {
match self {
Layer4Pkt::UDP(pkt) => pkt.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub struct UDPPkt<'a> {
data: &'a [u8],
}
impl<'a> UDPPkt<'a> {
pub fn srcport(&self) -> u16 {
u16::from_be_bytes(self.data[0..2].try_into().unwrap())
}
pub fn dstport(&self) -> u16 {
u16::from_be_bytes(self.data[2..4].try_into().unwrap())
}
pub fn len(&self) -> u16 {
u16::from_be_bytes(self.data[4..6].try_into().unwrap())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_owned(&self) -> UDPPacket {
UDPPacket {
data: self.data.to_vec(),
}
}
pub fn get_data(&'_ self) -> &'_ [u8] {
&self.data[8..(self.len() as usize)]
}
}
#[derive(Debug, Clone)]
pub struct EthernetPacket {
data: Vec<u8>,
}
impl EthernetPacket {
pub fn construct(src: [u8; 6], dst: [u8; 6], packet: &Layer3Packet) -> EthernetPacket {
match packet {
Layer3Packet::IPv4(pkt) => EthernetPacket {
data: [&dst[..], &src[..], &[0x08_u8, 0x00_u8][..], &*pkt.data].concat(),
},
}
}
#[inline]
pub fn pkt(&'_ self) -> EthernetPkt<'_> {
EthernetPkt { data: &self.data }
}
}
#[derive(Clone)]
pub enum Layer3Packet {
IPv4(IPv4Packet),
}
static IPV4_ID: AtomicU16 = AtomicU16::new(0xabcd);
#[derive(Clone)]
pub struct IPv4Packet {
data: Vec<u8>,
}
impl IPv4Packet {
pub fn construct<I1, I2>(source: I1, dest: I2, packet: &Layer4Packet) -> IPv4Packet
where
I1: Into<Ipv4Addr>,
I2: Into<Ipv4Addr>,
{
let source = source.into();
let dest = dest.into();
let dscpen: u8 = 0;
let totallen: u16 = 20 + packet.pkt().len();
let id: u16 = IPV4_ID.fetch_add(1, Ordering::SeqCst);
let fragmentation: u16 = 0x4000;
let ttl: u8 = 64;
let protocol: u8 = match packet {
Layer4Packet::UDP(_) => 17,
};
let source_upper = u16::from_be_bytes(source.octets()[0..2].try_into().unwrap());
let source_lower = u16::from_be_bytes(source.octets()[2..4].try_into().unwrap());
let dest_upper = u16::from_be_bytes(dest.octets()[0..2].try_into().unwrap());
let dest_lower = u16::from_be_bytes(dest.octets()[2..4].try_into().unwrap());
let versihldscpen = 0x4500 | dscpen as u16;
let ttlprotocol = ((ttl as u32) << 8_u8) | protocol as u32;
let checksum_part: u32 = versihldscpen as u32
+ totallen as u32
+ id as u32
+ ttlprotocol
+ fragmentation as u32
+ source_upper as u32
+ source_lower as u32
+ dest_upper as u32
+ dest_lower as u32;
let checksum: u16 = ((checksum_part & 0xFFFF) + (checksum_part >> 16))
.try_into()
.unwrap();
let checksum = 0xFFFF - checksum;
let data = [
&versihldscpen.to_be_bytes()[..],
&totallen.to_be_bytes(),
&id.to_be_bytes(),
&fragmentation.to_be_bytes(),
&[ttl, protocol],
&checksum.to_be_bytes(),
&source.octets(),
&dest.octets(),
match packet {
Layer4Packet::UDP(pkt) => &*pkt.data,
},
]
.concat();
IPv4Packet { data }
}
#[inline]
pub fn pkt(&'_ self) -> IPv4Pkt<'_> {
IPv4Pkt { data: &self.data }
}
}
#[derive(Clone)]
pub enum Layer4Packet {
UDP(UDPPacket),
}
impl Layer4Packet {
pub fn pkt(&'_ self) -> Layer4Pkt<'_> {
match self {
Layer4Packet::UDP(pkt) => Layer4Pkt::UDP(pkt.pkt()),
}
}
}
#[derive(Clone)]
pub struct UDPPacket {
data: Vec<u8>,
}
impl UDPPacket {
pub fn construct<T>(source: u16, dest: u16, data: T) -> UDPPacket
where
T: Into<Vec<u8>>,
{
let data = data.into();
UDPPacket {
data: [
&source.to_be_bytes(),
&dest.to_be_bytes(),
&(8_u16 + TryInto::<u16>::try_into(data.len()).unwrap()).to_be_bytes(),
&[0_u8, 0_u8],
&*data,
]
.concat(),
}
}
#[inline]
pub fn pkt(&'_ self) -> UDPPkt<'_> {
UDPPkt { data: &self.data }
}
}