0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語で作る!Raw SocketによるTCP SYNパケット送信の実験

Last updated at Posted at 2025-08-10

初めに

前回はソケットを用いてHTTPリクエストを送信する実験を行いました。
(参考)https://qiita.com/earthen94/items/2ba51c53671f7fc4f2b4

今回は通信の仕組みをより深く理解する為、自分でIPおよびTCPヘッダを組み立て、もう一台のパソコンへTCPのSYNパケットを送信する実験を行います。

前提知識

IP (ネットワーク層)

IPアドレスを以て宛先を指定し、機器間の通信を実現する。
信頼性なし。パケットが紛失したり、順序が前後する可能性がある。
ただ送信するのみで事前に接続を確立することはない。

TCP (トランスポート層)

ポート番号を以てアプリケーション間の通信を実現する。
信頼性あり。パケットの再送、順序制御、誤り検出を行う。
データ送信の前に接続を確立する。(TCP 3ウェイハンドシェイク)

実験

送信元PC

ソースをコンパイルし実行する。

test@test-ThinkPad-X280:~/tcpip$ gcc raw_syn.c -o raw_syn
test@test-ThinkPad-X280:~/tcpip$ sudo ./raw_syn 
SYN packet sent

受信PC

IPアドレスを確認する。
tcpdumpを使い受信したパケットを即時で表示する

ubuntu@ubuntu:~$ hostname -I
192.168.68.96
ubuntu@ubuntu:~$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN mode DEFAULT group default qlen 1000
link/ether 2c:f0:5d:68:df:5b brd ff:ff:ff:ff:ff:ff
3: wlo1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DORMANT group default qlen 1000
link/ether a4:b1:c1:a2:9b:a3 brd ff:ff:ff:ff:ff:ff
altname wlp0s20f3
ubuntu@ubuntu:~$ sudo tcpdump -i wlo1 tcp and port 80

実験の風景(想像し易いように)
default(4).jpeg

C

少し長いですがやっていることは単純です。
datagram(IPヘッダ+TCPヘッダが連続したバッファ)に必要なデータを詰めて宛先のPC(192.168.68.96 80番ポート)へ送信しているだけです。

raw_syn.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ip.h>   // IPヘッダ用
#include <netinet/tcp.h>  // TCPヘッダ用
#include <arpa/inet.h>
#include <netinet/in.h>

// チェックサム計算(IPヘッダとTCPヘッダで使う)
unsigned short csum(unsigned short *ptr,int nbytes) {
    long sum;
    unsigned short oddbyte;
    short answer;

    sum=0;
    while(nbytes>1) {
        sum+=*ptr++;
        nbytes-=2;
    }
    if(nbytes==1) {
        oddbyte=0;
        *((unsigned char*)&oddbyte)=*(unsigned char*)ptr;
        sum+=oddbyte;
    }

    sum = (sum >> 16) + (sum & 0xffff);
    sum = sum + (sum >> 16);
    answer=(short)~sum;

    return(answer);
}

