カテゴリー別アーカイブ: Linux

Google Chrome 59のHeadlessモードを使ってみる

Headless modeを内蔵したGoogle Chrome 59がリリースされたので、Python + Seleniumで使ってみました。

これまでheadlessと言えば、Xvfbを使って仮想的なディスプレイに作画することで実現していました。しかしGoogle Chrome 59以降ではChrome自体にheadless modeが内蔵され、Xvfbを使わなくてもディスプレイを持たないCLI環境でWebページのテストなどをできるようになりました。

そこでUbuntu 17.04上でGoogle ChromeのHeadlessモードを試してみました。

Chromeをインストール

Ubuntu 17.04にはChromeのパッケージがない1ので、まずChromeのダウンロードページからdebパッケージをダウンロードします。

Chromeはいくつか依存パッケージがあるので、それらのパッケージをインストールした後にdpkgコマンドでインストールします。

$ sudo apt install libappindicator1 libindicator7
$ sudo dpkg -i google-chrome-stable_current_amd64.deb
$ which google-chrome-stable
/usr/bin/google-chrome-stable

Seleniumとchromedriverをインストール

Seleniumは問題ありませんが、Ubuntu 17.04にあるchromedriveはChrome 59をサポートしていません。そこで最新のChromeDriverをダウインロードしてインストールします。

$ sudo apt install python3-selenium
$ cd ~/bin
$ unzip ~/Download/chromedriver_linux64.zip
$ ./chromedriver -v
ChromeDriver 2.30.477691 (6ee44a7247c639c0703f291d320bdf05c1531b57)

Headless modeを試してみる。

Pythonで次のようなスクリプトを書いて、Chromeをheadless modeで使えるか試してみました。

#!/usr/bin/python3

import os

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

CHROME_BIN = "/usr/bin/google-chrome-stable"
CHROME_DRIVER = os.path.expanduser('~/bin/chromedriver')

options = Options()
options.binary_location = CHROME_BIN
options.add_argument('--headless')
#options.add_argument('--remote-debugging-port=9222')

driver = webdriver.Chrome(CHROME_DRIVER, chrome_options=options)

#driver.get("chrome://settings/help")
#driver.save_screenshot("/tmp/help.png")
driver.get("https://www.yahoo.co.jp")
driver.save_screenshot("/tmp/yahoo.png")
print(driver.title)
driver.quit()

このスクリプトを実行してYahoo!のページタイトルYahoo! JAPANとスクリーンショットが作成されていれば成功です。

画面のサイズ指定ができない

これまでWindowsサイズをset_window_size()で指定していましたが、これがあると次のようなエラーを引き起こします。

selenium.common.exceptions.WebDriverException: Message: unknown error: cannot get automation extension
from unknown error: page could not be found: chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/_generated_background_page.html
  (Session info: headless chrome=59.0.3071.86)
  (Driver info: chromedriver=2.30.477691 (6ee44a7247c639c0703f291d320bdf05c1531b57),platform=Linux 4.10.0-21-generic x86_64)

解決策がないかとググってChromeDriver error “unknown error: cannot get automation extension”を見つけたのですが、私の環境では

options.add_argument("disable-extensions");
options.add_argument("--start-maximized");

というオプションを追加しても解決せず、set_window_size()を削除する必要がありました。

脚注

  1. UbuntuにはChromeのパッケージはありませんが、オープンソース版のChromiumがあります。 

DHCPv6-PDでIPv6のサブネットワークを作成

フレッツ光ネクストは、ひかり電話契約をすると/64よりも小さなプレフィックスを使用できます。このプレフィックスを使用すると、ひかり電話ルータの下に別のルータを置いて自分のネットワークを持つことができます。今回はこの方法で自分のネットワークをIPv6の世界に接続してみます。

ひかり電話契約をしていない場合は、IPv6とIPv4のデュアルスタックで接続のようにNAPT(マスカレード)を使用してネットワークを接続します。

ここでの設定は、ZOOT NATIVE (DS-Lite)でネット接続の状態になっている前提での説明です。追加で必要なradvdのインストール方法は、IPv6とIPv4のデュアルスタックで接続を参照して下さい。

サイト構成

Raspberry Piのインターフェイスは、次のように使用します。

インターフェイス ネットワーク アドレス
eth0 自分のネットワーク/64 DHCPv6-PDで自動設定
eth1 Internet(IPoE) DHCPv6で自動設定

自分のネットワーク内の端末は、Raspberry Piが送信するRAで自動的にIPv6アドレスやデフォルトルータが設定されます。

DHCPv6-PDによるプレフィックスの委譲

自分のネットワークを上位のネットワークへ接続するためには、プレフィックス委譲を受けます。この委譲を受けるのに使われるのがDHCPv6-PDです。上位のルータは、DHCPv6-PDによるやりとりでプレフィックスの払い出しと同時に、払い出したプレフィックスに相当するIPv6パケットをどこに送ったらよいかを記録しています。

DHCPはIPv4でIPアドレスなどネット接続に必要な情報を自動設定する時にお馴染みですが、IPv6用のDHCP(DHCPv6)は名前は同じでも全く別のプロトコールです。もっともIPv4で使ったDHCPクライアントは、DHCPv6クライアントとしても機能します。

DHCPv6対応クライアントには何種類かありますが、今回はRaspbian(jessie)に標準でインストールされているdhcpcdを使用します。他のDHCPv6クライアントを使用してプレフィックスの委譲を受ける時は、IPv6 プレフィックス委譲 (DHCPv6-PD) – Archlinuxを参照して下さい1

dhcpcdを有効に

dhcpcdは、ZOOT NATIVE (DS-Lite)でネット接続で無効にしているので、次のコマンドでシステムが起動時する時に自動的に実行開始されるようにします。

$ sudo systemctl enable dhcpcd.service

dhcpcdの設定

dhcpcdの設定は、/etc/dhcpcd.confに書きます。

# この上はデフォルトのまま

# IPv4は設定しない
ipv6only

# LANのIPv4用
# /etc/network/interfacesで設定される。
interface eth0
noipv6

# https://wiki.archlinuxjp.org/index.php/IPv6#.E3.83.97.E3.83.AC.E3.83.95.E3.82.A3.E3.83.83.E3.82.AF.E3.82.B9.E5.A7.94.E8.AD.B2_.28DHCPv6-PD.29
duid
noipv6rs
waitip 6

# WANのIPv6用 DHCPv6-DP
interface eth1
ipv6rs
iaid 1
# use the interface connected to your LAN
ia_pd 1 eth0

ひかり電話ルータはIPv4のDHCPサーバ機能を持っているので、ip6only(noipv4)をしないとDHCPによりIPv4のアドレスがeth1に割り当てられます。

interface設定を削除

dhcpcdでインターフェイスを設定するので/etc/network/interfacesにあるeth1の設定は不要です。そこでeth1に関する部分は全て削除してしまいます。

eth0もdhcpcdで設定できるので不要な気がしますが、これを削除するとシステムを起動する時にDHCPサーバがエラーで実行開始できなくなってしまいます。またDHCPv6-PDでプレフィックスの委譲を受ける時に/etc/resolv.confの内容がひかり電話ルータからのものになってしまいます。そのためeth0はinterfacesで設定します。

ufwの設定

プレフィックス委譲を使って自分のネットワークを接続すると、ネットワーク内の全ての端末にグローバルなIPv6が割り振られます。そのためアドレスが分かればほとんど何の制限もなしに世界中からアクセスすることが可能となります。

その裏返しとして想定外の端末が世界に公開されてしまうなど、NAPT(マスカレード)を使ったIPv6とIPv4のデュアルスタックで接続より各端末がより危険な状態に置かれ、世界中から自分のネットワーク内の端末が攻撃される危険性にさらされます。

この危険性を少しでも小さくするには、ルータを通過するパケットの選別が非常に大切です。

と言っても今回は実験なので必要最小限の制限とし、次のような一般的な最小限のポリシーとしました。

  • 自分のネットワークから外へは無制限に出ていける。
  • 自分のネットワークへは、出て行ったパケットに関連するパケットのみが入れる。

そこで/etc/default/ufwを次のように変更し、許可されたパケット以外は全て破棄するようにします。

DEFAULT_FORWARD_POLICY="DROP"
DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"

