Segmentation Faultぐ

Segmentation Fault

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

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

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

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

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




各社の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のカーネルモジュールを作って遊んでみる

今回は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にメッセージが出力されていますね。

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