Dear all,

As a by-product of work I am doing on Minix, I created a simple packet
sniffer. I think this may be of use to others as well to debug
applications using the network or to diagnose network problems.

To run be able to it, save the C program below this post as msniff.c
and compile it using something like "cc -O2 -o msniff msniff.c".

One can run the program without arguments to send information about
all packets on the primary ethernet device to the standard output.
More usage instructions are found directly under this post.

The program can currently parse and display the most important
ethernet, IPv4, TCP and UDP headers. Feel free to add to this or fix
bugs (although if you really need more you may want to port some
existing program like tcpdump instead).

With kind regards,
Erik van der Kouwe





Usage:
msniff [-hHxaAsSb] []

This program opens the specified ethernet device and outputs
all incoming and outgoing packets to stdout. If
is not specified, /dev/eth is assumed.

Possible output formats are the following:
-h Print basic headers
-H Print all known headers parsed from packet
-x Print packet contents in hex
-a Print packet contents as ASCII, replacing all control
characters and non-ASCII characters by dots
-s Print packet size in decimal
-A Print packet contents as text, replacing all control
characters except newline and tab by dots
-S Print packet size in binary as host order uint
-b Print packet contents in binary

Multiple output formats can be combined. In this case they
are printed on separate lines in the sequence listed here
except that:
- output from -x and -a is printed side by side;
- output from -S and -b does not add newlines.

If no output formats are specified, the default is -hHxa.





/*
* Copyright (c) 2008, Erik van der Kouwe
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#define _POSIX_SOURCE

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

/* configuration parameters, set in main */
#define PRINT_HEADERS_BASIC (1 << 0) /* h */
#define PRINT_HEADERS_ALL (1 << 1) /* H */
#define PRINT_CONTENT_HEX (1 << 2) /* x */
#define PRINT_CONTENT_TXT (1 << 3) /* a */
#define PRINT_SIZE_DEC (1 << 4) /* s */
#define PRINT_CONTENT_TXT2 (1 << 5) /* A */
#define PRINT_SIZE_BIN (1 << 6) /* S */
#define PRINT_CONTENT_BIN (1 << 7) /* b */

static const char *config_eth_name = "/dev/eth";
static int config_print = 0;

/* maximum expected packet size - larger packets are truncated */
#define PACKET_SIZE_MAX 0x600

/* number of characters per line in hex dump */
#define PRINT_CHARS_PER_LINE 80
#define PRINT_BYTES_PER_GROUP 4

/* packet type: ethernet */
typedef struct
{
ether_addr_t dst;
ether_addr_t src;
unsigned short typ;
} header_ether_t;

#define HEADER_SIZE_ETHER (sizeof(header_ether_t))

/* packet type: IPv4 */
typedef struct
{
unsigned char ver_hlen;
unsigned char tos;
unsigned short len;
unsigned short id;
unsigned short fl_foff;
unsigned char ttl;
unsigned char prot;
unsigned short csum;
unsigned char src[4];
unsigned char dst[4];
} header_ipv4_t;

#define HEADER_SIZE_IPV4 (HEADER_SIZE_ETHER + sizeof(header_ipv4_t))

/* packet type: TCP */
typedef struct
{
unsigned short src;
unsigned short dst;
unsigned int seq;
unsigned int ack;
unsigned char doff;
unsigned char fl;
unsigned short wsz;
unsigned short csum;
unsigned short urg;
} header_tcp_t;

#define HEADER_SIZE_TCP (HEADER_SIZE_IPV4 + sizeof(header_tcp_t))

/* packet type: UDP */
typedef struct
{
unsigned short src;
unsigned short dst;
unsigned short len;
unsigned short csum;
} header_udp_t;

#define HEADER_SIZE_UDP (HEADER_SIZE_IPV4 + sizeof(header_udp_t))