許可するパケットのルールは、ufwの設定ファイルに追加します。

/etc/ufw/before.rules(IPv4用)

# 以下を追加する。
# accept all from LAN and returned RELATED or ESTABLISHED packets
-A ufw-before-forward -i eth0 -j ACCEPT
-A ufw-before-forward -m state --state RELATED,ESTABLISHED -j ACCEPT

/etc/ufw/before6.rules(IPv6用)

# 以下を追加する。
# https://www.cert.org/downloads/IPv6/ip6tables_rules.txt
# accept from LAN
-A ufw6-before-input -i eth0 -j ACCEPT

# allow DHCPv6-PD
#-A ufw6-before-input -p udp -s fe80::/10 --sport 547 -d fe80::/10 --dport 546
 -j ACCEPT
-A ufw6-before-input -p udp -s fe80::/10 -d fe80::/10 --dport 546 -j ACCEPT

-A ufw6-before-input -p icmpv6 -s fe80::/10 --icmpv6-type 130 -j ACCEPT

# accept all from LAN and returned RELATED or ESTABLISHED packets
-A ufw6-before-forward -i eth0 -j ACCEPT
-A ufw6-before-forward -m state --state RELATED,ESTABLISHED -j ACCEPT

ufwに元からあったルールを少し緩めて宛先ポート番号だけで許可するようにしているのは、DHCPv6-PDによるプレフィックスの委譲を受けるためです。

DHCPv6の通信を許可するルールがbefore6.rulesに入っていたので大丈夫だと思ったのですが、最初はDHCPv6-PDによるプレフィックスの委譲を受けることができませんでした。そこで原因を探るために捨てているパケットを見ていたら、ひかり電話ルータのDHCPv6サーバから送られてくるパケットのポート番号が547ではありませんでした。このため送信元のポート番号を無視して宛先のポート番号だけを見るようにしました。

IPv6の転送を許可

IPv4をトンネルするためにIPv4の転送を許可していましたが、加えてIPv6も転送するように設定します。

/etc/ufw/sysctl.conf

# 追加する
net/ipv6/conf/default/forwarding=1
net/ipv6/conf/all/forwarding=1

radvdの設定

radvdを使ってRA送信します。自分のネットワークに接続された端末は、このRAを受信することで自動的にIPv6の設定します。

radvdが配布するRAの設定は/etc/radvd.conf/radvd.confに書きます。

interface eth0 {
  AdvSendAdvert on;
  #MaxRtrAdvInterval 30;
  prefix ::/64 {
    AdvRouterAddr on;
  };
};

ここで指定しているプレフィックス::/64は、マジックワードです。このアドレスを指定すると、指定されたinterfaceの全グローバルアドレスからプレフィックスを決定してRAを送り出してくれます。今回のようにDHCPv6-PDで指定されるアドレスが何になるかわからない場合に便利な指定方法です。

IPv6での通信確認

以上でルータの設定は完了です。ここで再起動して自分のネットワーク内の端末からIPv6で通信できるか試してみます。

確認方法とトラブルシューティングは、IPv6とIPv4のデュアルスタックで接続のIPv6での通信確認を参照して下さい。

設定に間違いがないのに通信できない

今回どこにも間違いはないのに外と通信できないことがありました。自分のルータとひかり電話ルータの間に置いた端末とは通信できるのでルータの設定に問題はないはずですが、なぜかひかり電話ルータを越えて外に出られませんでした。

ひかり電話ルータにWebブラウザで接続してDHCPv6-PDによるプレフィックスの払い出し状況を確認するとなぜかRaspberry PiのLAN側のMACアドレスが登録されていました。そこから推測するとひかり電話ルータは外からの通信を接続されていないRasPiのLAN側宛に送ってしまっているのではないかと推測しました。

RaspPiを何度再起動してDHCPv6-PDをしなおしても登録されているMACアドレスは変わりません。

そこで/etc/dhcpcd.duid/etc/dhcpcd.secretを削除して再起動してみました。するとひかり電話ルータに登録されているMACアドレスは変化ありませんでしたが問題なく外と通信できるようになりました。

DS-Liteによるトンネル作成

IPv6に関する設定は以上ですが、interfacesからeth1に関する設定を全て削除してしまったのでIPv4をトンネルするインターフェイスが作成されません。そこでdhcpcdが設定のフェーズ毎に実行する/etc/dhcpcd.enter-hook2にスクリプトを書いておいてトンネルを作成します。スクリプトが実行される時に設定される変数については、dhcpcd-run-hooksのマニュアルを参照して下さい。

/etc/dhcpcd.enter-hook

#!/bin/sh
# http://techlog.iij.ad.jp/contents/dslite-raspi

REMOTE='2404:8e01::feed:101'

WAN_IF='eth1'
TUN_IF='ip6tnl1'

dig_tunnel()
{
    #  already tunnel exist?
    ip addr show | grep $TUN_IF > /dev/null
    if [ $? -eq 0 ] ; then
	return
    fi

    LOCAL=`ip -6 addr show $WAN_IF scope global |grep inet6 | awk '{print $2}'`
    if [ "$LOCAL" = '' ]; then
	return
    else
	logger dig DS-Lite tunnel $WAN_IF $LOCAL === $REMOTE
    fi

    # IPIP6 tunnel linkup
    ip -6 tunnel add $TUN_IF mode ip4ip6 remote $REMOTE local $LOCAL dev $WAN_IF
    ip link set dev $TUN_IF up

    # IPv4 routing
    route delete default
    route add default dev $TUN_IF
}

delete_tunnel()
{
    ip addr show | grep $TUN_IF > /dev/null
    if [ $? -ne 0 ] ; then
	return
    fi

    logger delete DS-Lite tunnel
    # delete DS-Lite tunnel
    ip -6 tunnel delete $TUN_IF

    # delete default route
    route delete default
}


if [ "$interface" = $WAN_IF ] ; then
    if $if_up ; then
	case "$reason" in
	    BOUND6|REBIND6)
		dig_tunnel
		;;
	esac
    else
	if [ "$reason" = EXPIRE6 ] ; then
	    delete_tunnel
	fi
    fi
fi

参照と脚注

  1. 今回使ったdhcpd以外にもIPv6用のDHCPクライアントはいくつかありますが、dibblerWIDE-DHCPv6では上手くプレフィックスの委譲を受けることができませんでした。委譲はしてもらえていたのかも知れませんが、インターフェイスにアドレスが設定されませんでした。自分で設定する必要があったのかも知れません。

  2. dhcpcdは、/etc/dhcpcd.enter-hookだけでなく/etc/dhcpcd.exit-hookも実行します。しかし今回順番は関係ないので、トンネルの作成と破棄の両方共/etc/dhcpcd.enter-hookに書いています。

IPv6とIPv4のデュアルスタックで接続

ZOOT NATIVE (DS-Lite)でネット接続では、DS-Liteを使ってIPv4通信できることを確認しました。今回は、せっかくZOOT NATIVEがIPv6のIPoEなので、IPv4だけでなくIPv6でも通信できるようにします。

フレッツ光のひかり電話契約なしの場合は/64のプレフィックスしかもらえなので、自分のルータの下に自分のネットワークを置くにはこの方法しかありません。

ひかり電話契約ありの場合は、DHCPv6-PDによりもっと普通に自分のネットワークを置けます。このDHCPv6-PDを使った方法は、DHCPv6-PDでIPv6のサブネットワークを作成を参照して下さい。

Raspbianの設定は、ZOOT NATIVE (DS-Lite)でネット接続での状態になっている前提での説明です。

サイト構成

IPv6は事実上無限の数のIPアドレスを持つので、IPv6に接続したどんな機械でもユニークなIPアドレスを持つことができInternetから接続できます。ということなのですが、ひかり電話契約がない場合は、フレッツ光のひかり電話ルータがRAで配布するプレフィックスは/64となっています。そのためさらに下に別なネットワークを作る事ができません1

そこで今回は、IPv4と同じようにルータでNAPT(マスカレード)してルータ以下のネットワークを隠してIPv6の世界に接続します。このためInternetから自分のネットワーク内の端末へ簡単には接続できません2

