広告
Linuxのpacketソケットを使って受信フレームの内容を表示してみます。
今回はEther + IPv4 + TCPで受信したフレームの各ヘッダ情報を表示するアプリケーションを作ります。Linux, C言語です。
packetソケットについての詳しい説明はMan page of PACKETとかを参照で。
L2/L3/L4ヘッダの構造体
各ヘッダのフォーマットと使用する構造体を確認しておきます。
・L2ヘッダ(Ethernet)
1 2 3 4 5 6 7 8 |
#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__)); |
1 |
・L3ヘッダ(IPv4)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#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. */ }; |
1 |
・L4ヘッダ(TCP)
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 |
#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; }; |
1 |
ソースコード
フレームを3回受信してヘッダを表示したら終了するサンプルです。
socket作成時にprotocolの引数にhtons(ETH_P_IP)を渡してPv4の場合のみ受信する指定をしてます。
packet.c
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
#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; } |
1 |
実行結果
実行してみるとこんな感じ。パケットキャプチャにはroot権限が必要ですのでsudo付きで実行します。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
[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]$ |
1 |
今回の例ではSSH(TCPの宛先ポート番号が22番)のセグメントが表示されています。
やってみると意外と簡単にできますね。
広告
広告