Segmentation Faultぐ

Segmentation Fault

コアダンプの数だけ強くなれるよ。

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番)のセグメントが表示されています。

やってみると意外と簡単にできますね。