自分のネットワーク内では、IPv4のプライベートアドレスに相当するユニークローカルアドレス(ULA)を使用します。今回はfd00::/64を自分のネットワークに使用することにしました。

Raspberry Piのインターフェイスは、次のように使用します。

インターフェイス ネットワーク アドレス
eth0 自分のネットワーク/64 fd00::1/64
eth1 Internet(IPoE) RAで自動設定

自分のネットワーク内の端末は、Raspberry Piが送信するRAで自動的にIPv6アドレスやデフォルトルータが設定されます。

ルータのアドレス指定

自分のネットワークへ入るルートを登録するため、eth0にIPv6のアドレスを割り当てます。

/etc/network/interfaces

# 追加する
iface eth0 inet6 static
 address fd00::1
 netmask 64

radvdをインストール

IPv4のDHCPサーバと同じように、IPv6ではルータ広告(RA)によって端末をネットワークに接続しただけで自動的にアドレスやデフォルトルートが設定されます。このためのサーバソフトがradvdです。

$ sudo apt-get install radvd

radvdが配布するRAの設定は/etc/radvd.conf/radvd.confに書きます。

interface eth0 {
  AdvSendAdvert on;
  #MaxRtrAdvInterval 30;
  #prefix ::/64 { でもOK?
  prefix fd00::/64 {
    AdvRouterAddr on;
  };
};

必要であればDNSの情報をRAで配布することも可能3ですが、今回はIPv4のDHCPに依存することにしました。

どんな内容のRAを送っているか(受信しているか)は、ルータ上で次のコマンドを実行することで確認できます。

$ sudo radvdump

ufwの設定

前回はIPv4のみだったのでDEFAULT_FORWARD_POLICYをACCEPTとして何でも転送を許可していましたが、今回は少し安全にするため通過できるパケットを制限します。制限するポリシーは次のとおりです。

  • 自分のネットワークから外へは無制限に出ていける。
  • 自分のネットワークへは、出て行ったパケットに関連するパケットのみが入れる。

そこで/etc/default/ufwを次のように変更し、許可されたパケット以外は全て破棄するようにします。

DEFAULT_FORWARD_POLICY="DROP"
DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"

許可するパケットのルールは、ufwの設定ファイルに追加します。

/etc/ufw/before.rules

# 以下を追加する。
# accept all from LAN and returned RELATED or ESTABLISHED packets
-A ufw6-before-forward -i eth0 -j ACCEPT
-A ufw6-before-forward -m state --state RELATED,ESTABLISHED -j ACCEPT

/etc/ufw/before6.rules

# 以下を追加する。
# accept from LAN
-A ufw6-before-input -i eth0 -j ACCEPT

# accept all from LAN and returned RELATED or ESTABLISHED packets
-A ufw6-before-forward -i eth0 -j ACCEPT
-A ufw6-before-forward -m state --state RELATED,ESTABLISHED -j ACCEPT

さらにIPv6のパケットをNAPT(マスカレード)するための設定を/etc/ufw/before6.rulesに追加します。追加は、ファイルの最初または最後、要するに*filterとCOMMITに挟まれていない場所にします。

/etc/ufw/before6.rules

*nat
:POSTROUTING ACCEPT [0:0]

# Forward traffic through eth1
-A POSTROUTING -s fd00::/64 -o eth1 -j MASQUERADE

COMMIT

IPv6の転送を許可

IPv4だけでなく、IPv6も転送するように設定します。

/etc/ufw/sysctl.conf

# 追加する
net/ipv6/conf/default/forwarding=1
net/ipv6/conf/all/forwarding=1

net/ipv6/conf/eth1/accept_ra=2
net/ipv6/conf/eth1/accept_ra_pinfo=1
net/ipv6/conf/eth1/autoconf=1

forwardingを有効にすると上流ルータからのRAを無視してしまいます。そこで外につながっているeth1はaccept_raなどを有効にして、RAを受信して自動的にアドレスを設定するようにしています。

IPv6はインターフェイスのMACアドレスからアドレスが決定されます。そのためこのまま外のサイトに接続するとIPv6アドレスからどのメーカのネットワークカードまたはアダプタを使っているがバレてしまいます。これが嫌な時は、プライバシー機能を有効にして隠すことが可能です。

IPv6での通信確認

IPv6で通信するための設定は以上で終わりです。一度再起動して設定を反映させます。

IPv6とIPv4の両方で通信できているかは、次のサイトへWebブラウザでアクセすることで確認できます。

IPv6 test – IPv6/4 connectivity and speed test
IPv4とIPv6の両方で接続できるか調べられる。
あなたの IPv6 をテストしましょう。
IPv6で通信できているかを確認できる。
IIJmio IPv6スピードテスト(β)
IPv6での通信速度を測定できる。
The KAME project
IPv6で接続すると亀が動きます。

トラブルシューティング

接続できない時は、まずIPv6アドレスが割り振られているか確認します。Windowsでは、ipconfigコマンドを使用します。

$ ip -6 addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 2409::xxx:xxxx:xxx:xxx/64 scope global temporary dynamic 
       valid_lft 86165sec preferred_lft 14165sec
    inet6 2409::xxx:xxxx:xxx:xxx/64 scope global mngtmpaddr noprefixroute dynamic 
       valid_lft 86165sec preferred_lft 14165sec
    inet6 fe80::xxx:xxxx:xxx:xxx/64 scope link 
       valid_lft forever preferred_lft forever

IPv6アドレスが割り当てられていることが確認できたら、デフォルトルートが登録されていることを確認します。

$ netstat -rn6
カーネルIPv6 経路テーブル
Destination                    Next Hop                   Flag Met Ref Use If
2409:xxx:xxxx:xxx::/64        ::                         Ue   256 2     7 enp0s25
fe80::/64                      ::                         U    256 0     0 enp0s25
::/0                           fe80::xxx:xxx:xxx:xxx  UG   100 1     1 enp0s25
# 省略

ここで::/0で始まる行がデフォルルートで、送り先がわからない時はパケットをNext Hopのfe80::xxx:xxx:xxx:xxxに送ってどうにかしてもらいます。このアドレスがルータのものか確認します。

参照と脚注

ZOOT NATIVE (DS-LITE)でネット接続

  1. さらに下にネットワークを作らないでも、ひかり電話ルータに直接接続するだけでIPv6で通信することが可能です。ただこれだとパケットの制限をこのルータに依存するのが嫌だな…と。

  2. NAPTでも外から自分のネットワーク内の端末に接続で可能です。その時は、IPv4の時と同じように特定ポートへの通信を特定の端末に転送させるように設定します。これで外部にサーバを公開する事が可能です。

  3. RAでDNSの情報を配布する場合は、RDNSSとDNSSLの設定をradvd.confに追加します。

ZOOT NATIVE (DS-Lite)でネット接続

インターリンク(Interlink)がZOOT NATIVEというIPv6 IPoEサービスを開始しました。この方式にするとIPv4のPPPoEよりも速くなる(遅くならない)という話なので試してみました。

ZOOT NATIVEは基本的にIPv6のサービスですが、今回はトンネルを作成してIPv4でInternetに接続してみます。

IPv6で通信するための設定方法は、IPv6とIPv4のデュアルスタックで接続にあります。この方法は、ひかり電話契約のないユーザにオススメの方法です。ひかり電話契約がある場合は、DHCPv6-PDでIPv6のサブネットワークを作成のように素直にネットワークを接続する方法もあります。

環境

  • フレット光ネクスト ファミリー・ハイスピードタイプ(NTT西日本)
  • ひかり電話オプション契約あり
  • Raspberry Pi B+ Raspbian(Debian jessei)

Raspberry Piの内蔵Ethernet(eth0)にLANを接続し、USB 2.0接続の100BASE-TX Ethernetアダプター(eth1)にフレッツ光ネクストのルータ(PPPoEブリッジ機能を有効)を接続しています。

パソコンのUbuntu 16.04でも、ここで紹介した方法で接続できました。

ZOOT NATIVEの申し込み

申し込みは、インターリンクのWebページから簡単に行えました。ただし事前に次のものを準備しておく必要があります。

  • クレジットカード
  • フレッツ光のお客様IDとアクセスキー1

4月5日の朝に申し込んで、翌4月6日の朝に使用できるようになったというメールが届きました。

設定方法