/* function prototypes */
static int eth_open(void);
static int eth_receive(int fd);
static void print_headers_eth(header_ether_t *packet, size_t size);
static void print_headers_ipv4(header_ipv4_t *packet, size_t size);
static void print_headers_tcp(header_tcp_t *packet, size_t size);
static void print_headers_udp(header_udp_t *packet, size_t size);
static void print_packet(char *packet, size_t size);
static void print_packet_binary(char *packet, size_t size,
int escape_control);
static void print_packet_hex(char *packet, size_t size,
int include_hex, int include_ascii);
static void print_usage(const char *name);

static int eth_open(void)
{
nwio_ethopt_t ethopt;
int fd;

/* attempt to open eth */
fd = open(config_eth_name, O_RDONLY);
if (fd < 0)
{
fprintf(stderr, "msniff : cannot open %s : %s\n",
config_eth_name, strerror(errno));
return -1;
}

/* configure the fd */
memset(&ethopt, 0, sizeof(ethopt));
ethopt.nweo_flags =
NWEO_COPY | /* receive copies of incoming packets */
NWEO_EN_LOC | /* receive local packets */
NWEO_EN_BROAD | /* receive broadcast packets */
NWEO_EN_MULTI | /* receive multicast packets */
NWEO_EN_PROMISC | /* receive all other packets */
NWEO_REMANY | /* no restriction on destinations */
NWEO_TYPEANY | /* no restriction on packet types */
NWEO_RWDATALL; /* ethernet header included */
if (ioctl(fd, NWIOSETHOPT, &ethopt) < 0)
{
fprintf(stderr, "msniff : cannot configure %s : %s\n",
config_eth_name, strerror(errno));
close(fd);
return -1;
}

return fd;
}

static int eth_receive(int fd)
{
char packet[PACKET_SIZE_MAX];
ssize_t size;

/* read packet */
size = read(fd, packet, sizeof(packet));
if (size < 0)
{
if (errno == EINTR)
return 0;

fprintf(stderr, "msniff : failed reading packet : %s\n",
strerror(errno));
return -1;
}

/* we do not want length zero (eof) */
if (size == 0)
{
fprintf(stderr, "msniff : end-of-file unexpected\n");
return -1;
}

/* print the packet we read */
print_packet(packet, size);
return 0;
}

static void print_headers_eth(header_ether_t *packet, size_t size)
{
/* source and destination */
printf("ether %.2x:%.2x:%.2x:%.2x:%.2x:%.2x->"
"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x, type ",
packet->src.ea_addr[0],
packet->src.ea_addr[1],
packet->src.ea_addr[2],
packet->src.ea_addr[3],
packet->src.ea_addr[4],
packet->src.ea_addr[5],
packet->dst.ea_addr[0],
packet->dst.ea_addr[1],
packet->dst.ea_addr[2],
packet->dst.ea_addr[3],
packet->dst.ea_addr[4],
packet->dst.ea_addr[5]);

/* ethernet packet type */
switch (ntohs(packet->typ))
{
case 0x0800:
/* IPv4 headers */
printf("IPv4\n");
if (size >= HEADER_SIZE_IPV4)
print_headers_ipv4((header_ipv4_t *) (packet + 1), size);
break;

case 0x0806:
printf("ARP\n");
break;

case 0x8035:
printf("RARP\n");
break;

case 0x86DD:
printf("IPv6\n");
break;

default:
printf("%u\n", ntohs(packet->typ));
break;
}
}

static void print_headers_ipv4(header_ipv4_t *packet, size_t size)
{
int header_size_diff;
char *data;

/* IPv4 headers */
printf("IPv4 %u.%u.%u.%u->%u.%u.%u.%u\n"
" len %5u, id %5u, ttl %3u, flags %s %s %s, type ",
packet->src[0],
packet->src[1],
packet->src[2],
packet->src[3],
packet->dst[0],
packet->dst[1],
packet->dst[2],
packet->dst[3],
ntohs(packet->len),
ntohs(packet->id),
packet->ttl,
(ntohs(packet->fl_foff) & 0x8000) ? "EVIL" : " ",
(ntohs(packet->fl_foff) & 0x4000) ? "DF" : " ",
(ntohs(packet->fl_foff) & 0x2000) ? "MF" : " ");

/* compute header size */
header_size_diff = (packet->ver_hlen & 0x0f) * 4 -
sizeof(header_ipv4_t);
data = (char *) (packet + 1) + header_size_diff;

/* protocol */
switch (packet->prot)
{
case 0x01:
printf("ICMP\n");
break;

case 0x06:
printf("TCP\n");
if (size >= HEADER_SIZE_TCP + header_size_diff)
print_headers_tcp((header_tcp_t *) data,
size - header_size_diff);
break;

case 0x11:
printf("UDP\n");
if (size >= HEADER_SIZE_UDP + header_size_diff)
print_headers_udp((header_udp_t *) data,
size - header_size_diff);
break;

default:
printf("%d\n", packet->prot);
break;
}
}

