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

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

socatで装置内通信のデータをのぞき見る

socatを使ってUNIXドメイン通信を中継し通信内容をのぞいてみる。


構成のイメージは下記。

--------------------------------------------------
 ________      ________      ________
|        |    |        |    |        | 
| Client |--->| socat  |--->| Sever  | 
|________|    |________|    |________| 
                  |
                   ---> 標準出力に通信内容を表示。

--------------------------------------------------

プログラム

local.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define MSGID_HELLO_REQUEST (0x0101)

#define MAX_USER (1)

typedef enum {
    MSG_KIND_REQUEST = 0,
    MSG_KIND_MAX
} msgkind_t;

typedef struct {
    uint32_t    id;
    uint16_t    kind;
    uint16_t    seqno;
} msg_header_t;


client.c

#include "local.h"

#define UNIX_SOCKET_FILEPATH "./usocket.client"

int main(void)
{
    int sfd = -1;
    struct sockaddr_un addr = {
        .sun_family = AF_UNIX,
        .sun_path   = UNIX_SOCKET_FILEPATH,
    };

    sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    int rc = connect(sfd, (struct sockaddr* )&addr, sizeof(addr));
    if (rc < 0) {
        perror("connect");
        close(sfd);
        exit(EXIT_FAILURE);
    }

    uint16_t    seqno = 0;
    while(1) {
        msg_header_t    req = {
            .id     = MSGID_HELLO_REQUEST,
            .kind   = MSG_KIND_REQUEST,
            .seqno  = seqno,
        };

        ssize_t ss = send(sfd, (void*)&req, sizeof(req), 0);
        if (ss < 0) {
            perror("send");
            close(sfd);
            break;
        }

        if (ss == 0 || ss < 0) {
            printf("connection closed.\n");
            unlink(UNIX_SOCKET_FILEPATH);
            break;
        }

        printf("sent request, wait 3 sec (-.-)zzZ\n");
        sleep(3);
        seqno++;
    }

    return 0;
}


server.c

#include "local.h"

#define UNIX_SOCKET_FILEPATH "./usocket.server"

int main(void)
{
    int sfd = -1;
    struct sockaddr_un addr = {
        .sun_family = AF_UNIX,
        .sun_path   = UNIX_SOCKET_FILEPATH,
    };

    sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    unlink(UNIX_SOCKET_FILEPATH);

    int rc = bind(sfd, (struct sockaddr* )&addr, sizeof(addr));
    if (rc < 0) {
        perror("bind");
        close(sfd);
        exit(EXIT_FAILURE);
    }

    rc = listen(sfd, MAX_USER);
    if (rc < 0) {
        perror("listen");
        close(sfd);
        exit(EXIT_FAILURE);
    }

    socklen_t   addrlen;
    struct sockaddr_un caddr;

    int acsfd = accept(sfd, (struct sockaddr *)&caddr, &addrlen);
    if (acsfd < 0) {
        perror("accept");
        close(sfd);
        exit(EXIT_FAILURE);
    }

    while(1) {
        uint16_t    seqno = 0;
        msg_header_t    req;

        ssize_t rs = recv(acsfd, (void*)&req, sizeof(req), 0);
        if (rs < 0) {
            perror("recv");
            close(sfd);
            break;
        }

        printf("recv size=%d, id=%#x, kind=%#x, seqno=%d\n",
               rs, req.id, req.kind, req.seqno);

        if (rs == 0) {
            printf("connection closed.\n");
            unlink(UNIX_SOCKET_FILEPATH);
            break;
        }
    }

    return 0;
}

実行結果

3つの端末を開き各端末で下記コマンドを実行。

$ sudo yum -y install socat
$ socat -x -v UNIX-LISTEN:./usocket.client, UNIX-CONNECT:./usocket.server
$ gcc -o server server.c
$ ./server
$ gcc -o client client.c
$ ./client


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



UNIXドメイン通信に対しtcpdumpライクなキャプチャができている。
単体テストとかで使えそう。

Ncursesでスクリーン制御

Ncurses(*1)を使ってスクリーン制御してみる。

(*1)
CUIでスクリーン、キー入力、カーソルなどの制御機能を提供するライブラリのこと。
make menuconfig とか実行すると出てくるアレを実現できる。

ソースコード

sample.c

#include <curses.h>
#include <stdlib.h>

#define ESCAPE 27

typedef enum {
    COLOR_TYPE_INVALID = 0,
    COLOR_TYPE_BACK_GROUND = 1
} color_type_t;

void init_curses()
{
    initscr();
    start_color();
    init_pair(COLOR_TYPE_BACK_GROUND, COLOR_RED, COLOR_BLACK);
    curs_set(0);
    noecho();
    keypad(stdscr,TRUE);
}