Raspberry Piを通信の中継だけでなく一般的な市販ルータが持つDNS機能とアドレスの自動割当機能を持たせます。そのためIPv6上でIPv4を使うDS-Liteトンネルの作成以外に、DNSやDHCPサーバ用の設定が入っています。

DNS(unbound)をインストール

まず最初にLANのDNS(キャッシュ)となるunboundをインストールします。ISPのDNSを使用できるならば、このステップは不要です。

$ sudo apt-get install unbound

unboundは、/etc/unbound/unbound.conf.d/にある*.confを起動時に設定ファイルとして読み込みます。そこでunbound-local.confというファイルを作成して設定を書き込みます。

/etc/unbound/unbound.conf.d/unbound-local.conf

server:
    #interface: 0.0.0.0
    interface: 127.0.0.1
    interface: 10.0.0.254
    access-control: 10.0.0.0/24 allow

IPv6のアドレスにはbindしないで、LANと自分自身からの問い合わせにのみ答えるようにしました。

ルータのアドレスを固定

ルータのIPv4アドレスは、LAN内のデフォルトルートとなるので固定されている必要があります。そこでDHCPクライアントを停止して、LANに接続しているeth0のIPv4アドレスを指定します。

まずはDHCPクライアントを停止します2

$ sudo systemctl stop dhcpcd.service
$ sudo systemctl disable dhcpcd.service

固定アドレスは、/etc/network/interfacesに設定ます。アドレスと同時にルータ自身が参照するDNS(自分自身)を指定します。

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
 address 10.0.0.254
 netmask 255.255.255.0
 dns-nameservers 127.0.0.1
 dns-search example.jp

これで再起動してもアドレスは変わらず、常に同じIPv4アドレスを使うようになります。

トンネルの出口確認

IPv4で通信できるようにするDS-Liteトンネルの設定には、トンネルの出口に当たるゲートウェイ(AFTR3)のIPv6アドレスが必要です。

AFTRのIPv6アドレスは、NVR510の設定マニュアルに書かれていました。ただしこのアドレスは固定ではなく予告なく変更されることがあるそうなので、接続できなくなった場合は設定を変更する必要があります4

私はNTT西日本を使っているので、AFTRとして2404:8e01::feed:100または2404:8e01::feed:101を使用します。

実際にAFTRと通信できるかping6コマンドで確認しておきます。

$ ping6 -c 2 2404:8e01::feed:100
PING 2404:8e01::feed:100(2404:8e01::feed:100) 56 data bytes
64 bytes from 2404:8e01::feed:100: icmp_seq=1 ttl=58 time=5.26 ms
64 bytes from 2404:8e01::feed:100: icmp_seq=2 ttl=58 time=4.97 ms

--- 2404:8e01::feed:100 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 4.978/5.120/5.262/0.142 ms

ufwをインストール

ルータなので不要な通信を制限します。そのため通信を制限するufwをインストールします。ufwを使用しない場合は、IPv4をインターフェイス間で転送するように/etc/sysctl.confを編集してIPv4をルーティングするようにします。

$ sudo apt-get install ufw

/etc/ufw/ufw.conf

ENABLED=yes

# allow ssh connection
# これがないとRaspberry PiにLANからsloginできなくなる。
ufw allow 22/tcp

IPv4をルーティングするように設定します。また必要ならば不要な通信を制限する設定を追加します。

/etc/ufw/sysctl.conf

net/ipv4/ip_forward=1

/etc/default/ufw

DEFAULT_FORWARD_POLICY="ACCEPT"
DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"

転送ルーティングのポリシーをACCEPTにすることは通常危険ですが、トンネル出口でアドレス変換が行われていて外から入ってくる通信はないので良いことにしました5

ufwの自分宛ルーティングポリシーはDROPとなっています。そこで/etc/ufw/before.rulesを編集して、LANからDNS(port 53)への通信を許可する次のルールを最後のCOMMITの前に追加します。

# 以下を追加する。
# Accpet for DNS from LAN
-A ufw-before-input -i eth0 -p udp --dport 53 -j ACCEPT
-A ufw-before-input -i eth0 -p tcp --dport 53 -j ACCEPT

ufwの設定がすんだら、DS-LiteでIPv4をトンネルするために必要なモジュールを起動時に読み込むように設定ます。

/etc/modules

ip6_tunnel

ここまできたら、一旦再起動して設定を反映させておきます。

DS-Liteトンネルを作成

DS-Liteトンネルは、DS-Lite(RFC6333)対応ルータをLinux (Raspberry Pi) で作るのシェルスクリプトを使って作成します。

/etc/network/dig-tunnel.sh

#!/bin/bash
# Ref. http://techlog.iij.ad.jp/contents/dslite-raspi

# gw.transix.jp
REMOTE='2404:8e01::feed:100'

#IFACE='eth1'
IFACE=$1

# get my IPv6 address
for i in $(seq 0 9); do
 LOCAL=`ip -6 addr show $IFACE scope global |grep inet6 | awk '{print $2}'`
 if [[ $LOCAL != '' ]]; then
  break
 fi

 sleep 1
done

if [[ $LOCAL == '' ]]; then
 exit
else
 echo $LOCAL
fi

# IPIP6 tunnel linkup
ip -6 tunnel add ip6tnl1 mode ip4ip6 remote $REMOTE local $LOCAL dev $IFACE
ip link set dev ip6tnl1 up

# IPv4 routing
route delete default
route add default dev ip6tnl1

それでは実際にスクリプトを起動してトンネルを掘ってみます。

$ sudo ifconfig up eth1
$ sudo /etc/network/dig-tunnel.sh eth1

トンネルが掘れているか確認します。

$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether b8:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.254/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::xxxx:xxxx:xxxx:xxxx/64 scope link 
       valid_lft forever preferred_lft forever
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1
    link/tunnel6 :: brd ::
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet6 2409:xxx::xxxx/64 scope global mngtmpaddr dynamic 
       valid_lft 14125sec preferred_lft 12325sec
    inet6 fe80::xxxx:xxxx:xxxx:xxxx/64 scope link 
       valid_lft forever preferred_lft forever
5: ip6tnl1@eth1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1452 qdisc noqueue state UNKNOWN group default qlen 1
    link/tunnel6 2409::xxx peer 2404:8e01::feed:100
    inet6 fe80::xxxx:xxxx:xxxx:xxxx/64 scope link 
       valid_lft forever preferred_lft forever

$ netstat -r
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
default         *               0.0.0.0         U         0 0          0 ip6tnl1
10.0.0.0        *               255.255.255.0   U         0 0          0 eth0

$ ping -c 2 www.yahoo.co.jp
PING edge.g.yimg.jp (183.79.248.252) 56(84) bytes of data.
64 bytes from 183.79.248.252: icmp_seq=1 ttl=59 time=4.35 ms
64 bytes from 183.79.248.252: icmp_seq=2 ttl=59 time=4.92 ms

--- edge.g.yimg.jp ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 4.357/4.640/4.924/0.291 ms

トンネルの自動作成

問題なく接続できることを確認したら、起動した時に自動的にトンネルを掘ってルータとして機能するようにします。

トンネル作成は、/etc/network/interfacesに設定ます。

# これまでの設定に追加
auto eth1
iface eth1 inet manual
 pre-up /sbin/ifconfig eth1 up
 post-up /etc/network/dig-tunnel.sh eth1

ここで再起動して自動的にトンネルが掘れているか確認します。

DHCPサーバをインストール

最後にLAN内の端末を自動設定できるようにDHCPサーバをインストールします。

$ sudo apt-get install isc-dhcp-server

/etc/default/isc-dhcp-server

# eth0(LAN)にのみDHCPサーバとして働く。
#INTERFACES=""
INTERFACES="eth0"

/etc/dhcp/dhcpd.conf

#option domain-name "example.org";
#option domain-name-servers ns1.example.org, ns2.example.org;
option domain-name "example.jp";
option domain-name-servers 10.0.0.254;
option routers 10.0.0.254;

subnet 10.0.0.0 netmask 255.255.255.0 {
  pool {
    range 10.0.0.1 10.0.0.10;
  }
}

通信速度を測定

Raspberry Pi B+をルータとしたDS-Lite接続でどのくらいの速度が出るかBNRスピードテストでテストしてみました。

