Segmentation Faultぐ

Segmentation Fault

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

各社のAPIを使って仮想通貨の価格を表示してみる

以前はcoincheckのAPIだけ使っていましたが、仮想通貨の取引所も増えてきましたので他の取引所が提供しているAPIを試してみました。 pythonで仮想通貨の価格(日本円)表示します。

www.segmentation-fault.xyz

Zaif


APIの公式ドキュメントは下記。

corp.zaif.jp

ソースコード

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import requests
import json

coins = [
            [1,  'BTC',  'btc_jpy'],
            [2,  'XEM',  'xem_jpy'],
            [3,  'MONA', 'mona_jpy'],
        ]

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

def main():
    for i in range(len(coins)):
        response = requests.get(urlbase+coins[i][2])
        if response.status_code != 200:
            raise Exception('return status code is {}'.format(response.status_code))

        rate = json.loads(response.text)

        print("%-4s : \%-10s" % (coins[i][1], rate['last_price']))

if __name__ == "__main__":
    main()


実行結果

[user@localhost zaif]$ date
Sun Nov 12 20:59:35 JST 2017

[user@localhost zaif]$ /usr/local/bin/python3 zaif.py
BTC  : \720000.0
XEM  : \21.1
MONA : \305.7


bitflyer


APIの公式ドキュメントは下記。

lightning.bitflyer.jp

ソースコード

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import requests
import json

coins = [
            [1,  'BTC',    'btc_jpy'],
            [2,  'FX_BTC', 'fx_btc_jpy'],
        ]

urlbase = 'https://api.bitflyer.jp/v1/getticker?product_code='

def main():
    for i in range(len(coins)):
        response = requests.get(urlbase+coins[i][2])
        if response.status_code != 200:
            raise Exception('return status code is {}'.format(response.status_code))

        rate = json.loads(response.text)

        print("%-6s : \%-10s" % (coins[i][1], rate['ltp']))

if __name__ == "__main__":
    main()


実行結果

[user@localhost bitflyer]$ /usr/local/bin/python3 bitflyer.py
BTC    : \710919.0
FX_BTC : \717699.0


bitbank


APIの公式ドキュメントは下記。

docs.bitbank.cc

ソースコード

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import requests
import json

coins = [
            [1,  'BTC',     'btc_jpy'],
            [2,  'XRP',     'xrp_jpy'],
            [3,  'MONA',    'mona_jpy'],
            [4,  'BCC(BCH)','bcc_jpy'],
        ]

urlbase = 'https://public.bitbank.cc/'

def main():
    for i in range(len(coins)):
        response = requests.get(urlbase+coins[i][2]+'/ticker')
        if response.status_code != 200:
            raise Exception('return status code is {}'.format(response.status_code))

        result = json.loads(response.text)

        print("%-8s : \%-10s" % (coins[i][1], result['data']['last']))

if __name__ == "__main__":
    main()


実行結果

[user@localhost bitbank]$ date
Sun Nov 12 21:18:20 JST 2017

[user@localhost bitbank]$ /usr/local/bin/python3 bitbank.py
BTC      : \718474
XRP      : \22.762
MONA     : \320.000
BCC(BCH) : \180365


BTCBOX


APIの公式ドキュメントは下記。

https://www.btcbox.co.jp/help/asm

ソースコード

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import requests
import json

ticker = 'https://www.btcbox.co.jp/api/v1/ticker/'

def main():
    response = requests.get(ticker)
    if response.status_code != 200:
        raise Exception('return status code is {}'.format(response.status_code))

    rate = json.loads(response.text)

    print("BTC_JPY : \%-10s" % (rate['last']))

if __name__ == "__main__":
    main()


実行結果

[user@localhost btcbox]$ date
Sun Nov 12 21:26:59 JST 2017

[user@localhost btcbox]$ /usr/local/bin/python3 btcbox.py
BTC_JPY : \710030


kraken


APIの公式ドキュメントは下記。

www.kraken.com

ソースコード

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import requests
import json

coins = [
            [1,  'XBT(BTC)', 'xbtjpy', 'XXBTZJPY'],
            [2,  'ETH',      'ethjpy', 'XETHZJPY'],
        ]

urlbase = 'https://api.kraken.com/0/public/Ticker?pair='

def main():
    for i in range(len(coins)):
        response = requests.get(urlbase+coins[i][2])
        if response.status_code != 200:
            raise Exception('return status code is {}'.format(response.status_code))

        rate = json.loads(response.text)

        print("%-8s : \%-10s" % (coins[i][1], rate['result'][coins[i][3]]['c'][0]))