int main() {
    // 送信先IP、送信元IPは適宜書き換える(筆者はhostname -Iで確認した)
    char source_ip[20] = "192.168.68.245";  // 自分のマシンのIPアドレスに変更
    char dest_ip[20] = "192.168.68.96";      // 宛先IPアドレス

    // raw socket作成
    int s = socket (PF_INET,   // IPv4
                   SOCK_RAW,   // RAWにすることでIPヘッダやTCPヘッダを自由に作成・操作可能となる
                   IPPROTO_TCP // TCP指定
                   );
    if(s < 0) {
        perror("Socket error");
        exit(1);
    }

    // IPヘッダ+TCPヘッダを格納するバッファ
    char datagram[4096];

    // バッファを0で埋める
    memset(datagram, 0, 4096);

    // IPヘッダポインタ
    struct iphdr *iph = (struct iphdr *) datagram;

    // TCPヘッダポインタ(IPヘッダの後ろ)
    struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof(struct iphdr));

    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(80);  // HTTPのポート
    sin.sin_addr.s_addr = inet_addr(dest_ip);


    /*
                誤解し易いので注記
       iph,tcphポインタはdatagramを指しており、iph->(要素)へ値を代入すると
       datagramへ直接値を書き込んでいることになる。
    */
    // IPヘッダ作成
    iph->ihl = 5;  // ヘッダ長(5×4=20バイト) (標準の長さ)
    iph->version = 4;  // IPv4
    iph->tos = 0;      // 今は殆ど使われず0で問題ない模様
    iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr)); // パケット全体の長さ(IPヘッダ+データ、バイト単位)
    iph->id = htons(54321);
    iph->frag_off = 0;
    iph->ttl = 64; // 生存時間
    iph->protocol = IPPROTO_TCP;  // TCPは6、UDPは17
    iph->check = 0; // 計算前は0
    iph->saddr = inet_addr(source_ip); // 送信元IPアドレス
    iph->daddr = sin.sin_addr.s_addr; // 宛先IPアドレス

    // IPヘッダチェックサム計算
    iph->check = csum((unsigned short *)datagram, iph->ihl<<2);

    // TCPヘッダ作成
    tcph->source = htons(12345); // 適当な送信元ポート番号
    tcph->dest = htons(80);      // 宛先ポート番号
    tcph->seq = htonl(0);
    tcph->ack_seq = 0; // 0(SYN送信時はまだACKなし)
    tcph->doff = 5; // TCPヘッダ長(20バイト)
    tcph->syn = 1;  // SYNフラグセット 1 → SYNパケットとして送る(接続開始の合図)
    tcph->window = htons(5840);
    tcph->check = 0; // 計算前は0
    tcph->urg_ptr = 0; // 0 → 緊急データなし

    // TCPチェックサム計算用の擬似ヘッダを作る構造体
    struct pseudo_header {
        unsigned int source_address;
        unsigned int dest_address;
        unsigned char placeholder;
        unsigned char protocol;
        unsigned short tcp_length;
    };

    // 擬似ヘッダとTCPヘッダを結合したバッファ
    char pseudo_packet[4096];
    struct pseudo_header psh;

    psh.source_address = inet_addr(source_ip);
    psh.dest_address = sin.sin_addr.s_addr;
    psh.placeholder = 0;
    psh.protocol = IPPROTO_TCP;
    psh.tcp_length = htons(sizeof(struct tcphdr));

    // 擬似ヘッダをコピー
    memcpy(pseudo_packet, &psh, sizeof(struct pseudo_header));
    // TCPヘッダを擬似ヘッダの後にコピー
    memcpy(pseudo_packet + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr));

    tcph->check = csum((unsigned short*) pseudo_packet, sizeof(struct pseudo_header) + sizeof(struct tcphdr));

    // 通常のソケット通信では、IPヘッダはOS(カーネル)が自動で付け加える。
    // Raw Socketで自分でIPヘッダを作る場合は、
    // カーネルに二重でIPヘッダを付けられないように設定をする必要がある。
    // IPPROTO_IP:IP層の設定
    // IP_HDRINCL:1(true)に設定すると、自分でIPヘッダを作成して送ることを意味する
    // oneはただの有効(true)
    // setsockoptのoptvalはvoidポインタなので、値を直接渡せず
    // 値のアドレス(ポインタ)を渡す必要があるというCの関数仕様によるもの
    int one = 1;
    const int *val = &one;
    if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0) {
        perror("setsockopt() error");
        exit(1);
    }

    // パケット送信
    if (sendto (s, datagram, ntohs(iph->tot_len), 0, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
        perror("sendto failed");
    } else {
        printf ("SYN packet sent\n");
    }

    close(s);
    return 0;
}

動作環境

送信側

图片.png

受信側

default(5).jpeg

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?