Segmentation Faultぐ

Segmentation Fault

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

sshでログインしている端末の出力をのぞき見る

やりたいこと

  • 自分は端末Aでログインしている状態。
    端末Aから別の端末である端末Bのコマンドの入力、応答をのぞき見たい。
  • できればC言語で実装したい


実現方法

ググってみたところ、perl製のttylogを使ったり、shellスクリプトで頑張ればできるようです。
どちらも共通するのはstraceの出力を加工していること。

sshdに対してstraceを実行し、read(fd=13)の第2引数を取っています。

参考
orebibou.com

strace自体はptraceを使って実装されているので、マネすればc言語でもいけそうです。


ptraceについて

manページ見てもよくわからなかったのですが、下記の流れで実装すれば良いみたいです。

attach
↓
option設定
↓
システムコールをcatch
↓
レジスタから引数を解析

- システムコールの番号、引数はレジスタ(struct user_regs_struct)で判別する。<br>
- readの第2引数(buf)の中身を取得するにはPTRACE_PEEKDATAを使う。


X86のレジスタ対応表(とりあえずreadの捕捉に必要なものだけ)

種目 レジスタ(メンバ名)
システムコールの番号 orig_rax
リターン値 rax
第1引数 rdi
第2引数 rsi
第3引数 rdx


ソースコード

ttytrace.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/syscall.h>

#define FD_TERMINAL (13)

static int read_args(pid_t pid, long addr, size_t size)
{
    int rc = 0;
    long dat = 0;
    size_t datsz = 0;

    for (int i = 0; i < size; i+= sizeof(long)) {
        errno = 0;
        dat = ptrace(PTRACE_PEEKDATA, pid, addr + i, NULL);
        int err = errno;
        if (err != 0) {
            perror("ptrace");
            rc = -err;
            break;
        }
 
        if (i + sizeof(long) > size) {
            write(1, (void*)&dat, size % sizeof(long));
        } else {
            write(1, (void*)&dat, sizeof(long));
        }
    }

    return rc;
}


static int
get_syscall_args_read(
    pid_t pid,
    struct user_regs_struct *regs)
{
    int rc = 0;

#if defined(__x86_64__)
    /* skip other than "read" */
    if (!((regs->orig_rax == SYS_read) &&
         (regs->rdi == FD_TERMINAL) &&
         ((ssize_t)regs->rax > 0))) {
        goto end;
    }

    rc = read_args(pid, (long)regs->rsi, (size_t)regs->rax);
    if (rc < 0) {
        goto end;
    }
#endif   

 end:

    return rc;
}

static void start_trace(pid_t pid)
{
    int rc = 0;
    int status = 0;
    struct user_regs_struct regs;

    rc = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
    if (rc < 0) {
        perror("ptrace");
        goto end;
    }

    ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESYSGOOD);

    while (1) {
        ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

        rc = waitpid(pid, &status, 0);
        if (rc < 0) {
            perror("waitpid");
            goto detach_end;
        }

        if (WIFEXITED(status)) {
            break;
        }

        rc = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
        if (rc < 0) {
            perror("ptrace");
            goto detach_end;
        }

        rc = get_syscall_args_read(pid, &regs);
        if (rc < 0) {
            goto detach_end;
        }
    }

  detach_end:
    ptrace(PTRACE_DETACH, pid, NULL, NULL);

  end:
    return;
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("%s <pid>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    pid_t pid = atoi(argv[1]);

    start_trace(pid);

    return 0;
}


ビルド

gcc -o ttytrace ttytrace.c -std=gnu99


実行

監視したい端末のsshdのpidを指定する。
実行にはroot権限が必要です。

sudo ./ttytrace <sshdのpid>


実行に成功していれば、ttylogとかと同様に監視対象の端末での操作内容がそのまま表示されます。
意外と短い実装で済んでよかった。


Javascriptでテトリスを作る

Javascriptのサンプルとしてテトリスは定番ですが、そういえば今まで挑戦してなかったの作ってみました。

canvasを使って落下ブロック(テトリミノ)が7種、色が6種のザ定番ぽいものを作ります。

完成品


Chromeでしか動作確認してません。

a : 左に移動
s : 右に移動
d : 下に移動
f : 右に回転


ソースコード


