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

見習いエンジニアの備忘log

TCPのURGフラグの謎

普段からネットワークを扱う人にとってはお馴染みのTCPという技術。

ネットワークを学ぼうとするビギナーが最初に教わるであろう基礎的で重要なプロトコルですが、ずっとTCPヘッダにあるURGフラグ(緊急フラグ)とUrgenポインタ(緊急ポインタ)の使いどころが疑問でした。

参考書やネットで調べて出てくる記事にはURGフラグとUrgentポインタの意味は書いてあるのですが、具体的にどんなシーンで使うのかは書いてない事が多いので良く分かっていませんでした。

今回は疑問を解消するべくURGフラグについて少し調べてみました。

URGフラグ(緊急フラグ)とは



URGフラッグとは図で言うところの赤で囲ったところにある1bit幅のフィールドです。

大抵は"0"になっていますが、これに"1"が立っているとUrgentポインタ(緊急ポインタ)のフィールドにデータの在り処が設定されます。...みたいな説明をよく見かけます。


TCPヘッダ フォーマット
f:id:segmentation-fault:20171014113429p:plain

出典:RFC 793 - Transmission Control Protocol


URGフラグを使う一般的なプログラム



実際にURGフラグを使ってるプログラムは無いだろうかと探したところ、Telnetで使われているらしいです。というかTelnet以外見つけられなかった。

説明を見ると端末から実行したコマンドを<Ctrl-C>でキャンセルすれば発生するみたい。

参考
TCP Flags: PSH and URG - PacketLife.net
3 Minutes Networking No.54


実際にキャプチャして確認



コマンドを<Ctrl-C>でキャンセル
f:id:segmentation-fault:20171014120524p:plain


キャプチャ
f:id:segmentation-fault:20171014120715p:plain


確かにフラグが立っている!


プログラムを作って確認



実際にURGフラグが立ったデータを受信するとアプリケーションからはどう見えるのでしょうか。 簡単なプログラムを作って確認してみます。

Windowsをクライアント、LinuxをサーバとしてクライアントからURGフラグ無し、有りのデータを送信します。

URGフラグを付けるには送受信時(recv, send)実行時のフラグに"MSG_OOB"を設定すればよく、受信側にはOSからアプリに対しシグナル SIGURG が発行されるぽい。

クライアント

// TCP_Client.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

#include <stdio.h>
#include <winsock2.h>
#include <windows.h>

#define PORT_NO_SERVER (12345)
#define IP_ADDRESS_SERVER "192.168.100.101"

int main(void)
{
    WSADATA wsaData;
    struct sockaddr_in server;
    SOCKET sfd = -1;
    char buf[32] = { 0 };
    int rc = 0;
    int ws = 0;

    // winsock2の初期化
    WSAStartup(MAKEWORD(2, 0), &wsaData);

    // ソケットの作成
    sfd = socket(AF_INET, SOCK_STREAM, 0);

    // 接続先指定の準備
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT_NO_SERVER);
    server.sin_addr.S_un.S_addr = inet_addr(IP_ADDRESS_SERVER);

    // サーバに接続
    rc = connect(sfd, (struct sockaddr *)&server, sizeof(server));
    if (rc < 0) {
        printf("connect() failed(%d)\n", rc);
        exit(EXIT_FAILURE);
    }

    do  {
        // サーバへデータを送信
        // おはよう
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf) - 1, "GOOD MORNING.\n");
        ws = send(sfd, buf, sizeof(buf), 0);
        printf("send %d byte, %s\n", ws, buf);
        
        // こんにちは
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf) - 1, "GOOD AFTERNOON.\n");
        ws = send(sfd, buf, sizeof(buf), 0);
        printf("send %d byte, %s\n", ws, buf);

        // こんばんは
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf) - 1, "GOOD EVENING.\n");
        ws = send(sfd, buf, sizeof(buf), 0);
        printf("send %d byte, %s\n", ws, buf);

        // HELLO(URGフラグ付きで送信)
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf) - 1, "HELLO.\n");
        ws = send(sfd, buf, sizeof(buf), MSG_OOB);
        printf("send %d byte, %s\n", ws, buf);

        Sleep(3000);

    } while (0);

    // winsock2の終了処理
    WSACleanup();

    return 0;
}


サーバ

シグナルハンドラ内で、やってはイケない事をしてますが、サンプルなので許してください。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>

#define MAX_CLIENTS (1)
#define MSGBUF_SIZE (1024)

int gsfd = -1;

static void sig_urg(int signo)
{
    printf("SIGURG received\n");

    char msgbuf[MSGBUF_SIZE] = {0};
    ssize_t rs = 0;

    rs = recv(gsfd, msgbuf, sizeof(msgbuf)-1, MSG_OOB);

    printf("recv OOB %d bytes \n", rs, msgbuf);
    int i = 0;
    for (i = 0; i < rs; i++) {
        printf("%c", msgbuf[i]);
    }

    printf("\n");
    signal(SIGURG, sig_urg);
}

int main(void)
{
    int sfd = -1;
    struct sockaddr_in client;
    int socklen = sizeof(client);
    int ac_sfd = -1;
    int rc = 0;

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(12345),
        .sin_addr = {
            .s_addr = INADDR_ANY,
        },
    };

    /* ソケットの作成 */
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket");
        goto error_end;
    }

    rc = bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
    if (rc < 0) {
        perror("bind");
        goto close_sfd_end;
    }

    printf("Listening...\n");

    rc = listen(sfd, MAX_CLIENTS);
    if (rc < 0) {
        perror("listen");
        goto close_sfd_end;
    }

    /* 要求受付 */
    ac_sfd = accept(sfd, (struct sockaddr *)&client, &socklen);
    if (ac_sfd < 0) {
        perror("accept");
        goto close_sfd_end;
    }

    printf("Connected!\n");

    gsfd = ac_sfd;
    fcntl(ac_sfd, F_SETOWN, getpid());
    signal(SIGURG, sig_urg);
    sleep(3);

    while (1) {
        /* メッセージ受信 */
        char msgbuf[MSGBUF_SIZE] = {0};
        ssize_t rs = 0;
        rs = recv(ac_sfd, msgbuf, sizeof(msgbuf)-1, 0);
        if (rs < 0) {
            perror("write");
            break;
        } else if (rs == 0) {
            printf("Connection Lost\n");
            break;
        } else {
            printf("recv %d bytes \n", rs);
            int i = 0;
            for(i = 0; i < rs; i++)
                printf("%c", msgbuf[i]);
        }
    }

    close(ac_sfd);
 close_sfd_end:
    close(sfd);
 error_end:

    return 0;
}


実行結果

実際にプログラムを実行して、送受信データをキャプチャしてみます。

f:id:segmentation-fault:20171014121546p:plain

f:id:segmentation-fault:20171014121600p:plain


確かにクライアントからはデータがURGフラグ付きで送信されていて、サーバ側ではSIGURGを受信しました。

ただ、SIGURG受信時にMSG_OOBフラグ付きでrecvしたらデータを先読み出来ると思ったのですが、違うみたいです。(実装が間違ってるだけかもですが)


まとめ



またまだ謎は多いのですが、実際にURGフラグを使っている例があると分かっただけで良い収穫でした。

telnetのソースコードを読んでもう少し理解を深めたいと思います。