Segmentation Faultぐ

Segmentation Fault

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

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

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




Linuxのnamespaceでネットワークを区切って遊ぶ

Linuxのnamaspace(名前空間)で遊んでみます。

Dockerとかコンテナを実現する技術で使われるアレですね。

お遊び内容



まず、下図のネットワーク構成を作ります。

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


・eth1,eth2は内部ネットワーク
・vlan100,vlan200はVLANタグ付きのインタフェース
・veth1, veth2はnamespace間をつなぐインタフェース
・br1,br2はブリッジ



VLAN単位でネットワークを区切って通信ができるのか確認してみます。
ルーター、L3スイッチのVRFみたいなものですね。

vlan100,vlan200はネットワークが違うのでIPアドレスが同じでも各々で通信が可能なはずです。


各種設定


まず、VM1,VM2を作成します。(ここではともにCentOS 7です)

内部ネットワークのインタフェースはプロミスキャスモードをすべて許可にしてから起動します。
(許可しないとnamespace側のMACアドレスが宛先MACになっているフレームを破棄してしまうため)

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


以下、各VMの設定です。

◆VM1

内部ネットワークのインタフェース名を構成図のeth1に合わせます。

[user@localhost ~]$ ip link show dev enp0s9
4: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
    link/ether 08:00:27:1d:b8:43 brd ff:ff:ff:ff:ff:ff

[user@localhost ~]$ sudo ip link set dev enp0s9 down
[user@localhost ~]$ sudo ip link set dev enp0s9 name eth1
[user@localhost ~]$ sudo ip link set dev eth1 up

[user@localhost ~]$ ip link show dev eth1
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
    link/ether 08:00:27:1d:b8:43 brd ff:ff:ff:ff:ff:


続いてnamespace, 各インタフェースを作成します。

namespaceはip netns add 作ります。
namespace上でコマンドを実行する場合はip netns exec "namespace" "実行したいcommand" となります。

[user@vm1 ~]$ # namespaceの作成
[user@vm1 ~]$ sudo ip netns add ns1                                                      # namespaceの作成
[user@vm1 ~]$ sudo ip link add veth1 type veth peer name eth1 netns ns1                  # namespace間インタフェースの作成

[user@vm1 ~]$ # ブリッジの作成
[user@vm1 ~]$ sudo brctl addbr br1                                                       # ブリッジの作成
[user@vm1 ~]$ sudo brctl addif br1 veth1                                                 # ブリッジにveth1を接続
[user@vm1 ~]$ sudo brctl addif br1 eth1                                                  # ブリッジにeth1を接続

[user@vm1 ~]$ # VLANの作成
[user@vm1 ~]$ sudo ip netns exec ns1 ip link add link eth1 name vlan100 type vlan id 100 # vlan100の作成
[user@vm1 ~]$ sudo ip netns exec ns1 ip addr add 192.168.0.1/24 dev vlan100              # vlan100にIPアドレス設定
[user@vm1 ~]$ sudo ip link add link eth1 name vlan200 type vlan id 200                   # vlan200の作成
[user@vm1 ~]$ sudo ip addr add 192.168.0.1/24 dev vlan200                                # vlan200にIPアドレス設定

[user@vm1 ~]$ # インタフェースのUP
[user@vm1 ~]$ sudo ip link set dev veth1 up                                              # veth1のUP
[user@vm1 ~]$ sudo ip link set dev eth1 up                                               # eth1のUP
[user@vm1 ~]$ sudo ip link set dev br1 up                                                # br1のUP
[user@vm1 ~]$ sudo ip netns exec ns1 ip link set dev eth1 up                             # ns1のeth1をUP
[user@vm1 ~]$ sudo ip netns exec ns1 ip link set dev vlan100 up                          # vlan100のUP
[user@vm1 ~]$ sudo ip link set dev vlan200 up                                            # vlan200のUP


namespaceと各インタフェースが作成されていることを確認します。

[user@vm1 ~]$ ip netns list
ns1
[user@vm1 ~]$
[user@vm1 ~]$ ip addr show
...
       valid_lft forever preferred_lft forever
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br1 state UP qlen 1000
    link/ether 08:00:27:1d:b8:43 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::bac:439:3aa7:6261/64 scope link
       valid_lft forever preferred_lft forever
6: veth1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br1 state UP qlen 1000
    link/ether 3a:f0:e6:57:6b:16 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::38f0:e6ff:fe57:6b16/64 scope link
       valid_lft forever preferred_lft forever
7: br1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 08:00:27:1d:b8:43 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::a00:27ff:fe1d:b843/64 scope link
       valid_lft forever preferred_lft forever
8: vlan200@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 08:00:27:1d:b8:43 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/32 scope global vlan200
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe1d:b843/64 scope link
       valid_lft forever preferred_lft forever