int main()
{
    init_curses();

    bkgd(COLOR_PAIR(COLOR_TYPE_BACK_GROUND));
    move(2,1);
    printw("Press ESC key, you will exit.");
    refresh();

    int key;
    while(1) {
        int key = getch();
        if (key == ESCAPE) {
            break;
        }
        refresh();
    };

    endwin();

    return 0;
}

実行結果

$ gcc sample.c -o sample -lcurses
$ ./sample


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




Ncursesを使ってテトリスぐらいなら作れそう。

マウスの座標を表示する

Javascriptでマウスの座標(左上端からのX,Y距離)を表示する。

ソースコード

mouse.html

<HTML>
<HEAD>
    <TITLE> マウス座標 </TITLE>
</HEAD>
<BODY>
    マウス座標
    <br>
    (X,Y) = 
    <input type="text" style="width:50;" id="global_x">,
    <input type="text" style="width:50;" id="global_y"> <br>

    <SCRIPT type="text/javaSCRIPT">
        (function (){
            // スクロール位置取得
            function GetScrollPosition(obj){
                return{
                    x:obj.body.scrollLeft || obj.documentElement.scrollLeft,
                    y:obj.body.scrollTop  || obj.documentElement.scrollTop
                };
            }

            // マウス追従
            function TailingMouseMotion(e){

                var scrlpos = GetScrollPosition(document);
                var position;

                // X座標計算
                position = document.getElementById("global_x");
                position.value = e.clientX + scrlpos.x;
                
                // Y座標計算
                position = document.getElementById("global_y");
                position.value = e.clientY + scrlpos.y;
            }

            // イベント処理
            if(document.addEventListener){
                document.addEventListener("mousemove" , TailingMouseMotion);
            }else if(document.attachEvent){
                document.attachEvent("onmousemove" , TailingMouseMotion);
            }

        })();
        
    </SCRIPT>
</BODY>
</HTML>

実行結果

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





次回は適当なgif画像を追従させるようにしたい。

Windows-Linux間をTCPで通信する

TCPを使ってWindowsとLinux間で通信してみる。
具体的にはWindows上にVirtualboxで仮想マシンを作成しHost-Only-Adapter経由で通信する。

構成

  • サーバ    : Linux CentOS 6, X86_64(64bit) on Virtualbox
  • クライアント : Windows7 (32bit)

プログラム作成

クライアント側

実行バイナリはVisual Studio 2017 で作成。


TCP_Client.cpp

#include "stdafx.h"

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

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

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

    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);
    }

    while (1) {
        memset(buf, 0, sizeof(buf));
        int rs = recv(sfd, buf, sizeof(buf), 0);

        printf("recv %d byte, %s\n", rs, buf);
    }

    WSACleanup();

    return 0;
}


プロジェクトプロパティから入力にws2_32.libを追記してビルドする。

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




Windows側のIPアドレスを 192.168.100.0/24のネットワークに設定しておく。

C:> ipconfig

Windows IP 構成

イーサネット アダプター VirtualBox Host-Only Network:

   接続固有の DNS サフィックス . . . :
   IPv4 アドレス . . . . . . . . . . : 192.168.100.1
   サブネット マスク . . . . . . . . : 255.255.255.0
   デフォルト ゲートウェイ . . . . . :

サーバ側


tcp_server.c

#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>

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

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;
    }

    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;
    }

    while (1) {
        /* メッセージ送信 */
        char msgbuf[MSGBUF_SIZE] = "HELLO WORLD!";
        ssize_t ws = write(ac_sfd, msgbuf, strlen(msgbuf));
        if (ws < 0) {
            perror("write");
            goto close_all_end;
        }

        sleep(1);
    }

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

    return 0;
}


ソースコードのビルド

$ gcc tcp_server.c -o tcp_server
$ 



こちらも通信インタフェースに同様のネットワークのIPアドレスを設定しておく。

$ ip -a addr
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:8c:e9:83 brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.254/24 brd 192.168.100.255 scope global enp0s8
       valid_lft forever preferred_lft forever

実行結果

確かにサーバが送信した文字列をクライアントで受け取れている。

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


キャプチャを見ても確かに3WAYハンドシェイクからデータ送信まで行っている。
(ただし、クライアント→サーバのキャプチャは見えてない。理由不明。)

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

Qt5で始めるGUIプログラミング

Qt5を使ってGUIプログラミングに挑戦してみる。
まずはウィンドウを出すだけ。

Linuxサーバでアプリケーションを作成&実行し、表示はWindowsで行うスタイル。

実行環境

ホスト側

  • Windows7 32bit
  • Xming 6.9.0.31
  • Teraterm 4.9.2