Ras Pi Ubuntu 16.04 IPv4 PPPoE
22.53Mbps(n=3) 91.02Mbps(n=6) 88.54Mbps(n=2)

テストは一番スピードが出ると予想される早朝に測定しました。Ras PiとUbuntu 16.04はZOOT NATIVEで、IPv4 PPPoEはZOOT NEXTの結果です。

Raspberry Pi B+をルータにした場合は、USB 2.0接続のEthernetアダプターが100BASE-TXのためかCPUの性能が律速なのか、DS-Liteトンネルの性能をフルには発揮できていないようです。

フレッツ光ネクストのハイスピードタイプはPPPoEの通信速度に制限がかけられているという話で、DS-Liteにするとその制限がなくなり本来のIPv6による通信速度に近づくという話でした。しかし今回は、DS-Liteによる通信では、期待されたPPPoEを大きく越えるような高速な通信はできませんでした。

DS-LiteトンネルとPPPoEであまり差がなかった原因は、ルータにしているパソコンの性能が律速になっている可能性はあります。しかしこのパソコンはCore 2 Duoマシンで、速度測定中の負荷を見てもそれほど忙しくしているようには見えません。

ZOOT NATIVEはオススメ?

スピード命で固定のIPv4アドレスが不要ならば、ZOOT NATIVEオススメかな。でも積極的にZOOT NEXTから乗り換えるという程でもないかな。

ゴールデンタイムはZOOT NATIVEの圧勝

残念ながら私の環境ではPPPoEを大きく越える通信速度は見られませんでした。ただしDS-Liteによるトンネルのメリットは、最高速度だけでなくゴールデンタイムでも通信速度が低下しないという点です。

多くの人がネットを使うだろう土曜日の夜にZOOT NEXT(PPPoE)とZOOT NATIVE(IPoE + DS-Lite)で通信速度を測定してみました。なおルータにはRaspberry Piではなく、Ubuntu 16.04マシンを使用しています。

回線 平日の早朝 土曜日の夜
ZOOT NEXT 98.6Mbps(n=3) 3.37Mbps(n=8)
ZOOT NATIVE 83.4Mbps(n=3) 86.8Mbps(n=8)

このようにPPPoEであるZOOT NEXTに見られた夜間の速度低下が全く見られませんでした。信じられなくて、NETXとNATIVEを交互に8回も測定してしまいました。ただし速度低下が見られなかったのは、まだサービス開始から時間が経っていないのユーザが少ないからという可能性があります。

ZOOT NEXTのままで十分

ZOOT NATIVEの欠点は、IPv4の固定アドレスオプションが無いことです。また接続料金が倍になります。

ZOOT NEXT ZOOT NATIVE
1,080円 2,160円

現在の通信速度に問題があるならば、料金が倍になったとしてもこれよりも高い設定のISPは普通にあるので乗り換える価値は非常にあります。しかし夜間に通信速度が低下するとは言っても、Huluなどで動画を見るのに全く支障がありません6。今回測定してみて初めてこんなに速度が低下するのかと気がついたくらいです。

そうすると普通に使える速度なら満足でIPv6の通信が不要なうちは、積極的にZOOT NATIVEに切り替える理由は見当たらない気がします。ZOOT NATIVEは常にフルスピードを求めるユーザ向けということでしょうか。せっかく2ヶ月の無料体験期間を設けてくれているのにごめんなさい。

参照と脚注

  1. フレッツ光のお客様IDとアクセスキーは、フレッツ光を申し込んだ時の書類に記載されていました。

  2. 単純に固定アドレスを使いたいだけの場合は、DHCPクライアントを停止する必要はありません。/etc/dhcpcd.confに固定アドレスを使用するための設定を加えるだけで十分です。今回は、トンネルを掘るシェルスクリプトを起動したかったのでこのようにしました。

  3. AFTRは、Address Family Transition Routerの略。

  4. AFTRのIPv6のアドレスが変わった時、IPv6での通信だけで新しいAFTRを探せるのかな?

  5. 外から入ってくる通信は、全て出て行った通信への返信なのでルータを通過させて問題はない。

  6. ZOOT NEXTとAsahiNetは問題ありませんが、WakWakを使っていた時は非常に通信速度が落ちて動画配信サービスをまともには使えませんでした。

MPD (Music Protocol Daemon)のインストール

MPDをUbuntuマシンにインストールして、Bluetoothスピーカーで聴く設定方法です。

rtmpdumpで録音した番組を聴く方法を色々試してみましたが、MPDはファイルの同期など必要なく録画したテレビ番組を見るようにただリモンの再生ボタンを押すだけという手軽さが気に入りました。

ここではMPDのインストール方法と、ちょっとハマったBluetoothスピーカで聴くための設定方法を書きます。

MPDとは

MPD (Music Protocol Daemon)は、コンピュータに貯めてある音楽ファイルを再生させるためのプログラムです。

コンピュータに貯めた音楽ファイルを再生する方法は次の3パターンがあります。

  • 手元のスマホやパソコンにコピー(同期)して再生する。
  • 音楽ファイルを配信して手元で再生する。
  • 音楽ファイルを貯めているコンピュータで直接再生する。

MPDは、最後の音楽ファイルを貯めているコンピュータで再生するためのプログラムです。そのため基本的にそのコンピュータに接続されたスピーカーからしか音を出せません1。移動しながら聴くというよりは、家や職場など決まった場所で聴くのに向いています。逆に言うと移動しないためデジタル信号を音に変換するDACやスピーカーの大きさに制限がなく音に凝ることが可能です。

MPDのインストール

今回のパソコン環境は、次のようにUbuntu 16.10(デスクトップ版)です。

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 16.10
Release:	16.10
Codename:	yakkety

MPDに必要なプログラムは全てパッケージ化されているので、インストールに難しい所は全くありません。

$ sudo apt install mpd avahi-daemon

avahi-daemonは、zeroconfのために必要です。zeroconfを使用しない場合は、インストールする必要はありません。

MPDの設定

MPDの設定ファイルは、/etc/mpd.confです。このファイルを編集して自分の環境に合わせます。

私は次のようにして、Musicフォルダをパスワード無しで直接公開し、内蔵スピーカとBluetoothスピーカーで聞けるようにしました。またZeroconfを有効にしているので、MPDが動いているコンピュータはDHCPで動的にIPアドレスが割り振られていても問題ありません。

# MPDが音楽ファイルを探すディレクトリー設定
music_directory		"/home/user/Music"

playlist_directory	"/var/lib/mpd/playlists"
db_file			"/var/lib/mpd/tag_cache"
log_file		"/var/log/mpd/mpd.log"
pid_file		"/run/mpd/pid"
state_file		"/var/lib/mpd/state"
sticker_file            "/var/lib/mpd/sticker.sql"

user			"mpd"

# MPDがlistenするアドレス。anyだと全てのインターフェイスでlistenする。
bind_to_address		"any"

# 音楽ファイルが追加や削除されると自動的にMPDに反映されるようにする。
auto_update    		"yes"

# Zeroconfを有効にする。
zeroconf_enabled	"yes"
zeroconf_name		"Music Player T400"

input {
        plugin "curl"
}

# 内蔵スピーカ
audio_output {
	type		"alsa"
	name		"Internal Speaker"
	mixer_type      "disable"
}

# USB接続のスピーカ
# deviceは、aplay -lコマンドを使って調べる。
audio_output {                                                                  
        type            "alsa"                                                  
        name            "USB Speaker"                                           
        device          "hw:1,0"
}

# sinkの値は、pactlコマンドを使って調べます。
# http://aki.sunnyday.jp/blog/?p=1310
# この設定については後で詳しく説明します。
# PluseAudioの設定を修正しないとBluetoothスピーカからは音が出ません。
audio_output {
	type		"pulse"
	name		"Bluetooth Speaker"
	server		"localhost"
	sink		"bluez_sink.00_1A_7D_E2_36_7B"
}

# ncmpcppでビジュアライゼーションを有効にしたい時だけ。
# https://wiki.archlinuxjp.org/index.php/Ncmpcpp                                
#audio_output {                                                                 
#    type                    "fifo"                                             
#    name                    "my_fifo"                                          
#    path                    "/tmp/mpd.fifo"                                    
#    format                  "44100:16:2"                                       
#}                                                                              