if __name__ == "__main__":
    main()


実行結果

[user@localhost kraken]$ date
Sun Nov 12 21:45:18 JST 2017

[user@localhost kraken]$ /usr/local/bin/python3 kraken.py
XBT(BTC) : \714001.000
ETH      : \34330.00000


まとめ


国内の取引所の差額なんかを一覧にしてみるのも面白そうですね。



Linuxのカーネルモジュールを作って遊んでみる(1/2)

今回はLinuxのカーネルモジュール(例えばデバイスドライバ)のプログラミングにトライします。

前半はHello World的プログラムを作り、後半は/procファイルシステムへのアクセスなど色々試して遊んでみたいと思います。

環境はLinux(CentOS 7)(64bit)です。

カーネルモジュールとは


カーネル・モジュールはLinuxのユーザー空間ではなくカーネル空間で動作するプログラムの事です。

例としてデバイスドライバと呼ばれるものはカーネルモジュールの1つのです。

ユーザー空間のアプリケーションのプログラミングとは少々お作法が事なります。

[参考]
itpro.nikkeibp.co.jp


まずはHello World


どんなプログラミングでも最初はやはりHello Worldを試すのが一般的ですので今回も例にもれずやっていきます。

事前にカーネルモジュールのプログラミングに必要なものをインストールします。

# カーネルモジュール開発に必要なパッケージをインストール
[user@localhost ~]$ sudo yum install kernel-devel
[user@localhost ~]$ sudo yum install kernel-headers


# インストール後の確認
# devel, heeadersがインストールされていればOKです。
[user@localhost ~]$ rpm -aq|grep kernel
kernel-tools-libs-3.10.0-514.26.2.el7.x86_64
kernel-tools-3.10.0-514.26.2.el7.x86_64
kernel-3.10.0-327.el7.x86_64
kernel-3.10.0-514.26.2.el7.x86_64
kernel-headers-3.10.0-693.5.2.el7.x86_64
kernel-devel-3.10.0-514.26.2.el7.x86_64  
kernel-devel-3.10.0-693.5.2.el7.x86_64


準備が整ったらdmesgにHello Worldのログを出力するmymod.cとビルドするためのMakefileを作ります。

mymod.c

#include <linux/module.h>
#include <linux/init.h>

/* お約束的な宣言 */
MODULE_LICENSE("GPL");

/* insmod実行時に呼び出される */
static int mymod_init(void)
{
    printk(KERN_INFO "Hello World\n");
    return 0;
}

/* rmmod実行時に呼び出される */
static void mymod_exit(void)
{
    printk(KERN_INFO "Goodbye World\n");
}

/* 関数の登録 */
module_init(mymod_init);
module_exit(mymod_exit);


Makefile

obj-m := mymod.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean



makeを実行するとビルドが開始されます。ビルド後の出来上がった*.koのファイルがカーネルモジュールです。

[user@localhost driver]$ ls
Makefile  mymod.c


[user@localhost driver]$ make
make -C /lib/modules/3.10.0-514.26.2.el7.x86_64/build M=/home/user/driver modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-514.26.2.el7.x86_64'
  CC [M]  /home/user/driver/mymod.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/user/driver/mymod.mod.o
  LD [M]  /home/user/driver/mymod.ko