static void print_headers_tcp(header_tcp_t *packet, size_t size)
{
printf("TCP %5u->%5u, seq 0x%.8x, ack 0x%.8x, window %5u\n"
" flags %s %s %s %s %s %s %s %s\n",
ntohs(packet->src),
ntohs(packet->dst),
(unsigned) ntohl(packet->seq),
(unsigned) ntohl(packet->ack),
ntohs(packet->wsz),
(packet->fl & 0x80) ? "CWR" : " ",
(packet->fl & 0x40) ? "ECE" : " ",
(packet->fl & 0x20) ? "URG" : " ",
(packet->fl & 0x10) ? "ACK" : " ",
(packet->fl & 0x08) ? "PSH" : " ",
(packet->fl & 0x04) ? "RST" : " ",
(packet->fl & 0x02) ? "SYN" : " ",
(packet->fl & 0x01) ? "FIN" : " ");
}

static void print_headers_udp(header_udp_t *packet, size_t size)
{
printf("UDP %5u->%5u\n",
ntohs(packet->src),
ntohs(packet->dst));
}

static void print_packet(char *packet, size_t size)
{
static unsigned packet_index = 0;

struct tm *time;
struct timeval timeval;

/* basic headers */
if (config_print & PRINT_HEADERS_BASIC)
{
memset(&timeval, 0, sizeof(timeval));
gettimeofday(&timeval, NULL);
time = gmtime(&timeval.tv_sec);
printf("packet %5u, %4u bytes, "
"%4d-%.2d-%.2d %2d:%.2d:%.2d.%.3d\n",
packet_index++,
(unsigned) size,
time->tm_year + 1900,
time->tm_mon + 1,
time->tm_mday,
time->tm_hour,
time->tm_min,
time->tm_sec,
(int) (timeval.tv_usec / 1000));
}

/* parsed headers */
if (config_print & PRINT_HEADERS_ALL &&
size >= HEADER_SIZE_ETHER)
print_headers_eth((header_ether_t *) packet, size);

/* hex and formatted ASCII contents */
if (config_print & (PRINT_CONTENT_HEX | PRINT_CONTENT_TXT))
print_packet_hex(packet, size,
config_print & PRINT_CONTENT_HEX,
config_print & PRINT_CONTENT_TXT);

/* size in decimal */
if (config_print & PRINT_SIZE_DEC)
printf("%u\n", (unsigned) size);

/* ASCII contents */
if (config_print & PRINT_CONTENT_TXT2)
{
print_packet_binary(packet, size, 1);
printf("\n");
}

/* size in binary */
if (config_print & PRINT_SIZE_BIN)
print_packet_binary((char *) &size, sizeof(size), 0);

/* binary contents */
if (config_print & PRINT_CONTENT_BIN)
print_packet_binary(packet, size, 0);

/* newline separates packets, unless there is binary output */
if (!(config_print & (PRINT_SIZE_BIN | PRINT_CONTENT_BIN)))
printf("\n");

/* make sure everything gets written */
fflush(stdout);
}

static void print_packet_binary(char *packet, size_t size,
int escape_control)
{
unsigned char c;
int i;

/* print data byte by byte */
for (i = 0; i < size; i++)
{
c = packet[i];
if (escape_control && c < ' ' && c != '\n' && c != '\t')
printf(".");
else
printf("%c", c);
}
}