filesystem_charset	"UTF-8"
id3v1_encoding		"UTF-8"

MPDの設定はこれだけです。

設定がすんだら再起動して設定を反映させます。

$ sudo systemctl restart mpd.service
$ sudo systemctl status mpd.service
● mpd.service - Music Player Daemon
   Loaded: loaded (/lib/systemd/system/mpd.service; enabled; vendor preset: enab
   Active: active (running) since 木 2017-02-23 09:23:34 JST; 8s ago
     Docs: man:mpd(1)
           man:mpd.conf(5)
           file:///usr/share/doc/mpd/user-manual.html
 Main PID: 32426 (mpd)
    Tasks: 6 (limit: 4915)
   CGroup: /system.slice/mpd.service
           └─32426 /usr/bin/mpd --no-daemon

 2月 23 09:23:34 T400 systemd[1]: Started Music Player Daemon.
 2月 23 09:23:35 T400 mpd[32426]: Feb 23 09:23 : server_socket: bind to '0.0.0.0

MPDのテストと音楽ファイルを再生

Zeroconfの確認

まず初めにzeroconfiが有効になっているか確認します。私はAndroidのZeroConf Browserを使用しました。

MPDが見つからない時は、avahiが動いているかとMPDの設定でzeroconfを有効にしているか確認します。avahiが動いているかは、netstatコマンドを使用します。

$ netstat -ln46|grep 5353
udp        0      0 0.0.0.0:5353            0.0.0.0:*
udp        0      0 0.0.0.0:5353            0.0.0.0:*
udp6       0      0 :::5353                 :::*

zeroconfでMPDを見つけるには、avahi-utilsパッケージに含まれるavahi-browseコマンドも使用できます。ただしこのコマンドは、コマンドを実行したコンピュータ上のサービスを表示しません。

$ avahi-browse -t -all
+ enp0s25 IPv6 Music Player on RERUN                         Music Player Daemon  local
+ enp0s25 IPv4 Music Player on RERUN                         Music Player Daemon  local

systemdでの問題

Ubuntuなどsystemdを使用しているマシンでは、次のようなログを残してZeroconfでMPDを見つけられないことがあります。

/var/log/mpd/mpd.log

zeroconf: No global port, disabling zeroconf

この場合は、Creating a home music server using mpdにあるようにmpd.socketを停止します。

sudo systemctl stop mpd.service
sudo systemctl stop mpd.socket
sudo systemctl disable mpd.socket
sudo systemctl start mpd.service

これでZeroconfでMPDを見つけられるようになります。

MPDで音楽を再生

MPDは音楽を再生するサーバなので、クライアントプログラムが必要です。

クライアントプログラムには、次のように色々な種類があります。

CUI
mpc, ncmpc, ncmpcppなど。mcpは純粋にコマンドラインで使用するプログラムなのでシェルプログラムと組み合わせて使用するのに都合が良いです。ncmpcとncmpcppは、ncursesを使用するので、CUIと言ってもGUIのような感じで使用できます。ncmpcとncmpcppの使い方はほとんど同じなので、使い方はNcmpcppを参照して下さい。
GUI
sonata, arioなど。sonataは、初期設定ではアルバム名が表示されませんが、設定で変更できます。それでもarioの方が私は好みでした。
Android
MPDroid, plainMPDCなど。MPDroidが人気みたいですが、私はplainMPDCが気に入りました。ただplainMPDCは結構頻繁にMPDと接続できなることが欠点です。

Bluetoothスピーカ

MPDの音声をBluetoothスピーカに送る設定の部分は合っているはずですが、Bluetoothスピーカから音が出ない、または再生できないといういうエラーになります。ここでだいぶ悩みました。

mpd.confからBluetoothに関わる部分を抜き出すと次のようになっています。

audio_output {
	type		"pulse"
	name		"Bluetooth Speaker"
	server		"localhost"
	sink		"bluez_sink.00_1A_7D_E2_36_7B"
	# sink            "bluez_sink.00_1A_7D_E2_36_7B.a2dp_sink" # On Ubuntu 17.04
}

鍵となるのは、serverとsinkの設定です。serverはPulseAudioの動いているホスト名で、sinkは音声の送り先です。

sinkの値が間違っているのかと思いpactlコマンドを使って調べましたが、間違っていません。

$ sudo pactl list|grep Sink
Sink #0
Sink #37
	Monitor of Sink: alsa_output.pci-0000_00_1b.0.analog-stereo
	Monitor of Sink: n/a
	Monitor of Sink: bluez_sink.00_1A_7D_E2_36_7B
		a2dp_sink: High Fidelity Playback (A2DP Sink) (sinks: 1, sources: 0, priority: 10, available: yes)

そしてはたとひらめきました。

PulseAudioの設定にserverを指定するということはネットワークに関する設定が絡んでいるのでないかと推定しました。そこでググるとTCP サポートと匿名クライアントがヒットしました。内容を要約するとPulseAudioをネットワーク経由で使うには、ネットワークモジュールのロードと設定が必要だということです。

Ubuntu 16.10のデスクトップ版ではPulseAudioの設定は/etc/pulse/default.paにあります。これを確認すると、ネットワークモジュールがロードされないような設定になっています。

そこでPulseAudioの設定ファイルを次のように修正しました。

# ローカルホストからの接続のみをパスワード無しで受け入れる。
load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1

設定を修正したら、PulseAudioを再起動させます。pulseaudioコマンドを使用する方法もあるようですが、sysctlコマンドまたはserviceで再起動する方法が分からなかったのでパソコンごと再起動しました。

再起動したらpacmdコマンドでmodule-native-protocol-tcpがロードされているか確認します。

$ pacmd list-modules
<SNIP>
index: 11
	name: <module-native-protocol-tcp>
	argument: <auth-ip-acl=127.0.0.1>
	used: -1
	load once: no
	properties:
		module.author = "Lennart Poettering"
		module.description = "Native protocol (TCP sockets)"
		module.version = "9.0"
<SNIP>

これでMPDクライアントから再生を指示するとBluetoothスピーカからも音声が流れてきて問題が解決しました。

参照と注釈

  1. MPDは基本的にそのプログラムが走っているコンピュータのスピーカーからしか音を出せませんが、PulseAudioを使うことで別のコンピュータに音声を送って聴くことができます。

「らじる★らじる」をrtmpdumpで録音する

「らじる★らじる」はrtmpdumpコマンドを使用すると簡単に録音できる。rtmpdumpで録音したファイルは、ffmpegコマンドでMP3に変換する。

たまたまNHKのラジオドラマ(オーディオドラマと今は呼ぶらしい)を聞いて、それ以来NHKのラジオドラマを気に入ってしまいました。ラジオドラマは、テレビと違って目を使う必要がないのでご飯を食べながら楽しむのにちょうど良さそうです。

しかしNHKラジオのネット放送「らじる★らじる」radikoと違って放送を後から聞くことができません。そこで「らじる★らじる」を録音して好きな時間に聞けるようにしました。これでNHKのラジオ語学講座も一週間遅れでの配信を待つ必要がなくなります。

「らじる★らじる」の録音

「らじる★らじる」の放送を録音するには、rtmpdumpコマンドを使用します。録音したファイルは、ffmpegコマンドでMP3ファイルに変換します。

インストール

rtmpdumpコマンドのインストールは、Ubuntuでは次のように簡単に導入できます。同時にMP3に変換するffmepgコマンドとID3タグを編集・表示するeyed3コマンドをインストールしておきます。

$ sudo apt install rtmpdump
$ sudo apt install ffmpeg eyed3

録音方法

「らじる★らじる」の放送を録音するrtmpdumpコマンドの肝は指定するオプションです。そこでもう何も考えずにriocampos/らじるらじるをrtmpdumpで録音する(仙台・名古屋・大阪も)を参考にさせてもらいます。

ファイルをMP3に変換

rtmpdumpはMP3よりも高圧縮なMPEG4 Audio(M4A)形式で録音しますが、私の音楽プレイヤがサポートしていないようなのでffmpegコマンドを使って一般的なMP3形式にトランスコードします。必要であればビットレートの指定などもffmpegコマンドのオプションで指定できます。

$ ffmpeg -i nhk.m4a -acodec libmp3lame nhk.mp3

MP3ファイルにID3タグを登録

ID3タグはffmpegコマンドにmetadataオプションを指定することで登録できます。複数のタグを指定する場合は、metadataオプションを必要なだけ追加します。

$ ffmpeg -i nhk.m4a -acodec libmp3lame -metadata "artist"="らじる ★ らじる" nhk.mp3
$ eyeD3 nkh.mp3
nhk.mp3	[ 469.47 KB ]
-------------------------------------------------------------------------------
Time: 01:00	MPEG1, Layer III	[ 64 kb/s @ 48000 Hz - Stereo ]
-------------------------------------------------------------------------------
ID3 v2.4:
title: 		artist: らじる ★ らじる
album: 		year: 
track: 		
Publisher/label: 

たくさんのID3タグがありますが、今回は次のタグ(metadataキー)を使用しました。

metadataのキー 意味
album アルバム名(番組名)
artist アーティスト名(らじるに固定)
title タイトル(放送日)
publisher 出版社名(放送局)
date 出版年(放送年)

「らじる★らじる」を録音するシェルスクリプト

以上のコマンドをまとめて「らじる★らじる」を録音するシェルスクリプトを作成しました。このスクリプトを使えば、次のようにcrontabに設定することでNHKのラジオドラマを自動的に録音してMP3ファイルに変換することができます。

45 22 * * 1,2,3,4,5 rec-radio.sh fm 15m 青春アドベンチャー
0 22 * * 6 rec-radio.sh fm 50m FMシアター
20 19 * * 0 rec-radio.sh r1 30m 新日曜名作座

UbuntuでCapsLockキーをControlキーに

LinuxでCapsLockとControlキーを入れ替えるには一般的にxmodmapコマンドを使用します。しかしUbuntuでCapsLockキーとControlを入れ替えるには、dconfコマンドで簡単に変更できます。

参照したページではUbuntu 14.04LTSが対象と書かれていましたが、Ubuntu 16.04LTSでも有効でした。

dconfでCapsLockキーもControlキーに

今回はControlキーが壊れたようで認識しなくなったので、交換ではなく次のようにしてCapsLockもControlキーとすして使えるようにしました。

$ dconf write /org/gnome/desktop/input-sources/xkb-options "['ctrl:nocaps']"

xmodmapでCapsLockキーもControlキーに

xmodmapコマンドを使用してCapsLockキーもControlキーと使用できるようにするには、例えば設定を記述するファイルとして~/.Xmodmapファイルを作成して、そこに次のように設定を書き込みます。

keycode  66 = Control_L Control_L Control_L Control_L
remove Lock = Control_L
add Control = Control_L

これをxmodmapコマンに読み込ませます。

$ xmodmap ~/.Xmodmap

この方法は、dconfと異なりログアウトすると設定を忘れてしまうので、.bash_profileにxmodmap ~/.Xmodmapを追加しておく必要があります。

参照

UbuntuTips/Desktop/HowToSetCapsLockAsCtrl – ubuntu Japanese Team Wiki

MongoDB 3.2(stable)をUbuntu 16.04にインストール

現在(2016年6月10日)MongoDB 3.xのUbuntu 16.04用のパッケージは、MongoDBから提供されていません。そのためUbuntu 14.04用のパッケージを使うことによる問題で、16.04用のパッケージが公開された時には解決しているはずです。

2016年8月26日:Ubuntu 16.04用のパッケージが提供されています。そのためもうsystemd用の起動設定ファイルを作成する必要はありません。

これまでUbuntu 14.04でMongoDBを運用してきましたが、新しいLTSであるUbuntu 16.04にアップグレードしたらmongodが起動しなくなってしまいました。

ここではUbuntu 16.04にMongoDB 3.2(stable)をインストール(アップグレード)する手順と、使用できるようにするまでのworkaroundを説明します。

MongoDB 3.2のインストール

MongoDBをアップグレードする場合は、データと/etc/mongod.confをバックアップしておきます。

MongoDBのインストール方法は、MongoDBが公開しているInstall MongoDB Community Editionに基づいて行います。

まず改変されていないか確認するための鍵を登録します。この鍵は、以前と変わっているので、アップグレードの場合も必要です。

$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927

次にパッケージのレポジトリを登録します。ここで3.2というバージョン指定をstableに変更すると、MongoDB 3.3が安定版になったらそのままアップグレードされ、常に最新の安定版MongoDBを使用できるようになります。

$ echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list
$ sudo apt update

実際にMongoDBをインストールします。アップグレードの場合は、mongodb-orgだけでなくそのメタパッケージが指すパッケージ群を指定しないとアップグレードできません1

# 新規インストールの場合
$ sudo apt install mongodb-org

# アップグレードの場合
$sudo apt install mongodb-org mongodb-org-mongos mongodb-org-server mongodb-org-shell mongodb-org-tools

本来ならばこれでmongodが起動するはずですが、起動ファイル(systemdの設定)が存在しないので自動では起動しません。たぶんUbuntu 16.04用のパッケージが公開されれば修正されるはずです。

mongodを自動起動させるworkaround

Ubuntu16.04用のパッケージが用意されているので、起動設定ファイルの作成ステップは不要です。Ubuntu 16.04用パッケージでは、起動用設定ファイルが/lib/systemd/system/mongod.serviceに作成されます。

mongodが起動しないのは、インストールしたパッケージがUbuntu 14.04用なのでUbuntu 16.04で自動起動させるために必要なsystemd設定が無いからです2

そこでsystemd用の起動設定ファイルmongodb.serviceRunning mongodb on ubuntu 16.04 LTSを参考にして作成します。

$ cat /etc/systemd/system/mongod.service
[Unit]
Description=MongoDB Database Service
Wants=network.target
After=network.target

[Service]
ExecStart=/usr/bin/mongod --config /etc/mongod.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
User=mongodb
Group=mongodb
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target

mongod.serviceを作成したら、起動するか確認しておきます。

$ sudo systemctl start mongod
# or
$ sudo service mongod start

$ sudo systemctl status mongod
# or
$ sudo service mongod status
● mongod.service - MongoDB Database Service
   Loaded: loaded (/etc/systemd/system/mongod.service; disabled; vendor preset: 
   Active: active (running) since 金 2016-06-10 09:47:06 JST; 7s ago
 Main PID: 1479 (mongod)
   CGroup: /system.slice/mongod.service
           └─1479 /usr/bin/mongod --config /etc/mongod.conf

 6月 10 09:47:06 rerun systemd[1]: Started MongoDB Database Service.

起動することが確認できたら、自動起動するようにしておきます。

$ sudo systemctl enable mongod
Created symlink from /etc/systemd/system/multi-user.target.wants/mongod.service to /etc/systemd/system/mongod.service.

mongoコマンドの警告表を止める方法

mongoコマンドでサーバに接続すると次のような警告メッセージが表示されることがあります。

$ mongo
MongoDB shell version: 3.2.7
connecting to: test
Server has startup warnings: 
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] 
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] 
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] 