[user@vm1 ~]$ sudo ip netns exec ns1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth1@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT qlen 1000
    link/ether 26:5a:84:c9:33:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: vlan100@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT qlen 1000
    link/ether 26:5a:84:c9:33:03 brd ff:ff:ff:ff:ff:ff


◆VM2

VM2もVM1と同じように設定します。

[user@vm2 ~]$ # namespaceの作成
[user@vm2 ~]$ sudo ip netns add ns2                                                      # namespaceの作成
[user@vm2 ~]$ sudo ip link add veth2 type veth peer name eth2 netns ns2                  # namespace間インタフェースの作成

[user@vm2 ~]$ # ブリッジの作成
[user@vm2 ~]$ sudo brctl addbr br2                                                       # ブリッジの作成
[user@vm2 ~]$ sudo brctl addif br2 veth2                                                 # ブリッジにveth2を接続
[user@vm2 ~]$ sudo brctl addif br2 eth2                                                  # ブリッジにeth2を接続

[user@vm2 ~]$ # VLANの作成
[user@vm2 ~]$ sudo ip netns exec ns2 ip link add link eth2 name vlan100 type vlan id 100 # vlan100の作成
[user@vm2 ~]$ sudo ip netns exec ns2 ip addr add 192.168.0.2/24 dev vlan100              # vlan100にIPアドレス設定
[user@vm2 ~]$ sudo ip link add link eth2 name vlan200 type vlan id 200                   # vlan200の作成
[user@vm2 ~]$ sudo ip addr add 192.168.0.2/24 dev vlan200                                # vlan200にIPアドレス設定

[user@vm2 ~]$ # インタフェースのUP
[user@vm2 ~]$ sudo ip link set dev veth2 up                                              # veth2のUP
[user@vm2 ~]$ sudo ip link set dev eth2 up                                               # eth2のUP
[user@vm2 ~]$ sudo ip link set dev br2 up                                                # br2のUP
[user@vm2 ~]$ sudo ip netns exec ns2 ip link set dev eth2 up                             # ns2のeth2をUP
[user@vm2 ~]$ sudo ip netns exec ns2 ip link set dev vlan100 up                          # vlan100のUP
[user@vm2 ~]$ sudo ip link set dev vlan200 up                                            # vlan200のUP


疎通確認


vlan200の通信確認

[user@vm1 ~]$ ping 192.168.0.2
[user@vm2 ~]$ sudo tcpdump -i vlan100


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



vlan100の通信確認

[user@vm1 ~]$ sudo ip netns exec ns1 ping 192.168.0.2
[user@vm2 ~]$ sudo ip netns exec ns2 tcpdump -i vlan100


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



期待通り通信ができることを確認できました。


参考


Technology: VRF & Linux Network Name Space

Linux Network Namespace で遊んでみる | CUBE SUGAR STORAGE

namespace を使ってみる - いますぐ実践! Linuxシステム管理 / Vol.260




proc経由でkernelとやり取りしてみる

procインタフェースを利用してユーザランドのアプリケーションとカーネルでデータをやりとりするサンプルです。

具体的には下記のようにechoでリダイレクトした文字列をカーネルで受信して、catで覗くとリダイレクトした文字列が表示されるような簡単なカーネルモジュールを作ります。開発環境はCentOS7です。

完成イメージ

$ echo "hello" > /proc/example 
$ cat /proc/example 
hello
$


ソースコード

procmod.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>

static char example[64] = {0};

static ssize_t
example_write(
    struct file *filp,
    const char __user *buff,
    size_t user_len,
    loff_t *offset )
{
    size_t len = 0;

    if (0 != *offset)
        return -EINVAL;

    memset(example, 0, sizeof(example));

    len = user_len > sizeof(example) ? sizeof(example) : user_len;

    memcpy(example, buff, len);
    example[len-1] = '\n';

    printk(KERN_INFO "example: wrote - %s", example);

    return user_len;
}

/* /proc/exampleの内容を表示 */
static int
example_show(
    struct seq_file *p,
    void *v)
{
    seq_printf(p, "%s", example);
    printk(KERN_INFO "example: show - %s", example);
    return 0;
}

/* cat /proc/exampleで実行される */
static int
example_open(
    struct inode *inode,
    struct file *file)
{
    return single_open(file, example_show, NULL);
}

static const struct file_operations proc_example_operations = {
    .owner      = THIS_MODULE,
    .open       = example_open,
    .read       = seq_read,
    .llseek     = seq_lseek,
    .release    = single_release,
    .write      = example_write,
};

/* insmod実行時に呼び出される */
static int proc_example_init(void)
{
    proc_create("example", S_IRUGO | S_IWUGO, NULL, &proc_example_operations);
    printk(KERN_INFO "example: Module loaded, and created /proc/example\n");
    return 0;
}