サーバ側(実行側)

  • CentOS 6, x86_64 (64bit)
  • Qt 5.6.2

事前準備

ホスト側

下記アプリケーションをインストールする。

Xmingの起動、TeratermのSSH転送設定

インストール後、Xmingを起動しTeratermは下記設定を行う。

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

その後、一度Teratermを再起動して再度サーバにログインする。

サーバ側

Qt Downloads から実行環境に応じたバイナリをダウンロードする。今回はqt-opensource-linux-x64-5.6.2.runを選択。

$ chmod +x qt-opensource-linux-x64-5.6.2.run
$ ./qt-opensource-linux-x64-5.6.2.run



すると以下の画面が表示されるので必要事項を入力してインストール実行。

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



インストール場所の変更

デフォルトだとhome配下にインストールされるのでパスを変更する。
(インストール時に指定可能だがrootユーザor sudo実行では画面転送が上手くいかない場合がある)

/usr/local/shareへ変更

$ sudo mv Qt5.6.2 /usr/local/share



ld.so.confにライブラリパスを追記

$ sudo vi /etc/ld.so.conf
#/usr/local/share/Qt5.6.2/5.6/gcc_64/lib/ を追記
$
$ sudo ldconfig #追記したパスを反映
$


~/.bashrcにqmakeのパスを追記

$ vi ~/.bashrc
# 下記行を追記
# export QT_ROOT="/usr/local/share/Qt5.6.2/5.6/" 
# export PATH="$QT_ROOT/gcc_64/bin:$PATH"
$
$ source ~/.bashrc #追記したパスを反映
$








プログラム作成

事前準備が終ったのでプログラムを作っていく。


sample.cpp

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QLabel *label = new QLabel("Hello Qt!");

    label->show();

    return app.exec();
}



Makefileを生成する

$ qmake -project
$ qmake qt.pro



ここでmakeを実行するとコンパイルエラーが発生。

$ make
g++ -c -pipe -O2 -std=gnu++0x -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -I. -I. -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtGui -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtCore -I. -I/usr/local/share/Qt5.6.2/5.6/gcc_64/mkspecs/linux-g++ -o sample.o sample.cpp
sample.cpp:1:24: error: QApplication: No such file or directory
sample.cpp:2:18: error: QLabel: No such file or directory
sample.cpp: In function 'int main(int, char**)':
sample.cpp:6: error: 'QApplication' was not declared in this scope
sample.cpp:6: error: expected ';' before 'app'
sample.cpp:7: error: 'QLabel' was not declared in this scope
sample.cpp:7: error: 'label' was not declared in this scope
sample.cpp:7: error: expected type-specifier before 'QLabel'
sample.cpp:7: error: expected ';' before 'QLabel'
sample.cpp:11: error: 'app' was not declared in this scope
sample.cpp: At global scope:
sample.cpp:4: warning: unused parameter 'argc'
sample.cpp:4: warning: unused parameter 'argv'



こちらによるとqt.proに下記行を追加すれば良いらしい。

$ vi qt.pro
TEMPLATE = app
TARGET = qt
INCLUDEPATH += .
QT += widgets    # この行を追記

# Input
SOURCES += sample.cpp


再度ビルド実行

$ make
/usr/local/share/Qt5.6.2/5.6/gcc_64/bin/qmake -o Makefile qt.pro
g++ -c -pipe -O2 -std=gnu++0x -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -I. -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtWidgets -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtGui -I/usr/local/share/Qt5.6.2/5.6/gcc_64/include/QtCore -I. -I/usr/local/share/Qt5.6.2/5.6/gcc_64/mkspecs/linux-g++ -o sample.o sample.cpp
$


コンパイルが成功し実行するが謎のエラーが発生。

$ ./qt
This application failed to start because it could not find or load the Qt platform plugin "xcb"
in "".

Reinstalling the application may fix this problem.
Aborted


こちらによると環境変数 "QT_PLUGIN_PATH"を追加すれば良いらしい。
(stackoverflowさん、いつもありがとう)

$ vi ~/.bashrc
# export QT_PLUGIN_PATH="$QT_ROOT/gcc_64/plugins"  を追記
$

実行結果

$ ./qt


Windows上に下記画面が表示されれば成功。

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

シグナル受信をepollで監視

Linuxのsignalfdを使ってシグナルをepollで監視する。


signalfd.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <errno.h>

#define MAX_EVENTS 10
#define RET_OK (0)
#define RET_NG (-1)

#define PERROR(X) \
{\
    char __strerr[128] = {0};\
    int errcode = errno;\
    \
    strerror_r(errcode, __strerr, sizeof(__strerr));\
    printf(X " failed(%d:%s)\n", errcode, __strerr);\
}