この場合は、Disable Transparent Huge Pages (THP)を参考にしてkernelのtransparent huge pages機能を無効にします。

機能を無効にするには、/etc/rc.localにkernelの仮想設定ファイルに設定を書き込む命令を加えます。参照したページのように起動ファイルを作成したら、実行される順番によるのか警告を止めることができませんでした。

具体的には、/etc/rc.localexit 0の前に次の文を加えます。

if [ -d /sys/kernel/mm/transparent_hugepage ]; then
    echo never > /sys/kernel/mm/transparent_hugepage/enabled
    echo never > /sys/kernel/mm/transparent_hugepage/defrag
fi

PHPのmongoライブラリ

Ubuntu 16.04では、PHP7が標準になりました。そのためmongodbライブラリをpeclコマンドでインストールすると、次のようなエラーが出てインストールできません。

$ sudo pecl install mongo
WARNING: "pecl/mongo" is deprecated in favor of "channel:///mongodb"
pecl/mongo requires PHP (version >= 5.3.0, version <= 5.99.99), installed version is 7.0.4-7ubuntu2.1
No valid packages found
install failed

そこで素直にパッケージに入っているmongoライブラリをインストールします。

$ sudo apt install php-mongo

脚注

  1. メタパッケージをアップグレードすれば、自動的にメタパッケージでインストールパッケージ群もアップグレードされるのだとずっと考えていました。

  2. Ubuntu 16.04ではsystemdがデーモンを起動させますが、Ubuntu 14.04ではUpstartが起動させていました。そのためUbunt 14.04用のパッケージにはsystemdで必要な設定ファイルが含まれていません。
    systemdに置き換えられたのは15.04からなので、Ubuntu 15.04と15.10でも同じ問題が発生します。