/* rmmod実行時に呼び出される */
static void proc_example_exit(void)
{
    remove_proc_entry("example", NULL);
    printk(KERN_INFO "example: Module unloaded.\n");
}

module_init(proc_example_init);
module_exit(proc_example_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example Kernel Module");


Makefile

obj-m := procmod.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
make -C /lib/modules/3.10.0-514.26.2.el7.x86_64/build M=/home/user/driver/proc modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-514.26.2.el7.x86_64'
  CC [M]  /home/user/driver/proc/procmod.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/user/driver/proc/procmod.mod.o
  LD [M]  /home/user/driver/proc/procmod.ko
make[1]: Leaving directory `/usr/src/kernels/3.10.0-514.26.2.el7.x86_64'
$
$ sudo insmod procmod.ko
$ lsmod |grep procmod
procmod                12702  0


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


ちゃんとアプリ、カーネル間でデータをやりとりできました。

次回は/dev/にスペシャルファイルを作ってアプリとカーネル間でepoll,ioctlを使った通信を試したいと思います。




さくらVPSでBitZenyを掘ってみる

さくらVPSでBitZenyのプールマイニングに挑戦してみます。まずはお試し1週間です。

はじめに

これまで、さくらVPSの空きリソースを使ってXMR(モネロ)という仮想通貨のプールマイニングをしていましたが、ここ最近の人気上昇を受けてか採掘量が段々と減ってきました(対JPY,BTCの価格もだいぶ上昇しましたし)。所詮空きリソースなのでこのままでも良いのですが、もう少しマイナーな仮想通貨で採掘難易度が低くかつ安定した数量を得られるものの方が面白いかな~と探していたところBitZenyという国産の仮想通貨にたどり着きました。

http://bitzeny.org/

www.segmentation-fault.xyz


全体の流れ

大まかな流れは下記になります。

  1. walletの作成
  2. マイナープールにアカウント作成
  3. ワーカーの作成
  4. マイナーソフトの導入と実行
  5. あとは待つだけ


walletの作成

まずはBitZeny用のwalletを作成します。

https://bitzeny.jp/login

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


作成時にニーモニックフレーズを選択します。 とりあえず注意書きにある通り、後で分かるように画面キャプチャとるなどして保管しておけばOK。

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


処理を進めるとwalletが出来上がります。
ページの下方にあるwalletのアドレスを後ほど使用します。

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


マイナープールにアカウント作成

BitZenyのマイナープールはいくつかありますが、今回アカウントを作成するのは"BitZeny Mining Pool - 大人の自由研究”です。 最初はLA BitZeny Poolにしようかと思ったのですが、最近のBitZeny高騰をうけて人気化した事とLA BitZeny Poolでのマイニングに関する記事が多いためかマイナーが1か所に偏っている状態でした。偏りがあると採掘効率が悪いようですのでこちらにしました。

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

BitZeny Mining Pool - 大人の自由研究 - Home



では、右上のメニューからSign Upを選択して新規アカウントを作成していきます。

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


アカウントが作成されたら、次はワーカーを作成します。


ワーカの作成

マイニングを行うにはワーカーなるものを作成する必要があります。

左メニューのMy AccountからMy Workerを選択します。

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

Add New Workerの所に任意の名前とパスワードを設定しワーカーを追加します。
ワーカーの名前とパスワードはuser, passwordのままでも構いません。

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


マイナーソフトの導入と実行

続いてマイナーソフトを採掘を行うマシンにインストールします。

まずは事前準備としてマイナーソフトを使うために必要なソフトを導入します。

$ sudo yum groupinstall "Development Tools"
$ sudo yum install git libcurl-devel python-devel screen rsync


マイナーソフトのソースコードをダウンロードしてビルドします。

$ git clone https://github.com/bitzeny/cpuminer.git cpuminer
$ cd cpuminer
$ ./autogen.sh
$ ./configure CFLAGS="-O3 -march=native -funroll-loops -fomit-frame-pointer"
$ make
$ sudo make install


マイナーソフトが用意できたらマイナープールのホームページの記載の通りにコマンドを実行します。

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

ログアウトしても処理を続けて欲しいのでnohupを付けておきます。

$ sudo nohup /usr/local/bin/minerd -a yescrypt -o stratum+tcp://ukkey3.space:3333 -u <ログインアカウント>.<ワーカー名> -p <ワーカーのパスワード> &


コマンドを実行してから数十分ほど待っているとDashBoardのハッシュレート等の採掘状況が表示されるようになります。


結果

ひとまず様子見で5日間でどれくらいマイニング報酬が得られるのか観測してみました。

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

大体1日あたり1.8 ZNY ぐらいは得られているでしょうか。
1.0 ZNYの相場が日本円で10円~40円なので20円/日の稼ぎって感じですね。

もちろん今後、価値が上がれば話は変わります(逆もしかり)。


参考

マイナーソフトの導入についてはこちらを参考にさせていただきました。

bombox.net



VBScriptでWordファイルのページ数一覧を作ってみる

ある日、大量のWordファイルで作られた資料の中身を目視確認する必要に迫られました。

複数人で分担して実施するのですがファイル毎にボリューム(ページ数)が異なるためファイル数で割り振ると不公平が発生します。

そこで事前にファイル単位のページ数がわかるといいなぁということでページ数の一覧を生成するスクリプトを作ってみました。

ソースコード


作成したスクリプトはスクリプトを実行したディレクトリ配下にあるWordファイルを検索しページ数を取得して結果をファイル出力します。

Wordファイルのページ数はBuiltinDocumentPropertiesで取ってこれます。

下記を参考にしました。

www.relief.jp


文字コードはSJISなので注意です。(UTF-8だと実行時に日本語を扱っている行で怒られます。)

'
'VBScript File
'Character code is SJIS
'

Option Explicit

Call Main()

'
'@func Main
'@brief メイン関数
'
Sub Main()
  'ファイルシステムオブジェクトの生成
  Dim fso 
  Set fso = CreateObject("Scripting.FileSystemObject")
  
  'カレントDIR配下にあるWordファイルのページ数を取得
  Dim result
  result = GetNumberOfWordFilePageAll(fso, fso.GetFolder("."))
  
  '結果をファイルに出力
  Dim ret
  ret = WriteResultToFile(fso, "result.csv", result)
  
End Sub

'
'@func GetNumberOfWordFilePageAll
'@brief カレントDIRにあるWordファイルのページ数を取得する
'@param[in] dir  ディレクトリパス
'@param[in] fso  ファイルシステムオブジェクト
'
Function GetNumberOfWordFilePageAll(fso, dir)
  
  Dim result
  result= ""
  
  ' カレントDIRにあるwordファイルのページ数を数える
  Dim File
  For Each File in dir.Files
    result= result& GetNumberOfWordPages(fso, File.path)
  Next
  
  ' カレントDIR配下にあるDIRに対して再帰的に実行する
  Dim Subdir
  For Each Subdir in dir.SubFolders
    ' 再帰呼び出し
    result= result& GetNumberOfWordFilePageAll(fso, Subdir)
  Next
  
  GetNumberOfWordFilePageAll = result
  
End Function

'
'@func GetNumberOfWordPages
'@brief Wordファイルのページ数を取得する
'@param[in] fso       ファイルシステムオブジェクト
'@param[in] filepath  対象ファイル
'
Function GetNumberOfWordPages(fso, filepath)
  
  '拡張子のチェック(wordファイル以外は対象外)
  Dim ExtName
  ExtName = UCase(fso.GetExtensionName(filepath))
  If Not (ExtName = "DOC" OR ExtName = "DOCX") Then
    Exit Function
  End If
  
  ' wordファイルを開く
  Dim wordApp
  Set wordApp = WScript.CreateObject("Word.Application")
  Dim doc
  Set doc = wordApp.Documents.Open(filepath)
  
  ' ファイル名,ページ数を返す
  GetNumberOfWordPages = filepath & "," & doc.BuiltInDocumentProperties(14) & vbNewLine
  
  ' wordファイルを閉じる
  doc.Close
  Set doc = Nothing
  wordApp.Quit
  Set wordApp = Nothing
  
End Function


' @func WriteResultToFile
' @brief 処理結果をファイルに出力
' @param[in] fso      ファイルシステムオブジェクト
' @param[in] filename 出力ファイル名
' @param[in] result   処理結果
'
Function WriteResultToFile(fso, filename, result)
  
  Dim file
  Set file = fso.CreateTextFile("." & "\" & filename)
  Dim header
  header = UCase("ファイル名, ページ数")
  file.WriteLine(header)
  file.WriteLine(result)
  file.close
  Set file = Nothing
  
  MsgBox "処理結果を '" & filename & "' に出力しました。"
  
End Function


実行結果


ちゃんと動作するか適当なワードファイルを用意して試してみます。

カレントにページ数が3のファイルを、サブディレクトリにページ数が1のファイルを作り配置します。

その他、Wordファイル以外を誤ってカウントしないか確認するためにダミーのテキストファイルを配置します。

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

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


こんな感じで配置しました。


ファイルの配置したらスクリプトをクリックして実行してみます。

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

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

ちゃんと動いてそうですね。

あとは記載内容のチェックもある程度自動化できればいいなぁ。

図とかは限界がありそうですが。