make[1]: Leaving directory `/usr/src/kernels/3.10.0-514.26.2.el7.x86_64'


[user@localhost driver]$ ls -lrt
total 204
-rw-rw-r--. 1 user user   156 Nov  2 23:00 Makefile
-rw-rw-r--. 1 user user   787 Nov  2 23:01 mymod.mod.c
-rw-rw-r--. 1 user user     0 Nov  2 23:01 Module.symvers
-rw-rw-r--. 1 user user 52864 Nov  2 23:01 mymod.mod.o
-rw-rw-r--. 1 user user   422 Nov  2 23:03 mymod.c
-rw-rw-r--. 1 user user 44400 Nov  2 23:04 mymod.o
-rw-rw-r--. 1 user user    34 Nov  2 23:04 modules.order
-rw-rw-r--. 1 user user 94184 Nov  2 23:04 mymod.ko ★これがカーネルモジュール


実行結果


カーネルモジュールが出来上がったので実際にロードしてみます。

insmodコマンドでカーネルにモジュールをロード、lsmodでロード中のモジュールを確認できます。

[user@localhost driver]$ sudo insmod mymod.ko


[user@localhost driver]$ lsmod |grep mymod
mymod                  12496  0



[user@localhost driver]$ dmesg |tail
[ 1121.612492] mymod: loading out-of-tree module taints kernel.
[ 1121.612524] mymod: module verification failed: signature and/or required key missing - tainting kernel
[ 1121.612713] Hello World



カーネルモジュールのロードとdmesgにHello Worldが出力されていることが確認できました。

今度はrmmodでモジュールをアンロードしてみます。

[user@localhost driver]$ sudo rmmod mymod


[user@localhost driver]$ lsmod |grep mymod
[user@localhost driver]$


[user@localhost driver]$ dmesg |tail
[ 1121.612492] mymod: loading out-of-tree module taints kernel.
[ 1121.612524] mymod: module verification failed: signature and/or required key missing - tainting kernel
[ 1121.612713] Hello World
[ 1179.087241] Goodbye World



こちらも期待通りdmesgにメッセージが出力されていますね。

次回はもう少し実用的なプログラムを作ってみます。




FREDから日経平均株価を取得してみる(Python)

最近は仮想通貨の取引所が公開しているAPIを使い販売レートを取得したりビットコインを購入したり出来ることを知り色々遊んでいました。

ふと、日経平均株価(N225)とか他の経済情報を収集してデータを分析できたら面白いのでは?と思ったので情報を取得する方法が無いかと思い探してみました。

今回はLinuxでPython3を使って日経平均株価を取得します。

日経平均株価の情報源


ちょっと調べたところFREDという米国のセントルイス連邦準備銀行が運営する経済統計に関するデータベースから取得することができるようです。

YahooやGoogleからも取得出来そうですが、取得禁止だったり非公式だったりと渋り気味でした。


情報源 N225の取得 備考
Yahooファイナンス × APIなし、スクレイピング禁止
Google ファイナンス APIはあるが非公式
FRED


FREDとは

  • セントルイス連銀(ミズーリ州セントルイスにある連邦準備銀行の1つ)が運営する経済データのデータベース
  • 米国経済に関する統計データを公開している。
  • 日経225の平均株価を取得可能。


経済情報を取得するライブラリ


経済情報の収集は各所で公開されているAPIを使うかスクレイピング、クローリングによって行うことが出来ますが自分で実装するのは少々面倒です。

Pythonではそういったデータ収集のためのライブラリであるpandas-datareaderを使うことで簡単に情報が取ってこれます。

インストールはpipで行います。

$ sudo /usr/local/bin/pip3 install pandas-datareader


日経平均株価の取得


ライブラリのインストールが出来たので早速取得してみます。

情報はDataReaderを使って取ってこれます。 開始日と終了日は省略可能です。

DataReader("銘柄", "情報源", "開始日", "終了日")


とりあえず、前日の値をとってみます。

# 日経平均株価を取得する(n225.py)

#!/usr/local/bin/python3

from pandas_datareader import data as web

n225 = web.DataReader("NIKKEI225", "fred", "2017/10/27")

print(n225)


$ /usr/local/bin/python3 n225.py
            NIKKEI225
DATE
2017-10-27   22008.45


こんな結果が得られました。値が合ってるかGoogle先生に聞いてみましょう。

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



ちゃんと一致してますね!

あとは色々とデータを集めてきてオレオレ分析してみたいと思います。



C/C++のメモリ破壊を3種の方法で検出してみる

C/C++のプログラマーなら一度は悩ませられるであろうメモリ関連のバグ。その内の1つであるメモリ破壊は作りこむのは簡単ですが、後々見つける事が難しくなる場合も多くやっかいな奴です。

今回はそんなメモリ破壊検出の助けとなるツールをいくつか試してみます。

メモリ破壊を起こすソースコード


まず、メモリ破壊を起こすサンプルとしてバッファオーバーフローを起こすoverflow.cを準備します。

overflow.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    uint8_t *ptr = NULL;

    // 64byte分のメモリ領域を取得
    ptr = (uint8_t *)malloc(64);
    if (NULL == ptr) {
        fprintf(stderr, "out-of-memory\n");
        exit(EXIT_FAILURE);
    }

    // メモリ破壊箇所
    // メモリ領域を1byteはみ出した位置に適当なデータを挿入
    ptr[64] = 0x5a;
    free(ptr);

    return 0;
}



Electric Fenceによる検出


Electric Fenceはmalloc()関数を使用する際にプログラマーがやりがちなバグの検出を手助けするライブラリーです。

通常、malloc()で取得したメモリ領域を超えて不正アクセスを行っても、そこがアクセス可能な領域である場合はプログラムは起動し続けてしまいバグに気づかないことがあります。

Electric Fenceを使用するとそういったケースでも不正メモリアクセスを検出し即時にコアダンプするようになります。

ライブラリーのインストールとコンパイル時に-lefenceを指定することで使用できます。


# ElectricFenceのインストール
[user@localhost ef]$ sudo yum -y install ElectricFence


# コアダンプ時にコアファイルを出力するように設定
[user@localhost ef]$ ulimit -c unlimited


# プログラムのコンパイル
[user@localhost ef]$ gcc -g -O0 overflow.c -lefence


# プログラムの実行
[user@localhost ef]$ ./a.out

  Electric Fence 2.2.2 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com> 
Segmentation fault (core dumped)


# gdbによる解析
[user@localhost ef]$ gdb a.out core.2396
...(途中略)
[New LWP 2396]
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0  0x0000000000400802 in main () at overflow.c:17
17          ptr[64] = 0x5a; ★ここでコアダンプ
Missing separate debuginfos, use: debuginfo-install ElectricFence-2.2.2-39.el7.x86_64 glibc-2.17-157.el7_3.5.x86_64
(gdb)



Valgrindによる検出


Varlgrindはメモリリーク検出やメモリデバッグを行うことができるツールです。解析の速度は低速ですがデバッグ対象のプログラムを再コンパイルしなくても使えるというメリットがあります。


# Valgrindのインストール
[user@localhost ef]$ sudo yum -y install valgrind


# プログラムのコンパイル
[user@localhost ef]$ gcc -g -O0 overflow.c


# プログラムの実行
[user@localhost ef]$ valgrind --leak-check=full ./a.out
==2430== Memcheck, a memory error detector
==2430== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==2430== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==2430== Command: ./a.out
==2430==
==2430== Invalid write of size 1 ★ここで検出
==2430==    at 0x4006A2: main (overflow.c:17)
==2430==  Address 0x51f6080 is 0 bytes after a block of size 64 alloc'd
==2430==    at 0x4C28BE3: malloc (vg_replace_malloc.c:299)
==2430==    by 0x400666: main (overflow.c:10)
==2430==
==2430==
==2430== HEAP SUMMARY:
==2430==     in use at exit: 0 bytes in 0 blocks
==2430==   total heap usage: 1 allocs, 1 frees, 64 bytes allocated
==2430==
==2430== All heap blocks were freed -- no leaks are possible
==2430==
==2430== For counts of detected and suppressed errors, rerun with: -v
==2430== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)



Mudflapによる検出


MudflapはGCC4に実装されているデバッグ機能です。バッファオーバーフローやメモリリークをプログラム実行時に検出することが可能です。

ライブラリーのインストールとコンパイル時に-fmudflap -lmudflapを指定することで使用できます。


# ライブラリーのインストール
[user@localhost ef]$ sudo yum -y install  libmudflap libmudflap-devel


# プログラムのコンパイル
[user@localhost ef]$ gcc -g -O0 -fmudflap -lmudflap overflow.c


# プログラムの実行
[user@localhost ef]$ ./a.out
*******
mudflap violation 1 (check/write): time=1508643754.888009 ptr=0x1864e10 size=1
pc=0x7f475e654308 location=`overflow.c:17:13 (main)'
      /lib64/libmudflap.so.0(__mf_check+0x18) [0x7f475e654308]
      ./a.out(main+0xb6) [0x400bb3]
      /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f475e2abb35]