Raspbian(jessi)でのIPアドレスの固定とresolv.conf設定

Debian(Raspbian)のjessieでIPアドレスを固定する方法とresolv.confファイルの設定方法

Debian(Raspbian)では、jessieからネットワーク周りの設定方法が変わりました。そのためDHCPではなくIPアドレスを固定して使用する方法と、関連してresolv.confファイルの設定でこれまでの経験が使えなくなってしまいました。

ここではDebian(Raspbian) jessieでIPアドレスを固定する方法と、resolv.confファイルの設定する方法をメモしておきます。

設定はdhcpcd.confで

Debian(Raspbian) jessieでは、ネットワーク周りの設定は/etc/dhcpcd.confファイルで行います。/etc/network/interfaces/etc/resolv.confファイルを編集してもdhcpcdにより上書きされてしまいます。

IPアドレスを固定する方法

DHCPサーバからIPアドレスをもらって設定するのではなく、指定したIPアドレスを使いたい場合は、dhcpcd.confに次の設定を加えます。

interface eth0
static ip_address=10.0.0.130/24
static routers=10.0.0.254

この設定によりeth0に10.0.0.130(netmask 10.0.0.255)というIPアドレスが設定されます。またstatic routersにより、デフォルトルータに10.0.0.254が指定されます。

$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether b8:27:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.130/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::XXXX:XXX:XXXX:XXXX/64 scope link 
       valid_lft forever preferred_lft forever

$ netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         10.0.0.254      0.0.0.0         UG        0 0          0 eth0
10.0.0.0        0.0.0.0         255.255.255.0   U         0 0          0 eth0

resolv.confの設定

上の設定を完了すればIPアドレスを指定して通信することが可能です。しかしホスト名を指定して通信するには、DNSに問い合わせてホスト名をIPアドレスに変換できなければなりません。そのための設定ファイルがresolv.confです。

resolv.confファイルにDNSのアドレスや自分のドメイン名を設定するには、dhcpcd.confに次の設定を加えます。

static domain_name_servers=10.0.0.123 10.0.0.234
static domain_name=nosuz.jp
#static domain_search=sample.jp

この設定により名前解決する時には10.0.0.123と10.0.0.234のDNSに問い合わせるようになります。またホストを指定する時にドメイン名まで指定する必要がなくなり、hostnameを指定するとdomain_nameで指定したドメイン名を付けてDNSに問い合わせるようになります。

domain_searchを設定すると、domain_nameに指定したホスト名が見つからない場合には、domain_searchで指定した別のドメイン名にないか調べるようになります。

WordPress REST APIを使った投稿と更新

WordPressのREST API(v2)を使用して記事の投稿と更新をしてみました。どうやってOAuth POSTをPHPからするか少し悩みましたが、分かってみると簡単にWordPressのREST APIを使用出来ました。

セットアップ

WordPress REST APIを使うには、REST APIのプラグインと認証プラグインをインストールして有効にしておく必要があります。

REST APIプラグイン

REST API(v2)プラグインは、通常のプラグインと同様にWordPressのダッシュボードからプラグインを検索してインストールします。

Application Passwordを使用する場合

Application Passwordは、ネット上を生のパスワードが流れます。 ローカル環境で使用するときには簡単で便利ですが、本番環境では必ず通信経路が暗号化されたHTTPS(SSL/TSL)で接続する必要があります。

Application Passwordを使用する場合は、OAuthプラグインと同様にApplication Passwordプラグインのページからアーカイブをダウンロードしてインストールします。

プラグインを有効にしたら、WordPressのダッシュボードから「ユーザー > あなたのプロフィール」と進んで、ページの最後の方にある「Application Passwords」でパスワードを自動生成します。このパスワードは再表示と修正はできないので、表示されたら安全な所に記録しておきます。修正や再確認が必要なときは、「Revoke」ボタンで現在のパスワードを無効にして再生成することになります。

Oauthを使用する場合

OAuthを使用する場合は、OAuthプラグインのGitHubページからアーカイブを取得して、OAuthプラグインをインストールします。

プラグインのインストールして有効にしたら、OAuthキーを取得しておきます。OAuthキーの取得方法は、WORDPRESS REST APIのOAUTHトークンを取得を参照して下さい。

OAuthライブラリ

プログラムからOAuth認証を使用するにはライブラリーが必要です。

言語 ライブラリ名
Ruby Oauth
PHP HTTP_OAuth
Pyhton Requests-OAuthlib

記事の投稿と更新サンプル

Application Passwordでのサンプル

Application Passwordは普通のBASIC認証と同じなので、curlコマンドにユーザ名1とアプリケーションパスワードを引数として渡すことで簡単にAPIを叩くことができます。

#!/bin/bash

read -r -d '' json << _EOM_
  {
    "title": "Post new draft with cat 2",
    "content": "TEST<br/>TEST",
    "categories": [2],
    "status": "draft"
  }
_EOM_

curl -X POST \
     -u "user:applicatopn_password" \
     -H "Content-Type: application/json" \
     -d "$json" \
     https://MY_WORDPRESS_SITE/wp-json/wp/v2/posts

-uオプションを指定しないでも、エンコード済みのAuthorizationヘッダー2を加えてもAPIにアクセスできます。

curl -X POST \
     -H "Authorization: Basic dXNlcjphcHBsaWNhdG9wbl9wYXNzd29yZA==" \
     -H "Content-Type: application/json" \
     -d "$json" \
     https://books.nosuz.jp/wp-json/wp/v2/posts

BASIC認証方法は、パスワードをBase64でエンコードしているだけなので簡単にデコードして読み取ることができます。

$ echo dXNlcjphcHBsaWNhdG9wbl9wYXNzd29yZA==|base64 -d
user:applicatopn_password

OAuthでのサンプル

OAuthのサンプルは、PHPとそのOAuthライブラリHTTP_OAuthを使用しました。ライブラリのインストール方法は、WORDPRESS REST APIのOAUTHトークンを取得を参照して下さい。

PHPからOAuth認証でPOSTする時に、HTTP_OAuthライブラリにデータを指定する方法が分からず少し困りました。しかしHTTP_Request2のインスタンスにデータをセットし、それをHTTP_OAuthに渡すことでOAuth認証でPOSTできました。

require_once('HTTP/OAuth/Consumer.php');

$oauth = new HTTP_OAuth_Consumer(
    $oauth_secret->client_key,
    $oauth_secret->client_secret,
    $oauth_secret->access_key,
    $oauth_secret->access_secret
);

# POST OAuth
$http_request = new HTTP_Request2();
$http_request->setHeader("Content-Type: application/json");
$http_request->setBody(json_encode($article));

$consumer_request = new HTTP_OAuth_Consumer_Request();
$consumer_request->accept($http_request);
$oauth->accept($consumer_request);
$result = $oauth->sendRequest($post_endpoint)->getResponse();

これを使ってWordPressに記事を投稿と更新するプログラムを書いてみました。このプログラムは、HTTP_OAuthライブラリの他にテンプレートエンジンのTwigを使用しています。

phpのビルトインサーバを起動させて

php -S localhost:8080

http://localhost:8080/wp-post.phpにアクセスします。最初にアクセスした時には新規投稿となり、結果には記事のIDが埋め込まれているので次からは更新となります。

参照と脚注

  1. Application PasswordによるBASIC認証でのユーザ名は、WordPressのユーザ名でありApplication Passwordを登録した時のアプリケーション名ではありません。これを勘違いしていて私はだいぶ時間をロスしました。

  2. Authorizationヘッダーは、ユーザ名とパスワードを:でつないでBase64でエンコードしただけです。

    $ echo -n 'user:applicatopn_password' | base64
    dXNlcjphcHBsaWNhdG9wbl9wYXNzd29yZA==