<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
<!-- canvas -->
<canvas id="tetris" width="300" height="600"></canvas>
<script type="text/javascript">
(function() {
    /* 全体のコンフィグ */
    var CONFIG = {
        'width'      : 0,                /* 横幅 */
        'hegith'     : 0,                /* 縦幅 */
        'xsqs'       : 10,               /* 横方眼数 */
        'ysqs'       : 20,               /* 縦方眼数 */
        'color'      : "rgb(65,65,65)",  /* 盤面の色 */
        'grid_color' : "black",          /* 格子の色 */
        'grid_width' : 2,                /* 格子の幅 */
    };

    /* 方眼 */
    var SQUARES;
    var squre = function() {
        this.color = 0;
    };

    /* 色リスト */
    var COLOR = {
        "default" : CONFIG.color,
        "red"     : "rgb(255,0,0)",
        "green"   : "rgb(0,255,0)",
        "blue"    : "rgb(0,0,255)",
        "yellow"  : "rgb(255,255,0)",
        "cyan"    : "rgb(0,255,255)",
        "magenta" : "rgb(255,0,255)",
    };

    var COLOR_LIST = [
        COLOR.red,
        COLOR.green,
        COLOR.blue,
        COLOR.yellow,
        COLOR.cyan,
        COLOR.magenta,
    ];

    /* ブロックの定義 */
    var BLOCK;
    var block = function () {
        this.color = COLOR_LIST[Math.floor(Math.random() * (COLOR_LIST.length))];
        this.shape = BLOCK_LIST[Math.floor(Math.random() * (BLOCK_LIST.length))];
        this.x = (CONFIG.xsqs / 2) - 1;
        this.y = 0;
        return this;
    };

    var BLOCK_BOX = [
        [0,0,0,0],
        [0,1,1,0],
        [0,1,1,0],
        [0,0,0,0]
    ];
    
    var BLOCK_BAR = [
        [1,1,1,1],
        [0,0,0,0],
        [0,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_LL = [
        [1,1,1,0],
        [1,0,0,0],
        [0,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_LN = [
        [1,0,0,0],
        [1,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_NL = [
        [1,0,0,0],
        [1,1,0,0],
        [0,1,0,0],
        [0,0,0,0]
    ];

    var BLOCK_NN = [
        [0,1,0,0],
        [1,1,0,0],
        [1,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_T = [
        [1,0,0,0],
        [1,1,0,0],
        [1,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_LIST = [
        BLOCK_BOX,
        BLOCK_BAR,
        BLOCK_LL,
        BLOCK_LN,
        BLOCK_NL,
        BLOCK_NN,
        BLOCK_T,
    ];
    
    /* 描画コンテキスト */
    var CTX;

    /* タイマー */
    var fallTimer;

    /* キー入力 */
    var KEY_DOWN = {
        "a"     : 65, /* left */
        "s"     : 83, /* right */
        "d"     : 68, /* down */
        "f"     : 70, /* turn right */
        "left"  : 37,
        "right" : 39,
        "down"  : 40,
        "space" : 32, /* turn right */
    };
    

    /* 初期化処理 */
    function initialize() {

        /* canvasの取得 */
        CTX = document.getElementById("tetris").getContext('2d');
        if (!CTX) {
            return false;
        }

        CONFIG.width = CTX.canvas.width;
        CONFIG.height = CTX.canvas.height;

        /* keydown event */
        document.onkeydown = moveBlock;

        /* 盤面の生成 */
        SQUARES = createSquares(CONFIG);

        /* 落下ブロックの生成 */
        BLOCK = block();

        /* テトリスの起動 */
        drawTetris(CTX, CONFIG, SQUARES, BLOCK);
        fallTimer = setInterval(tetris, 400, CTX, CONFIG, SQUARES, BLOCK);
    }

    /* ブロックの移動 */
   function moveBlock() {
 
        var key = event.keyCode;

        switch(key) {
        case KEY_DOWN.a :
        case KEY_DOWN.left :
            moveLeftBlock();
            break;
        case KEY_DOWN.s :
        case KEY_DOWN.right :
            moveRightBlock();
            break;
        case KEY_DOWN.f :
        case KEY_DOWN.space :
            turnRightBlock();
            break;
        case KEY_DOWN.d :
        case KEY_DOWN.down :
            moveDownBlock();
            break;
        default:
            break;
        }
   }

    /* 左に移動 */
    function moveLeftBlock() {

        var isEnabeMove = true;

        for (var y = 0 ; y < BLOCK.shape[0].length; y++) {
            for (var x = 0; x < BLOCK.shape.length; x++) {

                if (BLOCK.shape[x][y] == 0) {
                    continue;
                }

                /* これ以上左に行けない */
                if (((BLOCK.x + x) <= 0) ||
                    ((SQUARES[BLOCK.x + x - 1][BLOCK.y + y].color != CONFIG.color))) {
                    isEnabeMove = false;
                    break;
                }
            }
        }

        if (isEnabeMove) {
           BLOCK.x--;
        }
    }

    /* 右に移動 */
    function moveRightBlock() {

        isEnabeMove = true;

        for (var y = 0 ; y < BLOCK.shape[0].length; y++) {
            for (var x = 0; x < BLOCK.shape.length; x++) {
                if (BLOCK.shape[x][y] != 0) {
                    /* これ以上右に行けない */
                    if (((BLOCK.x + x) >= (CONFIG.xsqs - 1)) || 
                        ((SQUARES[BLOCK.x + x + 1][BLOCK.y + y].color != CONFIG.color))) {
                        isEnabeMove = false;
                        break;
                    }
                }
            }
        }

       if (isEnabeMove) {
           BLOCK.x++;
       }
    };

    /* 下に加速 */
    function moveDownBlock() {
        if (!isLanded(CONFIG, SQUARES, BLOCK)) {
            BLOCK.y++;
        }
    };

    /* ターンできるか */
    function isEnableToTurn(tmp) {
        for (var x = 0; x < BLOCK.shape.length; x++) {
            for (var y = 0; y < BLOCK.shape[0].length; y++) {
                if (tmp[x][y] != 0) {
                    if (((BLOCK.x + x) < 0) || (CONFIG.xsqs <= (BLOCK.x + x)) ||
                        ((BLOCK.y + y) < 0) || (CONFIG.ysqs <= (BLOCK.y + y)) ||
                        SQUARES[BLOCK.x + x][BLOCK.y + y].color != CONFIG.color) {
                    
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /* ブロックの右回転 */
    function turnRightBlock() {
        
        var tmp = Array(BLOCK.shape.length);
        for (var x = 0; x < BLOCK.shape.length; x++) {
            tmp[x] = Array(BLOCK.shape[0].length);
        }        

        for (var x = 0; x < BLOCK.shape.length; x++) {
            for (var y = 0; y < BLOCK.shape[0].length; y++) {
                tmp[x][y] = BLOCK.shape[y][BLOCK.shape[0].length - 1 - x];
            }
        }
     
        if (isEnableToTurn(tmp)) {
            BLOCK.shape = tmp;
        }
    }

    /* 方眼の作成 */
    function createSquares(c) {
        var sqrs = new Array(c.ysqs);

        for (var y = 0; y < c.ysqs; y++) {
            sqrs[y] = new Array(c.xsqs);
        }
        
        for (var x = 0; x < c.xsqs; x++) {
            for (var y = 0;  y < c.ysqs; y++) {
                sqrs[x][y] = new squre();
                sqrs[x][y].color = c.color;
            }
        }

        return sqrs;
    }

    /* 盤面の描画 */
    function drawTetris(ctx, c, sqrs, blk) {
        drawBack(ctx, c);   
        drawSquare(ctx, c, sqrs);
        drawBlock(ctx, c, blk);
        drawGrid(ctx, c);
    }

    /* 背景の描画 */
    function drawBack(ctx, c) {
        ctx.fillStyle = c.color;
        ctx.fillRect(0, 0, c.width, c.height);
        
        ctx.strokeStyle = c.grid_color;
        ctx.lineWidth = c.grid_width;
        
        ctx.beginPath();
        ctx.stroke();
    }

    /* 格子(グリッド)の描画 */
    function drawGrid(ctx, c) {

        ctx.beginPath();

        /* 縦のグリッド線 */
        for(var x = c.width/c.xsqs; x < c.width; x+= c.width/c.xsqs) {
            ctx.moveTo(x, 0);
            ctx.lineTo(x, c.height);
        } 
        
        /* 横のグリッド線 */
        for(var y = c.height/c.ysqs; y < c.height; y+= c.height/c.ysqs) {
            ctx.moveTo(0, y);
            ctx.lineTo(c.width, y);
        }
        
        ctx.stroke();
    }

    /* 方眼の描画 */
    function drawSquare(ctx, c, blocks) {
        var x_sp; /* 描画開始座標(x) */
        var y_sp; /* 描画開始座標(y) */
        var w; /* 横幅 */
        var h; /* 縦幅 */

        ctx.beginPath();
        for (var x = 0; x < c.xsqs; x++) {
            for (var y = 0; y < c.ysqs; y++) {
                if (blocks[x][y].color == COLOR.default) {
                    continue;
                }
            
                w = (c.width/c.xsqs);
                h = (c.height/c.ysqs);
                x_sp = x * w;
                y_sp = y * h;
                
                ctx.fillStyle = blocks[x][y].color;
                ctx.fillRect(x_sp, y_sp, w, h);
            }
        }
        ctx.stroke();
    };

    /* 落下ブロックの描画 */
    function drawBlock(ctx, c, blk) {
        var width = c.width/c.xsqs;
        var height = c.height/c.ysqs;

        ctx.fillStyle = blk.color;
        for (var x = 0; x < blk.shape.length; x++) {
            for (var y = 0; y < blk.shape[0].length; y++) {
                if (blk.shape[x][y] != 0) {
                    if (((blk.x + x) >= 0) && ((blk.x + x) <= c.xsqs) &&
                        ((blk.y + y) >= 0) && ((blk.y + y) <= c.ysqs)) {
                       
                        ctx.fillRect((blk.x + x) * width, (blk.y + y) * height, 
                                     width, height);
                    }
                }
            }
        }
    };

    /* ブロックの着地 */
    function addBlock2Board(c, squres, blk) {

        /* 盤面に着色 */
        for (var x = 0; x < blk.shape.length; x++) {
            for (var y = 0; y < blk.shape[0].length; y++) {

                if (blk.shape[x][y] == 0) {
                    continue;
                }
            
                if (blk.x + x <= c.xsqs && blk.y + y <= c.ysqs) {
                    squres[blk.x + x][blk.y + y].color = blk.color;
                }
            }
        }
    }

    /* ブロックの落下 */
    function isLanded(c, squres, blk) {

        /* 着地判定 */
        for (var x = 0; x < blk.shape.length; x++) {
            for (var y = 0; y < blk.shape[0].length; y++) {

                if (blk.shape[x][y] != 0) {
                    /* 地面と接触 */
                    if ((blk.y + y) == (c.ysqs - 1)) {
                        return true;
                    }

                    /* 既設ブロックと接触 */
                    if (squres[blk.x + x][blk.y + y + 1].color != c.color) {
                        return true;
                    }
                }
            } 
        }

        /* Not着地 */
        return false;
    }

    /* GameOverですか? */
    function isGameOver(c, squres) {
        for (var x = c.xsqs/2 - 2 ; x < c.xsqs/2 + 2; x++) {
            if (squres[x][0].color != c.color) {
                return true;
            }
        }
            
        return false;
    }

    /* 行削除 */
    function eraseLine(c, squres) {

        for (var y = 0; y < c.ysqs; y++) {
            /* is enable to erase ? */
            var flag = true;
            for (var x = 0; x < c.xsqs; x++) {
                if (squres[x][y].color == c.color) {
                    flag = false;
                    break;
                }
            }

            if(!flag) {
                continue;
            }

            /* do to erase */
            for (var x = 0; x < c.xsqs; x++) {
                squres[x][y].color = c.color;
            }
            
            if (y != 0) {
                for (var y1 = y; y1 != 0; y1--) {
                    for (var x = 0; x < c.xsqs; x++) {
                        squres[x][y1].color = squres[x][y1-1].color;
                    }
                }
            }
        }
    }

    /* main処理 */
    function tetris(ctx, c, squres, blk) {

        if (isLanded(c, squres, blk)) {
            /* ブロックの着地 */
            addBlock2Board(c, squres, blk);

            /* 削除処理 */
            eraseLine(c, squres);

            /* Game Over ? */
            if (isGameOver(c,squres)) {
                drawTetris(ctx, c, squres, blk);
                clearInterval(fallTimer);
            } else {
                /* 新しいブロックの生成 */
                blk = block();
            }
        } else {
            /* 落下 */
            blk.y++;
        }

        /* 盤面の描画 */
        drawTetris(ctx, c, squres, blk);
    }

    window.addEventListener('load', initialize, false);

} )();
</script>
<div>a : 左に移動</div>
<div>s : 右に移動</div>
<div>d : 下に移動</div>
<div>f : 右に回転</div>
</body>
</html>


もう少しシンプルに作れたかも。



Python3サンプルコード集(その2)

Pythonのサンプルコードです。

ネットワーク系が中心となります。

書かなくなるとすぐ忘れてしまうので備忘録として残しておきます。

全てクラアント-サーバの通信のサンプルです。

TCP


tcp_client.py

import os
import sys
import socket

if __name__ == '__main__':

    address = ('127.0.0.1', 50000)
    datasize = 4096

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(address)
    
    msg = 'Hello TCP'
    s.send(msg.encode())
    s.close()

tcp_server.py

import os
import sys
import socket

if __name__ == '__main__':

    address = ('127.0.0.1', 50000)
    datasize = 4096

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(address)
    s.listen(1)

    client, addr = s.accept()
    data = client.recv(datasize)
    
    sys.stdout.write("receive : {}\n".format(data.decode()))
    s.close()

UDP


udp_client.py

import os
import sys
import socket

if __name__ == '__main__':
    
    server_address = ('127.0.0.1', 50001)
    datasize = 4096

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    msg = 'Hello UDP'
    s.sendto(msg.encode(), server_address)
    s.close()

udp_server.py

import os
import sys
import socket

if __name__ == '__main__':
    
    bind_address = ('127.0.0.1', 50001)
    datasize = 4096
    
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(bind_address)
    data, client = s.recvfrom(datasize)
    sys.stdout.write("receive : {}\n".format(data.decode()))

    s.close()


UNIXドメイン(DATAGRAM型)


unix_dgram_client.py

import os
import sys
import socket

if __name__ == '__main__':
    
    socket_path = './unix_dgram'

    s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

    s.connect(socket_path)

    msg = 'Hello unix domain (dgram)'
    s.send(msg.encode())

    s.close()

unix_dgram_server.py

import os
import sys
import socket

if __name__ == '__main__':
    
    socket_path = './unix_dgram'
    datasize = 4096

    s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

    s.bind(socket_path)

    data, client = s.recvfrom(datasize)

    sys.stdout.write("received : {}\n".format(data.decode()))

    os.unlink(socket_path)
    s.close()


UNIXドメイン(STREAM型)


unix_stream_client.py

import os
import sys
import socket

if __name__ == '__main__':
    
    socket_path = './unix_stream'

    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

    s.connect(socket_path)

    msg = 'Hello unix domain (stream)'
    s.send(msg.encode())

    s.close()

unix_stream_server.py

import os
import sys
import socket

if __name__ == '__main__':
    
    socket_path = './unix_stream'
    datasize = 4096

    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

    s.bind(socket_path)
    s.listen(1)

    client, addr = s.accept()

    data = client.recv(datasize)

    sys.stdout.write("received : {}\n".format(data.decode()))

    os.unlink(socket_path)
    s.close()


TCP(selectで待つ)


tcp_select.py

import os
import sys
import socket
import select

if __name__ == '__main__':
    
    address = ('127.0.0.1', 50000)
    datasize = 4096
    maxconn = 10
    sockets = []
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    s.bind(address)
    s.listen(maxconn)
    sockets.append(s)    

    try:
        readfds = list(sockets)
        while True:
            
            sys.stdout.write("waiting...\n")
            r,w,x = select.select(readfds, [], [])
            
            for s in r:
                if s in sockets:    
                    conn, addr = s.accept()
                    readfds.append(conn)
                    sys.stdout.write("connected.\n")
                else:
                    data = s.recv(datasize)
                    if len(data) == 0:
                        sys.stdout.write("disconnected.\n")
                        readfds.remove(s)
                        s.close()
                        break
                    sys.stdout.write("received {}\n".format(data.decode()))
    finally:
        s.close()


TCP-UDP-UNIXを全てselectで待つ


all_select.py

import os
import sys
import socket
import select
import signal

if __name__ == '__main__':
    
    address1 = ('127.0.0.1', 50000)
    address2 = ('127.0.0.1', 50001)
    socket_path1 = "./unix_stream"
    socket_path2 = "./unix_dgram"
    datasize = 4096
    maxconn = 1
    sockets = []
    conn_unix = None
    conn_tcp = None

    tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp.bind(address1)
    tcp.listen(maxconn)

    udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp.bind(address2)

    unix_s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    unix_s.bind(socket_path1)
    unix_s.listen(maxconn)

    unix_d = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    unix_d.bind(socket_path2)

    sockets.append(tcp)
    sockets.append(udp)
    sockets.append(unix_s)
    sockets.append(unix_d)

    try:
        readfds = list(sockets)
        while True:
            
            sys.stdout.write("waiting...\n")
            r,w,x = select.select(readfds, [], [])
            
            for s in r:
                if s in sockets:
                    if s == tcp:
                        conn_tcp, addr_tcp = s.accept()
                        readfds.append(conn_tcp)
                        sys.stdout.write("tcp connected.\n")
                    if s == udp:
                        data, client = s.recvfrom(datasize)
                        sys.stdout.write("received {}\n".format(data.decode()))
                    if s == unix_s:
                        conn_unix, addr_unix = s.accept()
                        readfds.append(conn_unix)
                        sys.stdout.write("unix connected.\n")
                    if s == unix_d:
                        data, client = s.recvfrom(datasize)
                        sys.stdout.write("received {}\n".format(data.decode()))
                else:
                    if s == conn_tcp:
                        data = s.recv(datasize)
                        if len(data) == 0:
                            sys.stdout.write("tcp disconnected.\n")
                            readfds.remove(s)
                            s.close()
                            break
                        sys.stdout.write("received {}\n".format(data.decode()))
                    if s == conn_unix:
                        data = s.recv(datasize)
                        if len(data) == 0:
                            sys.stdout.write("unix disconnected.\n")
                            readfds.remove(s)
                            s.close()
                            break
                        sys.stdout.write("received {}\n".format(data.decode()))
    except KeyboardInterrupt:
        tcp.close()
        udp.close()
        os.unlink(socket_path1)
        os.unlink(socket_path2)
        unix_s.close()
        unix_d.close()


エラー処理は上手く書けないのでごめんなさい。

www.segmentation-fault.xyz



Nginx+uWSGI+viewvcでSubversionのリポジトリをブラウザから閲覧する

ViewVC: Repository Browsingを使ってLinux(CentOS7)上に構築したリポジトリをブラウザから参照可能にしてみます。

これで、Linuxが使えないポンコツな上司から修正内容を問われても、URLをコピペしてメールするだけで済むようになります(?)

Git環境向けはまた今度。

事前準備



必要なパッケージを先にインストールしておきます。

$ sudo yum -y install python python-devel svn subversion-python policycoreutils-devel
$ sudo yum -y groupinstall "Development Tools"

参照するリポジトリ



今回のテスト用に/opt/svn/reposを作ります。

$ sudo mkdir -p /opt/svn/
$ cd /opt/svn/
$ sudo svnadmin create repos
$ sudo chown -R user:user repos/


コミットログも見れるか確認するため作成したリポジトリに適当なファイルを追加します。

$ cd ~/
$ svn co file:///opt/svn/repos
$ cd repos
$ mkdir -p branches  tag  trunk
$ echo "Hello World" > trunk/README
$ svn add *
$ svn commit -m "Create New Repository." 


Nginxのインストール



Nginxをyumでインストールするため、リポジトリにnginxのパッケージ取得先を追記します。

$ sudo vi /etc/yum.repos.d/nginx.repo
$ cat /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=0


以下のコマンドでインストールできればOK

$ sudo yum -y --enablerepo=nginx install nginx


続いて、nginxのコンフィグです。

/viewvcでアクセスした場合にuWSGIに処理させるようにします。
/viewvc-staticはviewvcのロゴ等の画像ファイルを参照するための記述です。

$ sudo vi /etc/nginx/conf.d/default.conf
$ diff -u /etc/nginx/conf.d/default.conf.orig /etc/nginx/conf.d/default.conf
--- /etc/nginx/conf.d/default.conf.orig 2018-09-23 19:20:22.455255538 +0900
+++ /etc/nginx/conf.d/default.conf      2018-09-23 20:58:28.830092221 +0900
@@ -41,5 +41,14 @@
     #location ~ /\.ht {
     #    deny  all;
     #}
+
+    location /viewvc {
+        include uwsgi_params;
+        uwsgi_pass 127.0.0.1:3031;
+    }
+
+    location /viewvc-static {
+        alias /usr/local/viewvc/templates/docroot;
+    }
 }


このままではファイアウォールとSELinuxに接続が弾かれるので予めポートの開放とSELinuxを無効化しておきます。

ファイアウォールの設定

$ sudo firewall-cmd --permanent --add-port=3031/tcp --zone=public 
$ sudo firewall-cmd --permanent --zone public --add-service http
$ sudo firewall-cmd --reload

SELinuxの無効化

sudo semanage permissive -a httpd_t


設定が終ったらnginxを起動します。

$ sudo systemctl enable nginx
$ sudo systemctl start nginx


uWSGIのインストール



公式の方法はこちら

今回、uWSGIはpipでインストールするので、まずはpipを使えるようにします。

$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
$ sudo python get-pip.py


pipが準備できたらuwsgiを追加します。

$ sudo pip install uwsgi


追加できたら、systemctlで起動できるように下記ファイルを作ります。

/etc/uwsgi/uwsgi.ini

$ sudo mkdir -p /etc/uwsgi
$ sudo vi /etc/uwsgi/uwsgi.ini
$ cat /etc/uwsgi/uwsgi.ini
[uwsgi]
socket = 127.0.0.1:3031
wsgi-file = /usr/local/viewvc/bin/wsgi/viewvc.wsgi
uid = nginx
gid = nginx
log = /var/log/uwsgi
master = true


$ sudo vim /etc/systemd/system/uwsgi.service
$ cat /etc/systemd/system/uwsgi.service
[Unit]
Description=uWSGI
After=syslog.target

[Service]
ExecStart=/usr/bin/uwsgi --ini /etc/uwsgi/uwsgi.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target


設定が終ったら起動します。

$ sudo systemctl enable uwsgi
$ sudo systemctl start uwsgi


Viewvcのインストール



こちらから最新版を取ってきます。

$ curl http://www.viewvc.org/downloads/viewvc-1.1.26.tar.gz -o viewvc-1.1.26.tar.gz
$ tar xvf viewvc-1.1.26.tar.gz
$ cd xvf viewvc-1.1.26
$ sudo ./viewvc-install
  <インストール先について質問されるが全て<Enter>を押下>


ViewVCのパスとコンフィグをNginx、uWSGIに記載した内容に合わせます。
所有者もnginxに変更します。

$ cd /usr/local
$ sudo chown -R nginx:nginx viewvc-1.1.26/
$ sudo ln -s viewvc-1.1.26 viewvc
$ sudo chown nginx:nginx viewvc
$ cd /usr/local/viewvc
$ sudo vi viewvc.conf
$ diff -u viewvc.conf.orig viewvc.conf
--- viewvc.conf.orig    2018-09-23 19:28:57.955853140 +0900
+++ viewvc.conf 2018-09-23 20:18:35.987344907 +0900
@@ -109,7 +109,7 @@
 ## svn_roots = svnrepos: /opt/svn/,
 ##             anotherrepos: /usr/local/svn/repos2
 ##
-#svn_roots =
+svn_roots = viewvc:/opt/svn/repos

 ## root_parents: Specifies a list of directories under which any
 ## number of repositories may reside.  You can specify multiple root
@@ -658,7 +658,7 @@
 ## still be based on the global default template set per 'template_dir'
 ## above, not on 'template_dir' as overridden for a given root.
 ##
-#docroot =
+docroot = /viewvc-static

 ## show_subdir_lastmod: Show last changelog message for CVS subdirectories
 ##


結果



ブラウザにURLを入力してリポジトリの内容が参照できたら成功です。

今回の例では、"http://(サーバーのIPアドレス)/viewvc "にアクセスします。

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


参考


ViewVC: Repository Browsing

https://pip.pypa.io/en/stable/

https://uwsgi-docs.readthedocs.io/en/latest/index.html

https://nginx.org/

https://subversion.apache.org/




ZAIFのAPIで仮想通貨の積立スクリプトを作る(python)

pythonのお勉強の一環として、ちょこちょこ仮想通貨の自動取引の実装にトライしております。

今回はzaifでBTC/BCH/ETH/MONA/XEM(NEM)を500円分買い注文するプログラムを組んでみます。
作ったスクリプトとcronやsystemdのタイマーと組み合わせることで定期積立を実現できます。



はじめに


zaifを選択する理由は、国内の取引所の中ではAPIで操作できる通貨種類が多いからです。

現在日本円で手に入る主要な仮想通貨としては、

  • Bitcoin(BTC)
  • Bitcoin Cash(BCH)
  • Ethereum(ETH)
  • Ethereum classic(ETC)
  • XEM(NEM)
  • Monacoin(MONA)
  • Ripple(XRP)
  • Lisk(LSK)

このあたりでしょうか。

Zaifは大体カバーできてますね。

かつ販売ではなく取引ができるのでAPIのpairに指定できるというわけです。
(BitFlyerとCoinCheckはBitcoinしかpairに設定できないはず)

また、zaifには積立のサービスがありますが少額だと手数料の割合がそこそこ高いので自分で実装するとお得です。ただしセキュリティ面については自己責任で。


zaif.jp



実装方針


売りの実装は難しい(利益や損切りを考えると面倒)ので、まずは買いの実装だけやってみます。
バグって誤発注するのが怖いので予算は少額に設定しておきます。

  • 定期的に一定額(500円で買える分だけ)を購入する(積立)
  • 指値はpythonスクリプトを実行した時点の最終取引額
  • 最小注文単位の金額が500円を超える場合は注文しない
  • 約定判定はなし

実装にあたっては下記のAPIを使用します。

currency_pairs — Zaif api document v1.1.1 ドキュメント
last_price — Zaif api document v1.1.1 ドキュメント
trade — Zaif api document v1.1.1 ドキュメント


APIを利用するには事前にアカウント設定から取引用のキーを取得しておく必要があります。セキュリティ確保のため送信元IPアドレスの制限もしておくと良いでしょう。


ソースコード



zaif_fund.py

# -*- coding: utf-8 -*-
import sys
import json
import requests
import hmac
import hashlib
import time
from datetime import datetime
import urllib.parse
import math

class zaifApi:
    def __init__(self, key, key_secret, endpoint):
        self.key = key
        self.key_secret = key_secret
        self.endpoint = endpoint

    def get(self, path):
        nonce = int(datetime.now().strftime('%s'))
        text = nonce + self.endpoint + path

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.get(
                self.endpoint + path ,
                headers = self.__get_header(self.key, nonce, signature))

    def post(self, method, params):
        nonce = int(datetime.now().strftime('%s'))
        payload = {
           "method": method,
           "nonce": nonce,
        }
        payload.update(params)
        encoded_payload = urllib.parse.urlencode(payload)

        return requests.post(
                self.endpoint,
                data = payload,
                headers = self.__get_header(encoded_payload))

    def delete(self,path):
        nonce = str(int(time.time()))
        text = nonce + self.endpoint + path

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.delete(
                self.endpoint+path,
                headers = self.__get_header(self.key, nonce))

    def __get_header(self, params):
        signature = hmac.new(
                bytearray(self.key_secret.encode('utf-8')),
                digestmod=hashlib.sha512)
        signature.update(params.encode('utf-8'))

        return {
            'key': self.key,
            'sign': signature.hexdigest()
        }


def getLastPrice(pair):

    urlbase = 'https://api.zaif.jp/api/1/last_price/'

    response = requests.get(urlbase+pair)
    if response.status_code != 200:
        return None

    last_price = response.json()

    return last_price['last_price']

def getItemUnitMin(pair):

    urlbase = 'https://api.zaif.jp/api/1/currency_pairs/'

    response = requests.get(urlbase+pair)
    if response.status_code != 200:
        return None

    currency_pair = response.json()

    return currency_pair[0]['item_unit_min']

if __name__ == '__main__':

    argv = sys.argv
    argc = len(argv)

    if (argc != 3):
        print('usage: python %s <key> <secret key>' % argv[0])
        quit()

    key = argv[1]
    key_secret = argv[2]

    endpoint = 'https://api.zaif.jp/tapi'

    # 予算
    budget_jpy = 500

    # 取引対象の通貨
    pairs = [
        'btc_jpy',
        'bch_jpy',
        'eth_jpy',
        'xem_jpy',
        'mona_jpy',
    ]

    # Instance
    zaif = zaifApi(key,key_secret,endpoint)

    for p in pairs:
        # 認証に使うnonceのための遅延処理
        time.sleep(1.1)

        # 取引通貨の通貨情報を取得
        item_unit_min = getItemUnitMin(p)
        if (item_unit_min == None):
            # 失敗した場合は中断
            continue

        # 最終取引価格を取得
        last_price = getLastPrice(p)
        if (last_price == None):
            # 失敗した場合は中断
            continue

        # 注文価格が予算を超えた場合は中断
        if ((item_unit_min * last_price) > budget_jpy):
            continue

        # 注文量の計算
        # buget_jpy / last_price = 123.456789, item_unit_min = 0.01の場合, amount = 123.45になるような計算を行う
        # floadの場合, 最小桁付近で計算誤差がでるためroundで桁を調整する
        amount = round((math.floor((budget_jpy / last_price) / item_unit_min) * item_unit_min), int(math.log10(1/item_unit_min)))

        # 小数点を使わない場合は整数に変換する
        if (amount.is_integer()):
            amount = int(amount)

        if (last_price.is_integer()):
            last_price = int(last_price)

        # リクエストのパラメータ設定
        method = 'trade'
        params = {
            'currency_pair': p,
            'action' : 'bid',
            'price' : last_price,
            'amount': amount,
        }

        print(params)
        print('total  = {0:.2f} yen' .format(amount*last_price))

        # 注文の送信
        response = zaif.post(method, params)
        if response.status_code != 200:
            continue

        print(response.json())
        print('')


実行結果


$ /usr/local/bin/python3 zaif_fund.py 
usage: python zaif_fund.py <key> <secret key>


$ /usr/local/bin/python3 zaif_fund.py  'あなたのAPIキー' 'あなたのプライベートキー'
{'amount': 0.0005, 'currency_pair': 'btc_jpy', 'action': 'bid', 'price': 936820}
total  = 468.41 yen
{'return': {'order_id': 0, 'remains': 0.0, 'received': 0.0005, 'funds': {'保有資産の一覧'}}, 'success': 1}

{'amount': 0.0031, 'currency_pair': 'bch_jpy', 'action': 'bid', 'price': 159200}
total  = 493.52 yen
{'return': {'order_id': 0, 'remains': 0.0, 'received': 0.0030907, 'funds': {'保有資産の一覧'6}}, 'success': 1}

{'amount': 0.0065, 'currency_pair': 'eth_jpy', 'action': 'bid', 'price': 76800}
total  = 499.20 yen
{'return': {'order_id': xxxxxxxxx, 'remains': 0.0065, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1}

{'amount': 13.6, 'currency_pair': 'xem_jpy', 'action': 'bid', 'price': 36.55}
total  = 497.08 yen
{'return': {'order_id': xxxxxxxxx, 'remains': 13.6, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1}

{'amount': 1, 'currency_pair': 'mona_jpy', 'action': 'bid', 'price': 442.5}
total  = 442.50 yen
{'return': {'order_id': xxxxxxxxx, 'remains': 1.0, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1}



応答のremainsが0.0以外のものは、板に注文が残っていて、0.0の場合は既に取引が完了しています。

約定の判定処理はありませんが、上げトレンドが続くようなことでもない限り大体翌日ぐらいまでには約定する気がします。

今のところ2ヶ月ほど週1回の頻度で実行してますが、注文が未約定のまま残ったことは無いです。


仮想通貨を取引所に置きっぱなしが怖い人は送金するAPIの組み合わせることで別walletへの退避も自動で出来ますね。



Virtualbox + CentOS7 + シリアル接続してみる

Virutalbox上でLinuxを動かす際に起動ログやカーネルパニック時の画面ログを取りたい時があります。

調べると仮想シリアルポートを組み合わせることでVritualbox上の仮想マシンとシリアル接続が可能らしい。


イメージはこんな感じ。

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


ということで早速やってみました。

Windos7のVirtualbox上でCentOS7を起動しTeratermからシリアル接続して起動ログが表示されるか確認します。



仮想シリアルポートの導入


まずは、Windows側にここからソフトをダウンロードしてインストールします。

インストール後、Teratermを起動してCOMポートが2つ増えていればOKです。


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


動作確認としてTeratermを2つ起動し、各COMポート(上絵ではCOM4,COM5)を開いて片方の端末から入力した文字がもう一方の端末に表示されるか確認してみます。

動作確認がとれたら、Virutualboxの設定でシリアルポートにインストールしたCOMポート(今回の例ではCOM4)を割り当てます。

TeratermでCOMポートを開いているとエラーになるので事前に開放しておくこと。


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


ホスト側の設定は以上です。



grub2の設定ファイルを編集


続いてゲスト側の設定です。

シリアルの準備が出来たら、次はCentOS7を起動しgrub2の設定ファイル(/etc/default/grub)を開いて、画面ログをコンソールに出力するように編集します。

GRUB_CMDLINE_LINUXの行の"rhgb quiet"を消すと起動時のログが省略されずに画面に出力されるようになり、console=xxxで出力先のデバイスとボーレートを指定します。


修正前
[root@vm1 ~]# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet
GRUB_DISABLE_RECOVERY="true"

修正後
[root@vm1 ~]# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap console=tty0 console=ttyS0, 115200"
GRUB_DISABLE_RECOVERY="true"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"



ファイルを修正したら下記コマンドでgrub2を更新します。

[root@vm1 ~]# grub2-mkconfig -o /boot/grub2/grub.cfg



これで準備が整いました。


実行結果


Teratermを起動しシリアルポート(今回の例ではCOM5)を開きます。

ゲストOSを再起動してTeraterm上に画面ログが出力すれば成功です。


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


無事シリアルにログが表示されました。

シリアル端末からログイン、コマンド入力等の操作も問題なく行えます。




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

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