static int createSignalFd(int* sfd);
static int waitSignalEvent(int sfd);

int main(void)
{
    int sfd = -1;
    int ret = RET_OK;

    do {
        ret = createSignalFd(&sfd);
        if (RET_OK != ret) {
            printf("createSignalFd() failed(%d)\n", ret);
            break;
        }

        ret = waitSignalEvent(sfd);
        if (RET_OK != ret) {
            printf("waitSignalEvent");
            close(sfd);
            break;
        }

        close(sfd);
    } while(0);


    return 0;
}

static int createSignalFd(int* sfd)
{
    int ret = RET_OK;
    sigset_t    mask;

    sigemptyset(&mask);

    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);

    do {
        if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
            PERROR("sigprocmask()");
            break;
        }

        *sfd = signalfd(-1, &mask, 0);
        if (*sfd < 0) {
            PERROR("signalfd()");
            ret = RET_NG;
            break;
        }
    } while(0);

    return ret;
}

static int waitSignalEvent(int sfd)
{
    int ret = RET_OK;
    int rc = 0;
    int epollfd = -1;
    int nfds = -1;
    struct epoll_event events[MAX_EVENTS];
    struct epoll_event evt = {
        .events = EPOLLIN,
        .data = {
            .fd = sfd,
        },
    };

    epollfd = epoll_create(MAX_EVENTS);
    if (epollfd < 0) {
        PERROR("epoll_create()");
        goto error_end;
    }

    rc = epoll_ctl(epollfd, EPOLL_CTL_ADD, sfd, &evt);
    if (rc != 0) {
        PERROR("epoll_ctl()");
        close(epollfd);
        goto error_end;
    }

    for(;;) {
        nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            PERROR("epoll_wait()");
            close(epollfd);
            break;
        }

        int fd = 0;
        for (fd = 0; fd < MAX_EVENTS; fd++) {
            if (events[fd].data.fd == sfd) {
                struct signalfd_siginfo fdsi;
                ssize_t sz = read(sfd, &fdsi, sizeof(fdsi));
                if (sz < 0) {
                    PERROR("read()");
                    close(epollfd);
                    break;
                }

                printf("ssi_signo = %d\n", fdsi.ssi_signo);
            }
        }
    }

    close(epollfd);
error_end:

    return ret;
}


実行結果

$ gcc -std=gnu99 signalfd.c -o signalfd
$ ./signalfd
ssi_signo = 1
ssi_signo = 2
ssi_signo = 3
Terminated

#別端末から
$ killall -SIGHUP signalfd
$ killall -SIGINT signalfd
$ killall -SIGQUIT signalfd
$ killall -SIGTERM signalfd

TAILQでリスト管理

TAILQを使ってリストを実現する。
詳細はMan page of QUEUE参照。


tail.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/queue.h>

typedef struct memEntry {
    TAILQ_ENTRY(memEntry) entry;
    int32_t     size;
    void*       ptr;
} memEntry_t;

typedef struct {
    TAILQ_HEAD(tq_head, memEntry) head;
    int entry_count;
} mngMemPool_t;

static mngMemPool_t mngMem;

void* allocateMemory(size_t  size);
void printAllMemory(void);
void freeAllMemory(void);

int main(void)
{
    TAILQ_INIT(&mngMem.head);

    for (int i = 0; i < 10; i++) {
        void* ptr = allocateMemory(i * sizeof(int));
    }

    printAllMemory();

    freeAllMemory();

    return 0;
}

void* allocateMemory(size_t size)
{
    memEntry_t* ent;

    ent = malloc(sizeof(memEntry_t));
    if (NULL == ent) {
        printf("out-of-memory");
        exit(EXIT_FAILURE);
    }

    ent->ptr = malloc(size);
    if (NULL == ent->ptr) {
        free(ent);
        printf("out-of-memory");
        exit(EXIT_FAILURE);
    }

    ent->size = size;

    TAILQ_INSERT_TAIL(&mngMem.head, ent, entry);

    return ent->ptr;
}

void printAllMemory(void)
{
    memEntry_t* np = NULL;
    for (np = mngMem.head.tqh_first; np != NULL; np = np->entry.tqe_next) {
        printf("size=%d\n", np->size);
    }
}

void freeAllMemory(void)
{
    while (mngMem.head.tqh_first != NULL) {
        memEntry_t* np = mngMem.head.tqh_first;
        TAILQ_REMOVE(&mngMem.head, np, entry);
        free(np->ptr);
        free(np);
    }
}

実行結果

$ gcc -std=gnu99 tailq.c  -o tailq
$ ./tailq
size=0
size=4
size=8
size=12
size=16
size=20
size=24
size=28
size=32
size=36