Nearby object 1: checked region begins 1B after and ends 1B after
mudflap object 0x1864e70: name=`malloc region'
bounds=[0x1864dd0,0x1864e0f] size=64 area=heap check=0r/0w liveness=0
alloc time=1508643754.887851 pc=0x7f475e654718
      /lib64/libmudflap.so.0(__mf_register+0x18) [0x7f475e654718]
      /lib64/libmudflap.so.0(__real_malloc+0xbf) [0x7f475e65523f]
      ./a.out(main+0x2e) [0x400b2b]
      /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f475e2abb35]
number of nearby objects: 1



まとめ


各ツールの比較

ツール名 デバッグ対象の
再コンパイル
実行速度 動的メモリの
チェック機能
静的メモリの
チェック機能
分かりやすさ
Electric Fence 必要 ×
Valgrind 不要
Mudflap 必要


各ツールの使用感を比較するとこんな感じでしょうか。 どれも一長一短あるので状況に応じて使い分けるのが良さそうです。



gdbで動作中のプロセスをデバッグしてみる

gdbを使ってLinux上で既に動作しているアプリケーションをattachしてデバッグしてみる。

今回は例としてsnmpdをほんの少しだけ解析します。環境はLinux(CentOS7)です。

gdbのインストール

gdbが無いと始まらないのでyumでインストールします。

[user@localhost gdb]$ sudo yum install gdb


SNMPエージェントのビルド

解析用のsnmpdをソースコードからインストールします。インストール方法はこちらを参照。

www.segmentation-fault.xyz

[user@localhost net-snmp-5.7.3]$ /usr/local/sbin/snmpd --version

NET-SNMP version:  5.7.3
Web:               http://www.net-snmp.org/
Email:             net-snmp-coders@lists.sourceforge.net


シンボルの抽出と削除

インストールしたsnmpdをfileコマンドで調べるとシンボルがそのまま残っているのでstripコマンドでシンボルを削除します。

[user@localhost net-snmp-5.7.3]$ file /usr/local/sbin/snmpd
/usr/local/sbin/snmpd: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=e0b96f955caa14b8ad97c44cbc2f292f31e72caf, not stripped
[user@localhost net-snmp-5.7.3]$
[user@localhost net-snmp-5.7.3]$ sudo strip /usr/local/sbin/snmpd
[user@localhost net-snmp-5.7.3]$
[user@localhost net-snmp-5.7.3]$ file /usr/local/sbin/snmpd
/usr/local/sbin/snmpd: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=e0b96f955caa14b8ad97c44cbc2f292f31e72caf, stripped
[user@localhost net-snmp-5.7.3]$


また、ビルドしたsnmpdからデバッグ用のシンボルファイルを取り出しておきます。

[user@localhost net-snmp-5.7.3]$ objcopy --only-keep-debug agent/.libs/snmpd snmpd.debug


SNMPエージェント起動してgdbでattach

準備が出来たのでSNMPエージェントを起動してgdbで補足してみます。

attach後にdirectoryでソースコードを場所、symbol-fileでシンボル情報をgdbに教えてあげます。

そうしたら、snmpdが周期的に実行するループの処理にブレークポイントを張って動きを見てみましょう。

[user@localhost net-snmp-5.7.3]$ sudo /usr/local/sbin/snmpd -c /usr/local/etc/snmpd.conf -p /var/run/snmpd.pid -M /usr/local/share/snmp/mibs
[user@localhost net-snmp-5.7.3]$
[user@localhost net-snmp-5.7.3]$ ps aux |grep snmpd
root     11089  0.0  0.3 132640  4000 ?        S    09:47   0:00 /usr/local/sbin/snmpd -c /usr/local/etc/snmpd.conf -p /var/run/snmpd.pid -M /usr/local/share/snmp/mibs
[user@localhost net-snmp-5.7.3]$
[user@localhost net-snmp-5.7.3]$ sudo gdb --pid=11089
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

...(途中略)...

(gdb)
(gdb) directory agent/
Source directories searched: /home/user/gdb/net-snmp-5.7.3/agent:$cdir:$cwd
(gdb)
(gdb) symbol-file snmpd.debug
Load new symbol table from "/home/user/gdb/net-snmp-5.7.3/snmpd.debug"? (y or n) y
Reading symbols from /home/user/gdb/net-snmp-5.7.3/snmpd.debug...done.
(gdb)
(gdb) break snmpd.c:1217
Breakpoint 1 at 0x40383c: file snmpd.c, line 1217.
(gdb)
(gdb) continue
Continuing.
Breakpoint 1, receive () at snmpd.c:1217
(gdb) p reconfig
$1 = 0
(gdb) l
1212
1213        /*
1214         * Loop-forever: execute message handlers for sockets with data
1215         */
1216        while (netsnmp_running) {
1217            if (reconfig) {
1218    #if HAVE_SIGHOLD
1219                sighold(SIGHUP);
1220    #endif
1221                reconfig = 0;
(gdb) p netsnmp_running
$2 = 1
(gdb)
(gdb) detach
Detaching from program: /usr/local/sbin/snmpd, process 11089
(gdb) q



ちゃんと期待した箇所でブレークしてますね。detachするとsnmpdがgdbの補足から解放されます。 これで起動中のプロセスをgdbでデバッグできることが分かりました。


自動化してみる

実際にやってみるとgdbを起動してから実際に確認するまでにコマンドを打つ操作が多いことが分かります。1回だけなら良いですが何回も同じことをするのはとても面倒です。こういうところは自動化していきましょう。

gdbは-xオプジョンで指定したファイルからgdbのコマンドを入力することができるのでこれを利用します。 外部入力のファイルとしてsnmp_gdb.shを作成します。

snmp_gdb.sh

#
# snmpdをgdbでデバッグしてみる
#

# ログ出力
set logging file gdb.log
set logging on


# ページャー機能をOFF
set pagenation off

# ソースコードの所在を指定
directory agent/

# デバッグの為のシンボルファイルの読み込み
symbol-file snmpd.debug


# ブレークポイントの設定
break snmpd.c:1217
commands
  # ブレークポイント到達時に実行するコマンド
  printf "#### Break Point Start ###\n"
  list
  printf "reconfig = %d\n", reconfig
  printf "netsnmp_running = %d\n", netsnmp_running
  continue
  printf "#### Break Point End   ###\n"
  printf "\n"
end



commands~endの句はbreakポイントに到達した際に自動で実行するコマンドを定義しています。 commandsの書式はcommands <ブレークポイント番号>ですが、引数を省略すると直前に張ったブレークポイントのさ番号になります。

ファイルが作成できたら実際にやってみましょう。

[user@localhost net-snmp-5.7.3]$ sudo gdb --pid=11089 -x snmp_gdb.sh
...(略)
Breakpoint 1 at 0x40383c: file snmpd.c, line 1217.
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64 openssl-libs-1.0.1e-60.el7_3.1.x86_64 zlib-1.2.7-17.el7.x86_64
(gdb)
(gdb) continue
Continuing.

Breakpoint 1, receive () at snmpd.c:1217
1217            if (reconfig) {
#### Break Point Start ###
1212
1213        /*
1214         * Loop-forever: execute message handlers for sockets with data
1215         */
1216        while (netsnmp_running) {
1217            if (reconfig) {
1218    #if HAVE_SIGHOLD
1219                sighold(SIGHUP);
1220    #endif
1221                reconfig = 0;
reconfig = 0
netsnmp_running = 1
#### Break Point End   ###

...(以降繰り返しのため省略)...



これで期待通りの動作を確認できました。キチンと作ればテストの自動化も出来そうですね。



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のソースコードを読んでもう少し理解を深めたいと思います。



ownCloud+nginx+さくらVPSでプライベートクラウド構築

前回、さくらVPS+nginx環境にSSL証明書を導入してHTTPSによる通信ができるようになりました。今回はownCloudを導入して自分専用のファイルサーバを構築していきます。

www.segmentation-fault.xyz

はじめに

ownCloudはファイル共有サーバー・ソフトウェアです。ownCloud社によりオープンソースの無償版と機能拡張+商用サポートがついた有償版(Enterprise版)が提供されています。

公式はこちら。 owncloud.org

また、ownCloudを開発していたメンバーによって作られたNextcloudというものも存在します。こちらはオープンソースのソフトウェアとして提供することに注力しているようです。 ownCloudとNextcloudの関係については下記が参考になります。

qiita.com

どちらも簡単に言うとDropbox風のファイル共有サーバーを自前で作れますよという機能になります。今回はownCloudの無償版を使います。

ownCloudの導入

概要

使用するVPSは既にWordpressを運用している状態ですので、今回はサブディレクトリにownCloudをインストールします。 具体的にはLinux(CentOS7)に下記ディレクトリ構成でインストールします。

/var/www/wordpress
/var/www/owncloud

それぞれ、
https://"サーバのドメイン"/で接続 ⇒ Wordpressにアクセス、
https://"サーバのドメイン"/owncloudで接続 ⇒ ownCloudにアクセス

という感じに住み分けます。


事前準備

まずは下記を用意します。

  1. Linuxサーバ(VPS等)
  2. 独自ドメイン
  3. SSL証明書

Linuxサーバには事前に必要なソフト(nginx, php, mysql(mariaDB))をインストールしておきます。 PHPのバージョンが5.6.0以上である必要があるので注意です。

$ sudo yum install epel-release
$ sudo yum install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
$ sudo yum install --enablerepo remi-php70 php php-fpm php-gmp php-mbstring php-mcrypt php-mysqlnd php-opc
ache php-pear-Net-Curl php-pecl-redis php-pecl-zip php-soap php-intl php-ldap php-gd

$ sudo yum install nginx
$ sudo yum install mysql

$ sudo systemctl enable nginx.service
$ sudo systemctl enable php-fpm.service
$ sudo systemctl enable mariadb.service

下記所有者をnginxに変更します。

$ sudo chown nginx:nginx /var/lib/php/ -R

念のため装置をリブートしておきましょう。

$ sudo reboot


ownCloudのダウンロード

こちらから必要なファイルをダウンロードしてきます。

https://owncloud.org/install/

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

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


ダウンロードが完了したら/var/wwwに展開して所有者をnginxに変更します。

# ファイルのダウンロード
$ wget https://download.owncloud.org/community/owncloud-10.0.3.tar.bz2

# /var/wwwに展開
$ sudo tar jxvf owncloud-10.0.3.tar.bz2 -C /var/www

# 所有者をnginxに変更します
$ sudo chown nginx:nginx /var/www/wordpress/owncloud/ -R


各種設定

ownCloudにアクセスするために下記の設定ファイルを編集していきます。

(1) /etc/php-fpm.d/www.conf
(2) /etc/nginx/conf.d/myserver.conf


(1) /etc/php-fpm.d/www.conf


ユーザとグループがapacheになっている場合はnginxに変更します。 また、nginxとphp-fpm間で通信する為のUNIXドメインのソケットを追記します。

$ sudo vim /etc/php-fpm.d/www.conf

#下記を記載する 
listen = /var/run/php-fpm/php-fpm.sock
listen.mode = 0666
user = nginx
group = nginx


(2) /etc/nginx/conf.d/myserver.conf


nginxの設置ファイルを編集します。 "location ^~ /owncloud { ... }"でくくられた部分でWordpressとownCloudを住み分けてます。


$ sudo vim /etc/nginx/conf.d/myserver.conf


# 下記を記載する
upstream php-handler {
    server unix:/var/run/php-fpm/php-fpm.sock;
}

server {
    listen       80;
    server_name  .<あなたのドメイン>;
    rewrite     ^ https://<あなたのドメイン>$request_uri?;
}

server {
    listen       443 ssl http2;
    #listen       [::]:443 ssl http2;
    server_name  <あなたのドメイン>;
    root         /var/www/wordpress/;

    ssl_certificate "<公開鍵のパス>";
    ssl_certificate_key "<秘密鍵のパス>";

    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_protocols TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers on;
    ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';

    index index.php;

    location ~* /wp-config.php {
        deny all;
    }

    #location ~* /wp-login\.php|/wp-admin/((?!admin-ajax\.php).)*$ {
    #  auth_basic "Please enter your name and password";
    #  auth_basic_user_file  "/var/www/wordpress/.htpasswd";
    #}

    # For OwnCloud
    # Protecting sensitive files from the evil outside world
    location ~ ^/owncloud/(data|config|\.ht|db_structure.xml|README) {
        deny all;
    }

    location ^~ /owncloud {

        root /var/www/;

        # set max upload size
        client_max_body_size 10G;
        fastcgi_buffers 64 4K;

        # Disable gzip to avoid the removal of the ETag header
        gzip off;

        # Uncomment if your server is build with the ngx_pagespeed module
        # This module is currently not supported.
        #pagespeed off;

        index index.php;

        error_page 403 /owncloud/core/templates/403.php;
        error_page 404 /owncloud/core/templates/404.php;

        location ~ ^/owncloud/(?:\.htaccess|data|config|db_structure\.xml|README) {
            deny all;
        }

        rewrite ^/owncloud/caldav(.*)$ /owncloud/remote.php/caldav$1 redirect;
        rewrite ^/owncloud/carddav(.*)$ /owncloud/remote.php/carddav$1 redirect;
        rewrite ^/owncloud/webdav(.*)$ /owncloud/remote.php/webdav$1 redirect;

        rewrite ^/owncloud/core/doc/([^\/]+)(?:$|/) /owncloud/core/doc/$1/index.html;

        try_files $uri $uri/ /owncloud/index.php;

        location ~ \.php(?:$|/) {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param HTTPS on;
            fastcgi_pass php-handler;
        }

        # Optional: set long EXPIRES header on static assets
        location ~* \.(?:jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ {
            expires 30d;
            # Optional: Don't log access to assets
            access_log off;
        }

    }

    location ~ \.php$ {
       fastcgi_split_path_info ^(.+\.php)(/.+)$;
       include  fastcgi_params;
       fastcgi_index index.php;
       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
       fastcgi_param PATH_INFO $fastcgi_script_name;
       fastcgi_pass php-handler;
    }
}


編集が完了したらngint -tでコンフィグの確認をします。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

ファイルの編集が終ったらnginx, php-fpmを再起動します。

$ sudo systemctl restart nginx.service
$ sudo systemctl restart php-fpm.service


データベースの作成

ownCloud用にデータベースを作成します。

$ sudo mysql -u root -p

MariaDB [(none)]>

# ユーザの作成
MariaDB [(none)]> create user 'abcdefg'@localhost IDENTIFIED BY 'sukeke1234';
Query OK, 0 rows affected (0.12 sec)

# データベースの作成
MariaDB [(none)]> create database owncloud;
Query OK, 1 row affected (0.00 sec)

# データベースに作成したユーザによる操作権限を付与
MariaDB [(none)]> GRANT ALL ON ownclound.* TO 'abcdefg'@'localhost' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

# 念のため
MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> exit
Bye

$ 

これで事前準備は完了です。


接続確認

https://"サーバのドメイン"/owncloudで接続して下記画面が出ればOKです。

https://"サーバのドメイン"/の場合はWordpressが起動すること事も確認しましょう。

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

管理者アカウント(ownCloudへのログイン用のアカウント)とデータベースファイルを指定してセットアップを行います。 (管理者アカウントにadminを指定するとエラーになるので注意。)

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

セットアップ後に下記画面が表示されるので作成した管理者アカウントでログインします。

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

無事ログインまで出来れば完了です。

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


スマホからアクセス

ownCloud環境を構築できましたのでスマホアプリからアクセスしてみましょう。 私はAndroidユーザなのでAndroidアプリ(有料)を使います。

f:id:segmentation-fault:20171007153553j:plain

f:id:segmentation-fault:20171007153555j:plain


ファイルの参照、追加、削除が出来るか確認しましょう。



ハマったこと

作業の過程で色々なエラーで苦しめられました(^-^;)

PHPのバージョンが古い

現象:こんな画面が表示される。

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

対策:PHPのバージョンアップ

$ sudo yum install epel-release
$ sudo yum install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
$ sudo yum install --enablerepo remi-php70 php php-fpm php-gmp php-mbstring php-mcrypt php-mysqlnd php-opc
ache php-pear-Net-Curl php-pecl-redis php-pecl-zip php-soap php-intl php-ldap php-gd


/var/run/php-fpm/php-fpm.sockにアクセス不可

現象:こんな画面が表示される。

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

原因:/var/run/php-fpm/php-fpm.sockにアクセス権が足りない。

対策:www.confにソケットの権限を追記する。

$ sudo vim /etc/php-fpm.d/www.conf

#下記を記載する 
listen = /var/run/php-fpm/php-fpm.sock
listen.mode = 0666 # defaultだと 0660のためnginxがアクセスできない


データベースのユーザ名にハイフン"-"があるとAccess deniedになる。

現象:こんな画面が表示される。

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

対策:試しにデータベースのユーザ名を"-"無しにするとエラーが出なくなった。


セットアップ実行で404 Not Foundになる

現象:

公式のマニュアルでExample Configurationsにある例に従って設定してみるも、セットアップ画面までは行くが、セットアップ実行で404 Not Foundになってしまう。

対策:

原因は分からずじまいですが、nginxの設定ファイルに下記を記載したら動作した。

    location ^~ /owncloud {

        root /var/www/;

        # set max upload size
        client_max_body_size 10G;
        fastcgi_buffers 64 4K;

        # Disable gzip to avoid the removal of the ETag header
        gzip off;

        # Uncomment if your server is build with the ngx_pagespeed module
        # This module is currently not supported.
        #pagespeed off;

        index index.php;

        error_page 403 /owncloud/core/templates/403.php;
        error_page 404 /owncloud/core/templates/404.php;

        location ~ ^/owncloud/(?:\.htaccess|data|config|db_structure\.xml|README) {
            deny all;
        }

        rewrite ^/owncloud/caldav(.*)$ /owncloud/remote.php/caldav$1 redirect;
        rewrite ^/owncloud/carddav(.*)$ /owncloud/remote.php/carddav$1 redirect;
        rewrite ^/owncloud/webdav(.*)$ /owncloud/remote.php/webdav$1 redirect;

        rewrite ^/owncloud/core/doc/([^\/]+)(?:$|/) /owncloud/core/doc/$1/index.html;

        try_files $uri $uri/ /owncloud/index.php;

        location ~ \.php(?:$|/) {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param HTTPS on;
            fastcgi_pass php-handler;
        }

        # Optional: set long EXPIRES header on static assets
        location ~* \.(?:jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ {
            expires 30d;
            # Optional: Don't log access to assets
            access_log off;
        }

    }

まとめ

セキュリティ的に見直すところもあるとは思いますが、ひとまずownCloudを使って自分だけのファイルサーバを作ることができました。 エラーには苦しめられましたが、それもまた良い勉強になったということで。