0

I am writing a packet sniffer in C++ utilizing streams instead of printf() to store and create output. The problem I've run into is that recvfrom() seems to fail and return -1 when I have two or more statements that generate output using a stream.

If I comment one of the two output generating statements, the program runs fine. Through trial and error, I've found that by removing the std::setw() from the std::cout statement, it will work correctly and display both the packet and the "beef" message.

Any ideas or help would be much appreciated as I am at a loss and considering reverting back to using printf() since it never had this problem (and is faster than a stream). I admit, this is really the first time I have ever used ostringstream and I may be using it incorrectly.

My simplified source code:

#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <linux/if_packet.h>

std::string BufferInHex( unsigned char * buffer, int length )
{
    std::ostringstream out;
    for( int i = 0; i < length; i ++ ) {
        if( i % 16 == 0 && i != 0 ) {
            out << "\n";
        }
        else if( i % 8 == 0 && i != 0 ) {
            out << "  ";
        }
        out << std::hex;
        out << std::setfill('0') << std::setw(2) << static_cast<unsigned>(buffer[i]) << " ";
        out << std::dec;
    }
    return out.str();
}

int main( void )
{
    struct sockaddr_ll saddr = {0};
    socklen_t saddr_size = sizeof(saddr);
    unsigned char packet[1500] = {0};

    int sockFd = socket( AF_PACKET, SOCK_RAW, htons(ETH_P_ALL) );
    if( sockFd < 0 ) {
        std::cerr << "Error creating socket!\n";
        return 1;
    }

    int data_size = recvfrom( sockFd, packet, sizeof( packet ), 0, (struct sockaddr*)&saddr, &saddr_size );
    if( data_size == -1 ) {
        std::cerr << "Error in recvfrom()\n";
        return 2;
    }

    std::cout << std::hex;
    std::cout << std::setw( 8 ) << ntohs( 0xADDE ) << "\n";
    std::cout << std::dec;
    std::cout << BufferInHex( packet, data_size ) << "\n";
    return 0;
}

It is being compiled with g++ on Centos 6.4 kernel 2.6.32 using the following command:

g++ sniff.cpp -o sniff -Wall

Thanks for any ideas or help,
Jeremiah

Jeremiah
  • 86
  • 4
  • Unrelated to the problem: You need a ; instead of a , in the first for loop. Also std::hex and std::dec don't get streamed to out in BufferInHex. Apart from that, it runs fine for me. – sj0h Mar 27 '14 at 23:15
  • 1
    I notice that saddr_size is set to zero, which doesn't matter as you don't use saddr anyhow. but it's a bit odd. To debug, it would help to know the error that recvfrom is giving. You can print it using strerror(errno) – sj0h Mar 27 '14 at 23:23
  • @sj0h: `saddr_size` does matter. It tells `recvfrom() how large `saddr` is before `recvfrom()` can fill it. It is an in/out parameter, you must initialize `saddr_size` with the actual size of `saddr`, and then `recvfrom()` will update `saddr_size` to specify the actual size of the address saved in `saddr`. Since the code is using `sockaddr` directly (it should be using `sockaddr_ll` or `sockaddr_pkt` instead), `recvfrom()` will fail if it receives a packet whose address to too large for `saddr` to access. `sockaddr` is not large enough to hold an IPv6 address, for instance. – Remy Lebeau Mar 28 '14 at 01:12
  • I thought it just truncated the addr when the field was too small, so that's something to bear in mind. Still, it doesn't seem to explain why the stream operations could affect the behaviour. – sj0h Mar 28 '14 at 01:37
  • I'll change the sockaddr to use a correct structure and see what happens. I've also stepped through it with gdb and nothing is yelling at me. I'm on a vm, so it could be the environment, however I've not run into this problem before. If the weirdness doesn't show up, try adding the "dead" output message a few more times and see if that causes some weird behavior, in my larger program, every once in a while it will start working, though if I add another output statement it starts to fail again. Thanks for the help. – Jeremiah Mar 28 '14 at 03:46
  • As the man page says, "In the event of an error, errno is set to indicate the error." – David Schwartz Mar 28 '14 at 04:44
  • `errno` turns out to have no idea what the error was. After modifying the program to use `sockaddr_ll` instead of `sockaddr`, it seems to be working. Go figure I use the wrong data structure. Thanks for the good eyes! – Jeremiah Mar 31 '14 at 13:37

0 Answers0