データリンク層を扱えるソケットを使って、受信したデータのEthernetヘッダを表示してみました。自分でEhternetフレームを定義する必要はなく、すでにライブラリに準備されています。
Linuxの"net/ethernet.h"で定義されているEthernetヘッダは以下の通りです。
1 2 3 4 5 6 |
struct ether_header { u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */ u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */ u_int16_t ether_type; /* packet type ID field */ } __attribute__ ((__packed__)); |
コードは以下です。Ethernet以外にも各階層毎に様々なプロトコルがあります。なので、それぞれに対応したコードをmain関数に詰め込むと大変なことになりますので、本当はEthernetヘッダを表示させるための機能をまとめた関数を別に定義させるべきです。今回は、Ethernetだけしか対応していないのでmain関数に詰め込みました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include<stdio.h> #include<string.h> #include<sys/ioctl.h> #include<sys/socket.h> #include<net/if.h> #include<net/ethernet.h> #include<netpacket/packet.h> #include<arpa/inet.h> #include<linux/if.h> #include<unistd.h> /*Macアドレスを文字列に変換するための関数*/ char *macaddr_to_str(u_char *hwaddr, u_char *mac, size_t size) { snprintf(mac, size, "%02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); return mac; } int main(void) { struct ifreq ifreq; struct sockaddr_ll sa; char buf[1500]; int len; u_char mac[32]; /*ソケットの生成*/ int s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if(s<0){ perror("socket"); } /*ソケットとインターフェースを結びつける*/ memset(&ifreq, 0, sizeof(struct ifreq)); strncpy(ifreq.ifr_name, "enp0s8", sizeof(ifreq.ifr_name)-1); //インターフェースはenp0s8を指定 if(ioctl(s, SIOCGIFINDEX, &ifreq)!=0) perror("ioctl"); sa.sll_ifindex = ifreq.ifr_ifindex; sa.sll_family = PF_PACKET; sa.sll_protocol = htons(ETH_P_ALL); if(bind(s, (struct sockaddr *)&sa, sizeof(sa))!=0){ perror("bind"); } /*データを受信してEtherヘッダーを表示する*/ while(1){ if((len=recv(s, buf, sizeof(buf), 0))<=0){ perror("recv"); } else{ printf("Data Size=%d\n",len); //受信データのサイズ struct ether_header *eh = (struct ether_header *)buf; //受信データをethernet構造体にキャスト printf("Dest MAC = %s\n", macaddr_to_str(eh->ether_dhost, mac, sizeof(mac))); printf("Src MAC = %s\n", macaddr_to_str(eh->ether_shost, mac, sizeof(mac))); printf("Ether Type = %x\n\n", ntohs(eh->ether_type)); } } return 0; } |
socketの生成をしたら、recvで受信したデータをbufに格納します。データはchar型にただ詰め込まれただけなので、操作したいプロトコルに対応した型に変換してやる必要があります。上述したように、各型はlinuxで定義されています。
あと、MACアドレスは、"〇:〇:〇:〇:〇:〇"の形で表示させるために、snprintfで文字列に変換してあげる必要がります。なのでmain関数とは別に変換のための関数が定義されています。
このプログラムはenp0s8というインターフェースで動かしているので、enp0s8に向けて例えばpingを通せば、Ehternetヘッダが表示されます。またインタフェース名を変えれば、そのインタフェース上で動かせます。
最後まで読んでいただきありがとうございました。