static void print_packet_hex(char *packet, size_t size,
int include_hex, int include_ascii)
{
char c;
int chars_avail, chars_group, chars_per_line, groups_per_line;
int i, j;

/* compute number of four-byte groups per line */
chars_avail = PRINT_CHARS_PER_LINE - 6;
chars_group = 0;

if (include_hex)
chars_group += PRINT_BYTES_PER_GROUP * 3 + 1;

if (include_ascii)
{
chars_avail -= 2;
chars_group += PRINT_BYTES_PER_GROUP;
}

groups_per_line = chars_avail / chars_group;
chars_per_line = groups_per_line * PRINT_BYTES_PER_GROUP;

/* print data */
for (i = 0; i < size; i += chars_per_line)
{
printf("0x%.4x", i);

/* hex representation */
if (include_hex)
for (j = 0; j < chars_per_line; j++)
{
if (!(j % PRINT_BYTES_PER_GROUP))
printf(" ");

if (i + j >= size)
printf(" ");
else
printf(" %.2x", (unsigned char) packet[i + j]);
}

/* ASCII representation */
if (include_ascii)
{
printf(" ");
for (j = 0; j < chars_per_line; j++)
if (i + j >= size)
printf(" ");
else
{
c = packet[i + j];
if ((unsigned char) c < ' ' ||
(unsigned char) c >= 127)
printf(".");
else
printf("%c", c);
}
}

printf("\n");
}
}

static void print_usage(const char *name)
{
printf(
"Usage:\n"
" %s [-hHxaAsSb] []\n"
"\n"
"This program opens the specified ethernet device and outputs "
"all incoming and outgoing packets to stdout. If "
"is not specified, /dev/eth is assumed.\n"
"\n"
"Possible output formats are the following:\n"
" -h Print basic headers\n"
" -H Print all known headers parsed from packet\n"
" -x Print packet contents in hex\n"
" -a Print packet contents as ASCII, replacing all control "
"characters and\n"
" non-ASCII characters by dots\n"
" -s Print packet size in decimal\n"
" -A Print packet contents as text, replacing all control "
"characters except\n"
" newline and tab by dots\n"
" -S Print packet size in binary as host order uint\n"
" -b Print packet contents in binary\n"
"\n"
"Multiple output formats can be combined. In this case they "
"are printed on separate lines in the sequence listed here "
"except that:\n"
" - output from -x and -a is printed side by side;\n"
" - output from -S and -b does not add newlines.\n"
"\n"
"If no output formats are specified, the default is -hHxa.\n",
name);
exit(1);
}

int main(int argc, char **argv)
{
const char *arg_current, *exec_name;
int eth_fd;

/* parse arguments */
exec_name = argv[0];
while ((arg_current = *++argv))
if (arg_current[0] == '-')
{
while (*++arg_current)
switch (*arg_current)
{
case 'h': config_print |= PRINT_HEADERS_BASIC; break;
case 'H': config_print |= PRINT_HEADERS_ALL; break;
case 'x': config_print |= PRINT_CONTENT_HEX; break;
case 'a': config_print |= PRINT_CONTENT_TXT; break;
case 's': config_print |= PRINT_SIZE_DEC; break;
case 'A': config_print |= PRINT_CONTENT_TXT2; break;
case 'S': config_print |= PRINT_SIZE_BIN; break;
case 'b': config_print |= PRINT_CONTENT_BIN; break;
default: print_usage(exec_name);
}
}
else
{
config_eth_name = arg_current;
if (argv[1])
print_usage(exec_name);
}

/* default output */
if (!config_print)
config_print =
PRINT_HEADERS_BASIC | PRINT_HEADERS_ALL |
PRINT_CONTENT_HEX | PRINT_CONTENT_TXT;

/* handle incoming data */
eth_fd = eth_open();
if (eth_fd < 0)
return -1;

/* we should keep on receiving forever */
while (eth_receive(eth_fd) >= 0);

/* error occurred; close ethernet and exit */
close(eth_fd);
return -1;
}