I have the seemingly simple task of printing very basic information regarding frames passing through a specific ethernet interface. I have a socket defined as
if ((sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) return __LINE__;
strcpy(ifr.ifr_name, argv[1]);
if (ioctl(sd, SIOCGIFFLAGS, &ifr) == -1) return __LINE__;
ifr.ifr_flags |= IFF_PROMISC;
if (ioctl(sd, SIOCSIFFLAGS, &ifr) == -1) return __LINE__;
if (ioctl(sd, SIOCGIFINDEX, &ifr) == -1) return __LINE__;
I loop my input like so
while (active) {
FD_SET(sd, &fds);
FD_SET(STDIN_FILENO, &fds);
if ((rv = select(sd + 1, &fds, NULL, NULL, &tv)) < 0)
active = 0;
if (FD_ISSET(sd, &fds)) input(sd, buf);
This is where I am having problems. I define the ethernet header each frame is cast into with a struct
struct ethheader {
unsigned char dsta[6];
unsigned char srca[6];
uint16_t type;
};
And output the information like
void input(int sd, char *buf) {
int i;
char *p = buf;
struct ethheader *eth = (struct ethheader*)buf;
int len = read(sd, buf, BUF_SIZ);
if (len < sizeof(struct ethheader)) {
printf("smaller than an ethernet frame\n");
return;
} else {
char dst[18];
char src[18];
for (i = 0; i < 6; i++) {
sprintf(dst + i * 3, "%02x:", eth->dsta[i]);
sprintf(src + i * 3, "%02x:", eth->srca[i]);
}
dst[17] = '\0';
src[17] = '\0';
printf("dst: %s src: %s ", dst, src);
switch (eth->type) {
case 0x0800:
printf("IPv4\n");
break;
case 0x0842:
printf("ARP\n");
break;
case 0x86DD:
printf("IPv6\n");
break;
default:
printf("unknown\n");
break;
}
}
}
The output i receive indicates I am properly printing MAC addresses, I am not properly detecting protocols. I am pretty sure the bug deals with either lvalue byte sizes, or endian order-ness; or both. And it is at this point I feel compelled to ask here how I can better define my structs values, and why my protocol switch is broken?
OK so after reading some comments, I was able to properly read the ethernet type:
struct ethheader {
unsigned char dsta[6];
unsigned char srca[6];
unsigned char type[2];
};
int type = (eth->type[0] << 8) + eth->type[1];
My secondary question remains: How can I better define these structs with more portable types; or am I fine with unsigned char?
structdeclaration tells you nothing about the binary format of the data. It is implementation-specific at best, and undefined behavior at worst, and has no place in portable code. IOW, you can't use Cstructdeclarations to unmarshall binary data. You have to manipulate each input byte explicitly to extract the data you need: yes, you do need to read individual bytes to reconstitute words of various lengths, etc.