I'm taking my first stab at using the NetLink API in linux. I'm using Rust because it hasn't bitten me in the ass enough for me to go back to C yet. I figured a good place to start would be to enumerate the netlink devices, since there's already a utility that does that (ip link). When I run the Rust code it returns 3 devices out of the 6 devices that ip link returns. So I'm trying to inspect the request that I'm sending vs. what ip link is sending.
# Mine
$ sudo strace -ff -v -e trace=%network mine 2>&1 |grep sendto
sendto(3,
{
{nlmsg_len=40, nlmsg_type=0x12 /* NLMSG_??? */, nlmsg_flags=NLM_F_REQUEST|0x300, nlmsg_seq=1620766291, nlmsg_pid=0},
"\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x1d\x00\x01\x00\x00\x00"
},
40, 0, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 40
$ sudo strace -ff --const-print-style=raw -v -e trace=%network mine 2>&1 |grep sendto
sendto(3,
{
{nlmsg_len=40, nlmsg_type=0x12, nlmsg_flags=0x301, nlmsg_seq=1620766293, nlmsg_pid=0},
"\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x1d\x00\x01\x00\x00\x00"
},
40, 0, {sa_family=0x10, nl_pid=0, nl_groups=00000000}, 12) = 40
# ip link
$ sudo strace -ff -v -e trace=%network ip link 2>&1 |grep sendto
sendto(3,
{
{nlmsg_len=40, nlmsg_type=RTM_GETLINK, nlmsg_flags=NLM_F_REQUEST|NLM_F_DUMP, nlmsg_seq=1620765818, nlmsg_pid=0},
{ifi_family=AF_PACKET, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0},
{
{nla_len=8, nla_type=IFLA_EXT_MASK},
1
}
},
40, 0, NULL, 0) = 40
$ sudo strace -ff --const-print-style=raw -v -e trace=%network ip link 2>&1 |grep sendto
sendto(3,
{
{nlmsg_len=40, nlmsg_type=0x12, nlmsg_flags=0x301, nlmsg_seq=1620765854, nlmsg_pid=0},
{ifi_family=0x11, ifi_type=0, ifi_index=0, ifi_flags=0, ifi_change=0},
{
{nla_len=8, nla_type=0x1d},
1
}
},
40, 0, NULL, 0) = 40
The two differences that I can spot are that (A) ip link binds the socket whereas the Rust library provides a dest_addr argument to sendto. I doubt this is relevant. (B) strace can parse the structure sent by ip link but can't seem to fully parse the structure sent by my Rust program. strace says both programs agree on the struct nlmsghdr header. However, strace doesn't parse the struct ifinfomsg of my program. Looking at the bytes, however, it appears to match.
The rust library I'm using netlink-packet-route doesn't seem to have an obvious equivalent to struct rtattr. Adjacent to the ifinfomsg (called LinkHeader in the rust library) there's a list of what it calls Nla. The enum values line up with their C equivilents and as shown above the constant values line up too.
man rtnetlink doesn't mention anything about IFLA_EXT_MASK as a possible rtattr for RTM_GETLINK and I don't get many other hits in documentation for it.
I guess the next step is to pop both into gdb and see if there's any other observable difference between the two calls.
The super-ugly demo-quality Rust code that produces the above message:
use std::io::{Result, Error, ErrorKind};
use netlink_sys::{Socket, protocols::NETLINK_ROUTE, SocketAddr};
use netlink_packet_core::{NetlinkMessage, NetlinkHeader, NLM_F_DUMP, NLM_F_REQUEST};
use netlink_packet_route::{RtnlMessage, LinkMessage, LinkHeader, AF_PACKET, ARPHRD_NETROM};
use netlink_packet_core::NetlinkPayload::InnerMessage;
use netlink_packet_route::RtnlMessage::NewLink;
use netlink_packet_route::link::nlas::Nla;
use std::time::{UNIX_EPOCH, SystemTime};
fn main() -> Result<()> {
println!("Hello, world!");
let socket = Socket::new(NETLINK_ROUTE)?;
let kernel_addr = SocketAddr::new(0, 0);
let msgid = SystemTime::now().duration_since(UNIX_EPOCH).map_err(|_|{Error::from(ErrorKind::Other)})?.as_secs();
let nlas: Vec<Nla> = vec![Nla::ExtMask(1)];
let mut packet = NetlinkMessage {
header: NetlinkHeader {
sequence_number: msgid as u32,
flags: NLM_F_DUMP | NLM_F_REQUEST,
..Default::default()
},
payload: RtnlMessage::GetLink(LinkMessage {header: LinkHeader {
interface_family: AF_PACKET as u8,
link_layer_type: ARPHRD_NETROM,
..LinkHeader::default()}, nlas, ..LinkMessage::default()}).into(),
};
packet.finalize();
let mut buf = vec![0; packet.header.length as usize];
packet.serialize(&mut buf[..]);
let n_sent = socket.send_to(&buf[..], &kernel_addr, 0).unwrap();
assert_eq!(n_sent, buf.len());
let mut buf = vec![0; 4096];
loop {
let (n_received, sender_addr) = socket.recv_from(&mut buf[..], 0).unwrap();
assert_eq!(sender_addr, kernel_addr);
for i in &mut buf[n_received..] { *i = 0 };
if n_received == 4096 { return Err(Error::from(ErrorKind::OutOfMemory))}
if buf[4] == 2 && buf[5] == 0 {
println!("the kernel responded with an error");
return Err(Error::from(ErrorKind::ConnectionReset));
}
if buf[4] == 3 && buf[5] == 0 {
println!("Done");
return Ok(());
}
let resp = NetlinkMessage::<RtnlMessage>::deserialize(&buf).expect("Failed to deserialize message");
match resp.payload {
InnerMessage(i) => match i {
NewLink(j) => {
let name = j.nlas.iter().find(|nla| { matches!(nla, Nla::IfName(_))});
println!("index {:?}: {:?}", j.header.index, name);
},
_ => println!("Some other message type")
},
_ => println!("Some other message type")
}
}
}
So it turns out that netlink will send more than one response structure in a single response packet. I discovered this by setting up a monitor interface for netlink and tcpdump'ing it. The total number of bytes sent back in response to ip link and to my program were similar, but they were distributed over differing numbers of packets. The size of the buffer used on the recvmsg call affects the way netlink packs the results.
sudo ip link add nlmon0 type nlmon
sudo ip link set dev nlmon0 up
sudo tcpdump -i nlmon0 -w /tmp/dump &
ip link
mine
fg
CTRL+C
tshark -r /tmp/dump
1 0.000000 → Netlink route 56
2 0.000073 → Netlink route 2660
3 0.000127 → Netlink route 2688
4 0.000205 → Netlink route 4060
5 0.000258 → Netlink 36
6 3.622386 → Netlink route 56
7 3.622449 → Netlink route 2660
8 3.622512 → Netlink route 2688
9 3.623179 → Netlink route 2740
10 3.623748 → Netlink route 1336
11 3.624273 → Netlink 36
I will need to figure out how this Rust library is setup to deal with this.
Related
First off: I know running Rust on an ESP32 isn't a very common practice yet, and some (quite a bit of) trouble is to be expected. But I seem to have hit a roadblock.
What works:
flashing and running the code on an ESP32
passing along the certificates in the src/certificates directory
WiFi connection (simple WPA Personal, nothing fancy like WPA Enterprise)
publishing and suscribing to topics using MQTT
What doesn't work:
publising and subscribing to AWS IoT (Core) using MQTT. This needs certificates, and as far as I'm aware I'm handling this properly (see code below).
Some additional info (see code below):
server.cert.crt is renamed from the AWS provided root-CA.crt
client.cert.pem is renamed from the AWS provided my-thing-rev1.cert.pem
client.private.key is renamed from the AWS provided my-thing-rev1.private.key
I also received my-thing-rev1.public.key and my-thing-rev1-Policy, but I don't think I need these...?
I know this is not the proper way of implementing this (I should not provide the certificates directly, instead use a service to get them, but this is a very basic POC)
the code works fine if I don't want to connect to AWS, but instead use my own broker or broker.emqx.io for testing (even with the certificates included)
This is the code I'm currently using (heavily based on Rust on ESP32 STD demo app):
use embedded_svc::httpd::Result;
use embedded_svc::mqtt::client::{Connection, MessageImpl, QoS};
use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration};
use esp_idf_svc::tls::X509;
use esp_idf_sys::EspError;
// other needed imports (not relevant here)
extern crate dotenv_codegen;
extern crate core;
const AWS_IOT_ENDPOINT: &str = dotenv!("AWS_IOT_ENDPOINT");
const AWS_IOT_CLIENT_ID: &str = dotenv!("AWS_IOT_CLIENT_ID");
const AWS_IOT_TOPIC: &str = dotenv!("AWS_IOT_TOPIC");
fn main() -> Result<()> {
esp_idf_sys::link_patches();
// other code
let mqtt_client: EspMqttClient<ConnState<MessageImpl, EspError>> = test_mqtt_client()?;
// more code
Ok(())
}
fn convert_certificate(mut certificate_bytes: Vec<u8>) -> X509<'static> {
// append NUL
certificate_bytes.push(0);
// convert the certificate
let certificate_slice: &[u8] = unsafe {
let ptr: *const u8 = certificate_bytes.as_ptr();
let len: usize = certificate_bytes.len();
mem::forget(certificate_bytes);
slice::from_raw_parts(ptr, len)
};
// return the certificate file in the correct format
X509::pem_until_nul(certificate_slice)
}
fn test_mqtt_client() -> Result<EspMqttClient<ConnState<MessageImpl, EspError>>> {
info!("About to start MQTT client");
let server_cert_bytes: Vec<u8> = include_bytes!("certificates/server.cert.crt").to_vec();
let client_cert_bytes: Vec<u8> = include_bytes!("certificates/client.cert.pem").to_vec();
let private_key_bytes: Vec<u8> = include_bytes!("certificates/client.private.key").to_vec();
let server_cert: X509 = convert_certificate(server_cert_bytes);
let client_cert: X509 = convert_certificate(client_cert_bytes);
let private_key: X509 = convert_certificate(private_key_bytes);
// TODO: fix the following error: `E (16903) esp-tls-mbedtls: mbedtls_ssl_handshake returned -0x7280`
let conf = MqttClientConfiguration {
client_id: Some(AWS_IOT_CLIENT_ID),
crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach),
server_certificate: Some(server_cert),
client_certificate: Some(client_cert),
private_key: Some(private_key),
..Default::default()
};
let (mut client, mut connection) =
EspMqttClient::new_with_conn(AWS_IOT_ENDPOINT, &conf)?;
info!("MQTT client started");
// Need to immediately start pumping the connection for messages, or else subscribe() and publish() below will not work
// Note that when using the alternative constructor - `EspMqttClient::new` - you don't need to
// spawn a new thread, as the messages will be pumped with a backpressure into the callback you provide.
// Yet, you still need to efficiently process each message in the callback without blocking for too long.
//
// Note also that if you go to http://tools.emqx.io/ and then connect and send a message to the specified topic,
// the client configured here should receive it.
thread::spawn(move || {
info!("MQTT Listening for messages");
while let Some(msg) = connection.next() {
match msg {
Err(e) => info!("MQTT Message ERROR: {}", e),
Ok(msg) => info!("MQTT Message: {:?}", msg),
}
}
info!("MQTT connection loop exit");
});
client.subscribe(AWS_IOT_TOPIC, QoS::AtMostOnce)?;
info!("Subscribed to all topics ({})", AWS_IOT_TOPIC);
client.publish(
AWS_IOT_TOPIC,
QoS::AtMostOnce,
false,
format!("Hello from {}!", AWS_IOT_TOPIC).as_bytes(),
)?;
info!("Published a hello message to topic \"{}\".", AWS_IOT_TOPIC);
Ok(client)
}
Here are the final lines of output when I try to run this on the device (it's setup to compile and flash to the device and monitor (debug mode) when running cargo run):
I (16913) esp32_aws_iot_with_std: About to start MQTT client
I (16923) esp32_aws_iot_with_std: MQTT client started
I (16923) esp32_aws_iot_with_std: MQTT Listening for messages
I (16933) esp32_aws_iot_with_std: MQTT Message: BeforeConnect
I (17473) esp-x509-crt-bundle: Certificate validated
E (19403) MQTT_CLIENT: mqtt_message_receive: transport_read() error: errno=119 # <- This is the actual error
E (19403) MQTT_CLIENT: esp_mqtt_connect: mqtt_message_receive() returned -1
E (19413) MQTT_CLIENT: MQTT connect failed
I (19413) esp32_aws_iot_with_std: MQTT Message ERROR: ESP_FAIL
I (19423) esp32_aws_iot_with_std: MQTT Message: Disconnected
E (19433) MQTT_CLIENT: Client has not connected
I (19433) esp32_aws_iot_with_std: MQTT connection loop exit
I (24423) esp_idf_svc::eventloop: Dropped
I (24423) esp_idf_svc::wifi: Stop requested
I (24423) wifi:state: run -> init (0)
I (24423) wifi:pm stop, total sleep time: 10737262 us / 14862601 us
W (24423) wifi:<ba-del>idx
I (24433) wifi:new:<1,0>, old:<1,1>, ap:<1,1>, sta:<1,0>, prof:1
W (24443) wifi:hmac tx: ifx0 stop, discard
I (24473) wifi:flush txq
I (24473) wifi:stop sw txq
I (24473) wifi:lmac stop hw txq
I (24473) esp_idf_svc::wifi: Stopping
I (24473) esp_idf_svc::wifi: Disconnect requested
I (24473) esp_idf_svc::wifi: Stop requested
I (24483) esp_idf_svc::wifi: Stopping
I (24483) wifi:Deinit lldesc rx mblock:10
I (24503) esp_idf_svc::wifi: Driver deinitialized
I (24503) esp_idf_svc::wifi: Dropped
I (24503) esp_idf_svc::eventloop: Dropped
Error: ESP_FAIL
This error seems to indicate the buffer holding the incoming data is full and can't hold any more data, but I'm not sure. And I definately don't know how to fix it.
(I assume the actual certificate handling is done properly)
When I run the following command, I do get the message in AWS IoT (MQTT test client):
mosquitto_pub -h my.amazonawsIoT.com --cafile server.cert.crt --cert client.cert.pem --key client.private.key -i basicPubSub -t my/topic -m 'test'
Does anyone have some more experience with this who can point me in the right direction?
Is this actually a buffer error, and if so: how do I mitigate this error? Do I need to increase the buffer size somehow (it is running on a basic ESP32 revision 1, ESP32_Devkitc_v4, if that helps). As far as I can tell this version has a 4MB flash size, so that might explain the buffer overlow, although I think this should be enough. The total memory used is under 35% of the total storage (App/part. size: 1347344/4128768 bytes, 32.63%)
UPDATE 1: I have been made aware that this data is stored in RAM, not in flash memory (didn't cross my mind at the time), but I'm not entirely sure on how large the RAM on my specific device is (ESP32 revision 1, ESP32_Devkitc_v4). My best guess is 320KB, but I'm not sure.
UPDATE 2: I've tried changing the buffer size like so:
let conf = MqttClientConfiguration {
client_id: Some(AWS_IOT_CLIENT_ID),
crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach),
server_certificate: Some(server_cert),
client_certificate: Some(client_cert),
private_key: Some(private_key),
buffer_size: 50, // added this (tried various sizes)
out_buffer_size: 50, // added this (tried various sizes)
..Default::default()
};
I've tried various combinations, but this doesn't seem to change much: either I get the exact same error, or this one (when choosing smaller numbers, for example 10):
E (18303) MQTT_CLIENT: Connect message cannot be created
E (18303) MQTT_CLIENT: MQTT connect failed
E (18313) MQTT_CLIENT: Client has not connected
I'm not sure how big this buffer size should be (when sending simple timestamps to AWS IoT), and can't find any documentation on what this number represents: is it in Bit, KiloBit, ... No idea.
I want to transfer a socket between 2 unrelated processes.
Process 1 creates and listens on the socket, it then attaches shared memory and stores the socket file descriptor in this shared memory and awaits on signal SIGUSR1.
Process 2 attaches to the shared, attempts to transfer the file descriptor, then sends the signal SIGUSR1.
On calling pidfd_getfd in process 2, I get the error EPERM.
Process 1 output
fd: Data { socket: 3, pid: Pid(167046) }
Process 2 output
fd: Data { socket: 3, pid: Pid(167046) }
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: EPERM', src/bin/receive.rs:44:74
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Process 1
use std::os::fd::{AsRawFd, RawFd};
use nix::fcntl;
use nix::sys::{
mman, signal,
socket::{self, SockaddrLike},
};
use nix::unistd::{ftruncate, Pid};
#[derive(Debug)]
#[repr(C)]
struct Data {
socket: RawFd,
pid: Pid,
}
const PATH: &str = "/some_arbitrary_path_3";
fn main() {
// Create a TCP socket listening on localhost:8080
// ---------------------------------------------------------------------------------------------
let socket = socket::socket(
socket::AddressFamily::Inet6,
socket::SockType::Stream,
socket::SockFlag::empty(),
None,
)
.unwrap();
let local_host = libc::in6_addr {
s6_addr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
};
let addr = libc::sockaddr_in6 {
sin6_family: u16::try_from(libc::AF_INET6).unwrap(),
sin6_port: 8765,
sin6_flowinfo: u32::default(),
sin6_addr: local_host,
sin6_scope_id: u32::default(),
};
let addr = unsafe {
socket::SockaddrIn6::from_raw(
std::ptr::addr_of!(addr).cast(),
Some(u32::try_from(std::mem::size_of::<libc::sockaddr_in6>()).unwrap()),
)
.unwrap()
};
socket::bind(socket, &addr).unwrap();
socket::listen(socket, 64).unwrap();
// Store socket file descriptor in shared memory
// ---------------------------------------------------------------------------------------------
let shared_memory_object = std::mem::ManuallyDrop::new(
mman::shm_open(
PATH,
fcntl::OFlag::O_RDWR | fcntl::OFlag::O_CREAT | fcntl::OFlag::O_EXCL,
// TODO: Restrict these to minimum (likely read+write group)
// Uses full permissions
nix::sys::stat::Mode::all(),
)
.unwrap(),
);
let length = std::mem::size_of::<Data>();
ftruncate(shared_memory_object.as_raw_fd(), length as i64).unwrap();
let mapped_shared_memory = unsafe {
mman::mmap(
None,
std::num::NonZeroUsize::new(length).unwrap(),
mman::ProtFlags::PROT_WRITE | mman::ProtFlags::PROT_READ,
mman::MapFlags::MAP_SHARED,
Some(&*shared_memory_object),
0,
)
.unwrap()
};
let ptr = mapped_shared_memory.cast::<Data>();
let pid = Pid::this();
unsafe {
std::ptr::write(ptr, Data { socket, pid });
}
println!("fd: {:?}", unsafe { &*ptr });
// Await SIGUSR1
// ---------------------------------------------------------------------------------------------
let mut sigset = signal::SigSet::empty();
sigset.add(signal::Signal::SIGUSR1);
sigset.wait().unwrap();
}
Process 2
use std::os::fd::AsRawFd;
use std::os::fd::RawFd;
use nix::fcntl;
use nix::sys::{mman, pidfd, signal::Signal};
use nix::unistd::Pid;
#[derive(Debug)]
#[repr(C)]
struct Data {
socket: RawFd,
pid: Pid,
}
const PATH: &str = "/some_arbitrary_path_3";
fn main() {
// Get shared memory object.
let shared_memory_object = std::mem::ManuallyDrop::new(
mman::shm_open(PATH, fcntl::OFlag::O_RDWR, nix::sys::stat::Mode::all()).unwrap(),
);
let length = std::mem::size_of::<Data>();
// Map shared memory.
let mapped_shared_memory = unsafe {
mman::mmap(
None,
std::num::NonZeroUsize::new(length).unwrap(),
mman::ProtFlags::PROT_WRITE | mman::ProtFlags::PROT_READ,
mman::MapFlags::MAP_SHARED,
Some(&*shared_memory_object),
0,
)
.unwrap()
};
// Read data.
let ptr = mapped_shared_memory.cast::<Data>();
let data = unsafe { &mut *ptr };
println!("fd: {:?}", data);
// Transfer socket file descriptor.
let pid_fd = pidfd::pid_open(data.pid, false).unwrap();
let new_socket = pidfd::pidfd_getfd(pid_fd.as_raw_fd(), data.socket).unwrap();
data.socket = new_socket.as_raw_fd();
// Send SIGUSR1
pidfd::pidfd_send_signal(pid_fd, Signal::SIGUSR1, None).unwrap();
}
Dependencies
The dependencies I'm using here are:
nix = { git = "https://github.com/JonathanWoollett-Light/nix", rev = "747b7abf9e2dd57d6e52b7d9288f836780f0ec15" }
libc = "0.2.139" # https://crates.io/crates/libc/0.2.139
There are a few ways to circumvent this.
Run the process as privileged (e.g. sudo).
Use ptrace in the ancestor to give the successor permissions. While this can work, it is very tricky and it is using an extremely over-complicated tool for what we are doing here.
Send the file descriptor over a UDS (Unix Domain Socket), this works, but sending file descriptors over a UDS is ugly.
Enable the SO_REUSEPORT option on the socket such that the new process can use the socket without needing to transfer the file descriptor. This is my preferred solution.
I am trying to route all my traffic by Iptables .
iptables -t nat -D OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:3400
to my Rust Code which is listening on specific port
let addrs = [
SocketAddr::from(([127, 0, 0, 1], 3400)),
];
let tcp = TcpListener::bind(&addrs[..]).expect("error bind tcp");
match tcp.accept() {
Ok((_socket,addr)) => println!("{:?} ",addr),
Err(_) => println!("error found"),
}
let mut buffer = [0;500];
let mut buf = unsafe {
slice::from_raw_parts_mut((&mut buffer).as_mut_ptr(),buffer.len())
};
for stream in tcp.incoming() {
let buf = stream.unwrap().read(buf).expect("stream read buffer ");
let result = StrType::from_utf8(&buffer).expect("result decode failed");
// println!("{:?} {:?}",buffer,buf);
println!("{:?}",buf);
println!("{}",result.len());
println!("{:?}\n\n",result);
}
then i want to read my data which UTF8 and i faced this such error .
thread 'main' panicked at 'result decode failed: Utf8Error { valid_up_to: 8, error_len: Some(1) }', src/main.rs:46:50
How can i resolve this error or how can i get data of requested ?
Thanks for your helping.
Since utf8 encoded strings' chars can vary in length from 1 to 4 bytes, when you are getting transfer over the network (or in other streaming way) it can happen, that packet (or the buffer you read into) is divided in the middle of a character. Rust requires that str and String types contains only valid utf8 encoded characters, so when you are trying to interpret the bytes as utf8 string it returns error.
Luckily this error type Utf8Error contains information about until which byte this byte slice is valid utf8. So you can use only the first, correct part, and the rest concatenate with further data. You can see the example of that in the linked documentation.
Also, you don't have to use unsafe slice::from_raw_parts_mut, just use &mut buffer.
I want to implement command tcpdump -i eth0 arp to observe arp packets on interface eth0 on my ubuntu. I use libpcap, but the return value of function pcap_next_ex is always 0. With tcpdump -i eth0 arp in the same time , it can observe arp packets.
/*
* compile(root): gcc test.c -lpcap
* run : ./a.out
* output : time out
* time out
* time out
* ...
*/
#include <pcap.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define ARP_REQUEST 1
#define ARP_REPLY 2
typedef struct arp_hdr_s arp_hdr_t;
struct arp_hdr_s {
u_int16_t htype;
u_int16_t ptype;
u_char hlen;
u_char plen;
u_int16_t oper;
u_char sha[6];
u_char spa[4];
u_char tha[6];
u_char tpa[4];
};
#define MAXBYTES2CAPTURE 2048
int
main(int argc, char **argv)
{
char err_buf[PCAP_ERRBUF_SIZE];
const unsigned char *packet;
int i;
int ret;
arp_hdr_t *arp_header;
bpf_u_int32 net_addr;
bpf_u_int32 mask;
pcap_t *desrc;
struct pcap_pkthdr *pkthdr;
struct bpf_program filter;
net_addr = 0;
mask = 0;
memset(err_buf, 0, PCAP_ERRBUF_SIZE);
desrc = pcap_open_live("eth0", MAXBYTES2CAPTURE, 0, 512, err_buf);
if (desrc == NULL) {
fprintf(stderr, "error: %s\n", err_buf);
exit(-1);
}
ret = pcap_lookupnet("eth0", &net_addr, &mask, err_buf);
if (ret < 0) {
fprintf(stderr, "error: %s\n", err_buf);
exit(-1);
}
ret = pcap_compile(desrc, &filter, "arp", 1, mask);
if (ret < 0) {
fprintf(stderr, "error: %s\n", pcap_geterr(desrc));
exit(-1);
}
ret = pcap_setfilter(desrc, &filter);
if (ret < 0) {
fprintf(stderr, "errnor: %s\n", pcap_geterr(desrc));
exit(-1);
}
while (1) {
ret = pcap_next_ex(desrc, &pkthdr, &packet);
if (ret == -1) {
printf("%s\n", pcap_geterr(desrc));
exit(1);
} else if (ret == -2) {
printf("no more\n");
} else if (ret == 0) { // here
printf("time out\n");
continue;
}
arp_header = (arp_hdr_t *)(packet + 14);
if (ntohs(arp_header->htype) == 1 && ntohs(arp_header->ptype == 0x0800)) {
printf("src IP: ");
for (i = 0; i < 4; i++) {
printf("%d.", arp_header->spa[i]);
}
printf("dst IP: ");
for (i = 0; i < 4; i++) {
printf("%d.", arp_header->tpa[i]);
}
printf("\n");
}
}
return 0;
}
Without getting too deep in your code, I can see a major problem:
In your use of pcap_open_live(), you do not set promiscuous mode: the third parameter should be non-zero. If the ARP request is not targeted to your interface IP, pcap will not see it without promiscuous mode. tcpdump does, unless specifically told not to do so by using the --no-promiscuous-mode, use promisc (and hence will require CAP_NET_ADMIN privilege, which you'll get by sudo, which your program will require too).
Side note:
1/ Leak: you may want to free your filter using pcap_freecode() after your pcap_setfilter().
2/ I assume you've read the official tuto here:
http://www.tcpdump.org/pcap.html
...if that's not the case you'd be well advised to do that first. I quote:
A note about promiscuous vs. non-promiscuous sniffing: The two
techniques are very different in style. In standard, non-promiscuous
sniffing, a host is sniffing only traffic that is directly related to
it. Only traffic to, from, or routed through the host will be picked
up by the sniffer. Promiscuous mode, on the other hand, sniffs all
traffic on the wire. In a non-switched environment, this could be all
network traffic. [... more stuff on promisc vs non-promisc]
EDIT:
Actually, looking deeper to you code compared to my code running for +1 year at production level (both in-house and at the customer) I can see many more things that could be wrong:
You never call pcap_create()
You never call pcap_set_promisc(), we've talked about this already
You never call pcap_activate(), this may be the core issue here
...pcap is very touchy about the sequence order of operations to first get a pcap_t handle, and then operate on it.
At the moment, the best advice I can give you - otherwise this is going to a live debugging session between you and me, are:
1/ read and play/tweak with the code from the official tutorial:
http://www.tcpdump.org/pcap.html
This is mandatory.
2/ FWIW, my - definitely working - sequence of operations is this:
pcap_lookupnet()
pcap_create()
pcap_set_promisc()
pcap_set_snaplen(), you may or may not need this
pcap_set_buffer_size(), you may or may not need this
pcap_activate() with a note: Very important: first activate, then set non-blocking from PCAP_SETNONBLOCK(3PCAP): When first activated with pcap_activate() or opened with pcap_open_live() , a capture handle is not in non-blocking mode''; a call to pcap_set-nonblock() is required in order to put it intonon-blocking'' mode.
...and then, because I do not use stinking blocking/blocking with timeout, busy looping:
pcap_setnonblock()
pcap_get_selectable_fd()
...then and only then:
- pcap_compile()
- followed by a pcap_setfilter()
- and then as I mentioned a pcap_freecode()
- and then a select() or family on the file'des' I get from pcap_get_selectable_fd(), to pcap_dispatch(), but this is another topic.
pcap is an old API starting back in the 80's, and its really very very touchy. But don't get discouraged! It's great - once you get it right.
It would probably work better if you did
if (ntohs(arp_header->htype) == 1 && ntohs(arp_header->ptype) == 0x0800) {
rather than
if (ntohs(arp_header->htype) == 1 && ntohs(arp_header->ptype == 0x0800)) {
The latter evaluates arp_header->type == 0x0800, which, when running on a little-endian machine (such as a PC), will almost always evaluate to "false", because the value will look like 0x0008, not 0x0800, in an ARP packet - ARP types are big-endian, so they'll look byte-swapped on a little-endian machine). That means it'll evaluate to 0, and byte-swapping 0 gives you zero, so that if condition will evaluate to "false", and the printing code won't be called.
You'll still get lots of timeouts if you fix that, unless there's a flood of ARP packets, but at least you'll get the occasional ARP packet printed out. (I would advise printing nothing on a timeout; pcap-based programs doing live capturing should expect that timeouts should happen, and should not report them as unusual occurrences.)
I am working on a Linux server that listens for UDP messages as part of a discovery protocol. My code for listening follows:
rcd = ::select(
socket_handle + 1,
&wait_list,
0, // no write
0, // no error
&timeout);
if(rcd > 0)
{
if(FD_ISSET(socket_handle,&wait_list))
{
struct sockaddr address;
socklen_t address_size = sizeof(address);
len = ::recvfrom(
socket_handle,
rx_buff,
max_datagram_size,
0, // no flags
&address,
&address_size);
if(len > 0 && address.sa_family == AF_INET)
{
struct sockaddr_in *address_in =
reinterpret_cast<struct sockaddr_in *>(&address);
event_datagram_received::cpost(
this,
rx_buff,
rcd,
ntohl(address_in->sin_addr.s_addr),
ntohs(address_in->sin_port));
}
}
}
In the meantime, I have written a windows client that transmits the UDP messages. I have verified using wireshark that the messages are being transmitted with the right format and length (five bytes). However, when I examine the return value for recvfrom(), this value is always one. The size of my receive buffer (max_datagram_size) is set to 1024. The one byte of the packet that we get appears to have the correct value. My question is: why am I not getting all of the expected bytes?
In case it matters, my Linux server is running under Debian 5 within a VirtualBox virtual machine.
nos answered my question in the first comment. I was using the wrong variable to report the buffer length.