packetソケットでキャプチャしてみる
Linuxのpacketソケットを使って受信フレームの内容を表示してみます。
今回はEther + IPv4 + TCPで受信したフレームの各ヘッダ情報を表示するアプリケーションを作ります。Linux, C言語です。
packetソケットについての詳しい説明はMan page of PACKETとかを参照で。
目次
L2/L3/L4ヘッダの構造体
各ヘッダのフォーマットと使用する構造体を確認しておきます。
・L2ヘッダ(Ethernet)
#include <net/ethernet.h>
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__));
・L3ヘッダ(IPv4)
#include <netinet/ip.h>
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
u_int8_t tos;
u_int16_t tot_len;
u_int16_t id;
u_int16_t frag_off;
u_int8_t ttl;
u_int8_t protocol;
u_int16_t check;
u_int32_t saddr;
u_int32_t daddr;
/*The options start here. */
};
・L4ヘッダ(TCP)
#include <netinet/tcp.h>
struct tcphdr
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
# if __BYTE_ORDER == __LITTLE_ENDIAN
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:4;
u_int16_t res1:4;
u_int16_t res2:2;
u_int16_t urg:1;
u_int16_t ack:1;
u_int16_t psh:1;
u_int16_t rst:1;
u_int16_t syn:1;
u_int16_t fin:1;
# else
# error "Adjust your <bits/endian.h> defines"
# endif
u_int16_t window;
u_int16_t check;
u_int16_t urg_ptr;
};
ソースコード
フレームを3回受信してヘッダを表示したら終了するサンプルです。
socket作成時にprotocolの引数にhtons(ETH_P_IP)を渡してPv4の場合のみ受信する指定をしてます。
packet.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <stdint.h>
#define MAX_EVENTS 10
#define PERROR(X) \
{\
char __strerr[128] = {0};\
int errcode = errno;\
\
strerror_r(errcode, __strerr, sizeof(__strerr));\
printf(X " failed(%d:%s)\n", errcode, __strerr);\
}
int main(int argc, char *argv)
{
int ret = 0;
int rc = 0;
int sfd_raw = -1;
/* raw socketを用意 */
sfd_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
if (sfd_raw < 0) {
PERROR("socket");
goto error_end;
}
int epollfd = -1;
int nfds = -1;
struct epoll_event events[MAX_EVENTS];
struct epoll_event evt = {
.events = EPOLLIN,
.data = {
.fd = sfd_raw,
},
};
epollfd = epoll_create(MAX_EVENTS);
if (epollfd < 0) {
PERROR("epoll_create()");
close(sfd_raw);
goto error_end;
}
rc = epoll_ctl(epollfd, EPOLL_CTL_ADD, sfd_raw, &evt);
if (rc != 0) {
PERROR("epoll_ctl()");
close(sfd_raw);
close(epollfd);
goto error_end;
}
#define FRAME_COUNT_MAX (3)
int frame_cnt = 0;
for(;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds < 0) {
PERROR("epoll_wait()");
break;
}
int fd = 0;
for (fd = 0; fd < MAX_EVENTS; fd++) {
if (events[fd].data.fd == sfd_raw) {
/* try to recieve */
uint8_t buf[65535] = {0};
ssize_t sz = recv(sfd_raw, &buf, sizeof(buf), 0);
if (sz < 0) {
PERROR("recv");
close(sfd_raw);
close(epollfd);
goto error_end;
} else if (sz == 0) {
/* what's the matter ? */
printf("unknown event was happend.\n");
break;
} else {
/* 受信フレームの表示(IPv4 + TCPのみ) */
printf("frame:%d %d bytes recieved\n", frame_cnt+1, sz);
/* Ether Header */
struct ether_header *eth = (struct ether_header *) buf;
printf("======== ETHERNET HEADER ========\n");
printf("Destination MAC Address = %02x:%02x:%02x:%02x:%02x:%02x\n",
eth->ether_dhost[0],
eth->ether_dhost[1],
eth->ether_dhost[2],
eth->ether_dhost[3],
eth->ether_dhost[4],
eth->ether_dhost[5]);
printf("Source MAC Address = %02x:%02x:%02x:%02x:%02x:%02x\n",
eth->ether_shost[0],
eth->ether_shost[1],
eth->ether_shost[2],
eth->ether_shost[3],
eth->ether_shost[4],
eth->ether_shost[5]);
printf("Ethernet Type = 0x%04x\n", ntohs(eth->ether_type));
/* IP Header */
if (ntohs(eth->ether_type) == ETHERTYPE_IP) {
struct iphdr* ip = (struct iphdr *)(eth + 1);
printf("======== IP HEADER ========\n");
printf("Version = %u\n", ip->version);
printf("IHL = %u(%u bytes)\n", ip->ihl, ip->ihl*32);
printf("Type of Service = 0x%02x[%u%u%u%u%u%u%u%u]\n",
ip->tos,
(ip->tos >> 7) & 0x01,
(ip->tos >> 6) & 0x01,
(ip->tos >> 5) & 0x01,
(ip->tos >> 4) & 0x01,
(ip->tos >> 3) & 0x01,
(ip->tos >> 2) & 0x01,
(ip->tos >> 1) & 0x01,
(ip->tos) & 0x01);
printf("Total Length = %u bytes\n", ntohs(ip->tot_len));
printf("Identification = %u\n", ntohs(ip->id));
printf("Flags = 0x%02x[%u%u%u]\n",
ip->frag_off,
(ip->frag_off >> 2) & 0x01,
(ip->frag_off >> 1) & 0x01,
(ip->frag_off) & 0x01);
printf("TTL = %u\n", ip->ttl);
printf("Protocol = %u\n", ip->protocol);
printf("Check-Sum = 0x%04x\n", ntohs(ip->check));
struct in_addr saddr;
struct in_addr daddr;
saddr.s_addr = ip->saddr;
daddr.s_addr = ip->daddr;
printf("Source Address = %s\n", inet_ntoa(saddr));
printf("Destination Address = %s\n", inet_ntoa(daddr));
/* TCP Header */
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr* tcp = (struct tcphdr* )(ip+1);
printf("======== TCP HEADER ========\n");
printf("Source Port = %u\n", ntohs(tcp->source));
printf("Destination Port = %u\n", ntohs(tcp->dest));
printf("Sequence Number = %lu\n", ntohs(tcp->seq));
printf("Ack Sequence Number = %lu\n", ntohs(tcp->ack_seq));
printf("Data Offset = %u\n", tcp->doff);
printf("FIN|SYN|RST|PSH|ACK|URG = %u%u%u%u%u%u\n",
tcp->fin, tcp->syn, tcp->rst, tcp->psh, tcp->ack, tcp->urg);
printf("Window Size = %u\n", ntohs(tcp->window));
printf("Check-Sum = 0x%04x\n", ntohs(tcp->check));
printf("Urgent Pointer = 0x%04x\n", ntohs(tcp->urg_ptr));
}
}
printf("\n");
frame_cnt++;
if (frame_cnt >= FRAME_COUNT_MAX) {
goto end;
}
}
}
}
}
end:
close(sfd_raw);
close(epollfd);
error_end:
return 0;
}
実行結果
実行してみるとこんな感じ。パケットキャプチャにはroot権限が必要ですのでsudo付きで実行します。
[user@vm1 packet]$ gcc packet.c
[user@vm1 packet]$ sudo ./a.out
frame:1 150 bytes recieved
======== ETHERNET HEADER ========
Destination MAC Address = 08:00:27:82:c7:20
Source MAC Address = 0a:00:27:00:00:10
Ethernet Type = 0x0800
======== IP HEADER ========
Version = 4
IHL = 5(160 bytes)
Type of Service = 0x00[00000000]
Total Length = 136 bytes
Identification = 2872
Flags = 0x40[000]
TTL = 128
Protocol = 6
Check-Sum = 0xa580
Source Address = 192.168.100.1
Destination Address = 192.168.100.101
======== TCP HEADER ========
Source Port = 50312
Destination Port = 22
Sequence Number = 14001
Ack Sequence Number = 55884
Data Offset = 5
FIN|SYN|RST|PSH|ACK|URG = 000110
Window Size = 254
Check-Sum = 0xda51
Urgent Pointer = 0x0000
frame:2 60 bytes recieved
======== ETHERNET HEADER ========
Destination MAC Address = 08:00:27:82:c7:20
Source MAC Address = 0a:00:27:00:00:10
Ethernet Type = 0x0800
======== IP HEADER ========
Version = 4
IHL = 5(160 bytes)
Type of Service = 0x00[00000000]
Total Length = 40 bytes
Identification = 2873
Flags = 0x40[000]
TTL = 128
Protocol = 6
Check-Sum = 0xa5df
Source Address = 192.168.100.1
Destination Address = 192.168.100.101
======== TCP HEADER ========
Source Port = 50312
Destination Port = 22
Sequence Number = 14001
Ack Sequence Number = 55884
Data Offset = 5
FIN|SYN|RST|PSH|ACK|URG = 000010
Window Size = 254
Check-Sum = 0x4ecd
Urgent Pointer = 0x0000
frame:3 60 bytes recieved
======== ETHERNET HEADER ========
Destination MAC Address = 08:00:27:82:c7:20
Source MAC Address = 0a:00:27:00:00:10
Ethernet Type = 0x0800
======== IP HEADER ========
Version = 4
IHL = 5(160 bytes)
Type of Service = 0x00[00000000]
Total Length = 40 bytes
Identification = 2874
Flags = 0x40[000]
TTL = 128
Protocol = 6
Check-Sum = 0xa5de
Source Address = 192.168.100.1
Destination Address = 192.168.100.101
======== TCP HEADER ========
Source Port = 50312
Destination Port = 22
Sequence Number = 14001
Ack Sequence Number = 55884
Data Offset = 5
FIN|SYN|RST|PSH|ACK|URG = 000010
Window Size = 256
Check-Sum = 0x4917
Urgent Pointer = 0x0000
[user@vm1 packet]$
今回の例ではSSH(TCPの宛先ポート番号が22番)のセグメントが表示されています。
やってみると意外と簡単にできますね。
コメント