RTX 810 と Openswan で site-to-site VPN

自宅から 10.0.0.3 とかで VPS にあるサーバにアクセスしたいし、逆に VPS から 192.168.10.8 とかで自宅のサーバにアクセスしたい。

環境

  • 自宅 (RTX 810)
    • グローバルIP 1.1.1.1
    • プライベートIP 192.168.10.1
    • サブネット 192.168.10.0/24
  • VPS (Openswan)
    • グローバルIP 2.2.2.2
    • プライベートIP 10.0.0.2
    • サブネット 10.0.0.0/16
  • pre-shared-key HOGEFUGA

自宅側の設定

Web UI から VPN 接続の設定→IPsecを使用したネットワーク型 LAN間接続VPN からだいたい設定できる。 最終的に以下のような設定になった。

...
ip route 10.0.0.0/16 gateway tunnel 1
...
tunnel select 1
 tunnel name vps
 ipsec tunnel 1
  ipsec sa policy 1 1 esp aes-cbc sha-hmac
  ipsec ike group 1 modp1024
  ipsec ike hash 1 sha
  ipsec ike keepalive use 1 off
  ipsec ike local address 1 192.168.10.1
  ipsec ike pre-shared-key 1 *
  ipsec ike remote address 1 1.1.1.1
  ipsec auto refresh 1 off
 ip tunnel tcp mss limit auto
 tunnel enable 1
...
ip filter 200085 pass * 192.168.10.1 udp * 500
ip filter 200086 pass * 192.168.10.1 esp * *
...

VPS 側の設定

Openswan

/etc/ipsec.conf

version 2.0

config setup
    dumpdir=/var/run/pluto/
    virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v6:fd00::/8,%v6:fe80::/10
    oe=off
    protostack=auto

include /etc/ipsec.d/*.conf

/etc/ipsec.secrets

include /etc/ipsec.d/*.secrets

/etc/ipsec.d/rtx810.conf

conn rtx810
    auto=start
    type=tunnel
    authby=secret
    keyexchange=ike
    ike=aes128-sha1;modp1024
    phase2=esp
    phase2alg=aes128-sha1;modp1024
    pfs=no

    left=1.1.1.1
    leftid=1.1.1.1
    leftsourceip=10.0.0.200
    leftsubnet=10.0.0.2/16

    right=2.2.2.2
    rightid=192.168.10.1
    rightsubnet=192.168.10.1/24

/etc/ipsec.d/rtx810.secrets

1.1.1.1 192.168.10.1: PSK "HOGEFUGA"

/etc/sysctl.d/50-openswan.conf

net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.ip_forward = 1

これで ipsec verify に FAIL がなくなって起動するはず。

iptables

esp と udp 500 を開けておく。 あとは他のサーバから Openswan がいるサーバを通って通信させるために forward も許可しておく。

iptables -A INPUT -p udp -m multiport --dports 500 -j ACCEPT
iptables -A INPUT -p esp -j ACCEPT
iptables -A FORWARD -s 10.0.0.0/16 -d 192.168.10.0/24 -j ACCEPT
iptables -A FORWARD -s 192.168.10.0/24 -d 10.0.0.0/16 -j ACCEPT

これで VPN 接続を張れるはず。

route

Openswan を立てているサーバ以外から 192.168.10.0/24 にアクセスするときは 10.0.0.2 を通るようにする。 ip route add 192.168.10.0/24 via 10.0.0.2 dev ens4 とすればいいんだけど、この設定を永続化するために systemd-networkd では network ファイルに書いておく。

/etc/systemd/network/ens4.network

...

[Route]
Gateway=10.0.0.2
Destination=192.168.10.0/24

Docker コンテナ内の net.core.somaxconn を変える

--net=host でコンテナを起動すれば、network namespace が分離されないので net.core.somaxconn の値はホスト側と一致する。

% cat /proc/sys/net/core/somaxconn
1024
% docker run --net=host ubuntu:16.04 cat /proc/sys/net/core/somaxconn
1024

けど普通に docker run すると、ホスト側の値にかかわらず、コンテナ内ではデフォルトの128になる。

% cat /proc/sys/net/core/somaxconn
1024
% docker run ubuntu:16.04 cat /proc/sys/net/core/somaxconn
128

で、コンテナ内でこの値を変えようとしても、デフォルトでは許可されていない。

% docker run -it ubuntu:16.04 bash
root@1704e07731c0:/# cat /proc/sys/net/core/somaxconn
128
root@1704e07731c0:/# echo 512 > /proc/sys/net/core/somaxconn
bash: /proc/sys/net/core/somaxconn: Read-only file system

これを回避する方法はいくつかあって、1つは --privileged で実行する方法。

% docker run --privileged -it ubuntu:16.04 bash
root@41d5397d065b:/# echo 512 > /proc/sys/net/core/somaxconn
root@41d5397d065b:/# cat /proc/sys/net/core/somaxconn
512
root@41d5397d065b:/# exit
% cat /proc/sys/net/core/somaxconn
1024

ホスト側とは別の network namespace で値を変えているだけなので、ホスト側の値には影響は無い。 ただ、これだと不要な権限も色々と渡ってしまうのでできれば避けたい。

そこで、必要なものだけ rw で bind mount して、そこに書き込むという方法がある。

% docker run --volume /proc/sys/net/core/somaxconn:/somaxconn -it ubuntu:16.04 bash
root@9bc22f86b0f3:/# cat /somaxconn
128
root@9bc22f86b0f3:/# cat /proc/sys/net/core/somaxconn
128
root@9bc22f86b0f3:/# echo 512 > /somaxconn
root@9bc22f86b0f3:/# cat /somaxconn
512
root@9bc22f86b0f3:/# cat /proc/sys/net/core/somaxconn
512
root@9bc22f86b0f3:/# exit

なお Docker 1.12.0 からは --sysctl というオプションがつくようなので、試してないけど 1.12.0 以降は docker run --sysctl net.core.somaxconn=512 でよさそう。 https://github.com/docker/docker/pull/19265

3種類のドキュメント

ドキュメントが必要というのは誰もが思っていることだと思うけど、わかりやすくて過不足の無いドキュメントを書くのは難しい。 自分が実装したり構築したりするシステムのドキュメントを書くとき、最近は3種類の人に対して別々の文書を用意するように努力している。

ユーザ向け

どうやって使うのか、どうやって接続するのか、各エンドポイントがどんな意味を持っていてどんなリクエストを受け付けるのか、等々。 他のドキュメントと比べてこれが一番対象読者の数が多いので必ず書くようにしているんだけど、自分自身がユーザじゃないことが多くてどの情報が必要なのか正しく判断できず、過不足があることも多いのが悩ましい……

運用者向け

どうやってセットアップするのか、どのサーバで動いているのか、他のどのサービスに依存しているのか、等々。 僕の場合、自分が運用者になることも多く自分自身が対象読者に含まれているので、そのときにどんな情報が欲しいか想像しやすくてこの種類のドキュメントは書きやすい。 僕が書いたドキュメントに対して他の運用者が実際わかりやすいと思ってるのかどうかは知らないけど……

開発者向け

そのシステムを手元で動かすにはどうするのか、どういうポリシーでこの設計になっているのか、等々。 これも自分自身が対象読者に含まれているドキュメントではあるんだけど、運用者向けドキュメントと比べて何を明文化すべきなのかよく分からなくて、あまり書けていない……

ここで挙げた3つは完全に独立しているわけではなくて、例えば用語集とかログの場所とかは全員知っておいたほうがよさそうだし、全体的な構成は運用者も開発者も知っておいたほうがよさそうだったりする。 とはいえこういう区分でドキュメントを分けるのは個人的にはうまくいっていると思っていて、今後もこの3つの分類を意識していこうと思っている。

Linux デスクトップ環境 2016

5年半くらい前に http://d.hatena.ne.jp/eagletmt/20100905/1283686004 というのを書いたけど、そこから今どう変わっているのか。

こう列挙してみると2016年になっても Linux デスクトップは… みたいな気持ちも無いわけじゃないけど、色んなコンポーネントを好きなように設定できたり入れ替えることができたり、場合によってはパッチをあてることもできて、そのへんが好きで使っている。 もちろん、仕事では Linux で動作するようなコードばかり書いたり読んだりしているので、そのへんの知識を手元でも使えたり手元とサーバの違いではまったりしにくいから、というのもあるけど。

ディストリビューション

相変わらずずっと Arch Linux を使っている。 パッケージの更新が早かったり、最小限のデフォルトしか設定されていないのでディストリビューションが勝手に設定しているデフォルトにはまることがなかったり、PKGBUILD がシンプルで独自パッケージを作りやすかったり、ArchWiki が非常に充実していたり、全然不満は無い。

ウィンドウマネージャ

awesomeLua を書くのがつらくなって XMonad を使うようになっている。 最近はほぼ Haskell を書くことが無いので XMonad のために GHC を入れるのは若干だるいけど、XMonad 自体は快適なので使い続けている。

ディスプレイマネージャの類は今も昔も使っていない。普通に getty でログインして startx 叩いている。

ターミナル

mlterm はよくできてるんだけど動作が遅かったりチラつきが多かったりして rxvt-unicode を使ってたんだけど、最近また mlterm を使うようになっている。 でもやっぱり mlterm は遅いので、また rxvt-unicode を使うようになるかもしれない…… rxvt-unicode は異常に速い。

rxvt-unicode だといわゆる East Asian Ambiguous Width の問題があって「▽」とかを半角として認識してレンダリングされて困るんだけど、そのへんは ambiwidth.rbUTF-8-CJK という charmap を作って回避している。 いまググったら同じ方法で回避している人がいた https://github.com/hamano/locale-eaw

ちなみに tmux も同じ問題をかかえていて、しかし tmux は glibcロケールの定義は完全に無視して独自のテーブルを持っているので、wcwidth を使うようにしたパッチをあてて使っている https://github.com/eagletmt/arch.wanko.cc/tree/master/aur-eagletmt/PKGBUILDs/tmux-cjkwidth

と思ったら tmux 本体もついに wcwidth を使うようになったっぽいので、次のリリースからはパッチ不要かも https://github.com/tmux/tmux/commit/26945d7956bf1f160fba72677082e1a9c6968e0c

日本語入力

相変わらず uimuim-skk を使っている。 昔は skk-jisyo.L を直接使っていたけど、今は yaskkserv を使っている。 SKK server completion に対応しているし、あと変換候補が L 辞書に見付からなかったときに Google Japanese Input から探してくれて便利。とくにアニメキャラの名前等の固有名詞。

各種ビューア

画像には今も feh を使っている…… 薄い本をスキャンして電子化していたりするんだけど、そういうのを読むときは mcomix を使っている。

音声や動画には、昔は MPlayer を使っていたけど、その fork の MPlayer2 を経由して、最近はさらに別の fork の mpv を使っている。 MPlayer 系は余計な GUI 要素がなくて、キーボードで完結できるのがよい。

PDF は Evince がよくできているので使っている。これ入れると GNOME 系の一部が依存で入ってくるけど仕方ない… 別途 poppler-data というパッケージも入れないと日本語を表示できない点に注意。 パスワードが設定されている PDF も Evince で普通に扱えるんだけど、まぁだるいので適当にパスワードを外したりしている… https://github.com/eagletmt/misc/tree/master/cxx/pdf-unlock

スクリーンショット、スクリーンキャプチャ

ImageMagick に import というコマンドが含まれているので、それでスクリーンショットは簡単にとれる。

スクリーンキャプチャが欲しいときは FFmpeg を使って ffmpeg -f x11grab で。

Bluetooth

これは他に代替無いだろってかんじだけど BlueZ 5 を使っている。 BlueZ 4 -> 5 と PulseAudio 4 -> 5 の過渡期があってその時期はつらみがあったけど、まぁ今は BlueZ 5 + PulseAudio 8 で問題無く使えている。 そのへんの使い方とか設定はだいたい ArchWiki を見ればわかる。 https://wiki.archlinux.org/index.php/Bluetooth https://wiki.archlinux.org/index.php/Bluetooth_headset

フォント

最近は Web ページで色んなフォントが設定されていたり Unicode の絵文字が普通に使われるようになって、fontconfig でそのへんをちょっと調整しないとつらい時代になった。 基本的にはプロポーショナルフォントには Migu等幅フォントには Ricty を使っている。 これでカバーできていない絵文字やハングル等は Noto Font を使っている。 Linuxレンダリングすると汚ないようなフォントが一部サイトでは font-family に指定されているので、fontconfig で sans-serif とか monospace に書き換えていたりする。 https://github.com/eagletmt/dotfiles/blob/master/dot.config/fontconfig/fonts.conf

こういうのを設定していると「この文字のグリフを含むフォントは何だろ?」というのを知りたくなると思うんだけど、標準の fc- 系のコマンドでそれを達成する方法が分からなかったので、自分で書いたりした https://github.com/eagletmt/misc/tree/master/cxx/fc-find

fontconfig の設定については、例によって ArchWiki が参考になる https://wiki.archlinux.org/index.php/Font_configuration

Docker コンテナを systemd-nspawn で動かす (作業メモ)

ちょっと前までは machinectl pull-dkr というコマンドがあったんだけど、Docker の考えるコンテナと systemd-nspawn の考えるコンテナの差が大きいこともあって消されている。 とはいえ、Docker コンテナも systemd-nspawn (machinectl) で扱うコンテナも本質的に違うものではないので、Docker で作ったコンテナを systemd-nspawn で動かせないこともない。 以下、mysql:5.6 を例に使った作業メモ。

イメージのままだと export できないので、適当に起動してコンテナを作ってから export して、適当なディレクトリに展開しておく。

% docker run --detach mysql:5.6 false
9872d546b6d1f245f25b895ef4c05725d3fdba30d604c11801b000b78ac79d23
% docker export -o mysql56.tar 9872d546b6d1f245f25b895ef4c05725d3fdba30d604c11801b000b78ac79d23
% mkdir mysql56
% tar -C mysql56 -xf mysql56.tar

export すると Docker の ENV とか ENTRYPOINT とか CMD 等の情報が落ちてしまうため、適当に残しておく。

% docker inspect mysql:5.6 | jq --raw-output '.[].Config.Env | map("export \(.)") | join(";")' > mysql56/env.sh
% cat mysql56/env.sh
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin;export MYSQL_MAJOR=5.6;export MYSQL_VERSION=5.6.29-1debian8
% docker inspect mysql:5.6 | jq --raw-output '.[].Config.Entrypoint | join(" ")'
/entrypoint.sh
% docker inspect mysql:5.6 | jq --raw-output '.[].Config.Cmd | join(" ")'
mysqld

systemd-nspawn でコンテナを起動する。

% sudo systemd-nspawn --ephemeral --directory mysql56 --setenv=MYSQL_ROOT_PASSWORD=notasecret --network-veth
Spawning container mysql56-f4c55f3750654603 on /home/eagletmt/work/.#machine.mysql56f1652daa7d449b0c.
Press ^] three times within 1s to kill container.
root@mysql56-f4c55f3750654603:~#

ここで mysqld を起動しようとすると、systemd によって /run に tmpfs がマウントされてしまっていて /run/mysqld が存在せずエラーになってしまうので、適当に回避する。

root@mysql56-f4c55f3750654603:~# mkdir /run/mysqld
root@mysql56-f4c55f3750654603:~# chown mysql:mysql /run/mysqld

ホスト側から接続できるように設定。

root@mysql56-f4c55f3750654603:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    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: host0@if28: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 9e:83:dd:e9:23:c2 brd ff:ff:ff:ff:ff:ff
root@mysql56-f4c55f3750654603:~# ip addr add 10.0.0.10/24 dev host0
root@mysql56-f4c55f3750654603:~# ip link set dev host0 up
root@mysql56-f4c55f3750654603:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    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: host0@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 9e:83:dd:e9:23:c2 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.10/24 scope global host0
       valid_lft forever preferred_lft forever
    inet6 fe80::9c83:ddff:fee9:23c2/64 scope link
       valid_lft forever preferred_lft forever

ようやく mysqld を起動。

root@mysql56-f4c55f3750654603:~# source /env.sh
root@mysql56-f4c55f3750654603:~# /entrypoint.sh mysqld
Initializing database
(snip)

ホスト側から接続確認。

% mysql -uroot -h 10.0.0.10 -pnotasecret -e 'SELECT version()'
+-----------+
| version() |
+-----------+
| 5.6.29    |
+-----------+

PID から Docker の container id を知る方法

ホスト側から見てなんか挙動が怪しいプロセスがいてその PID が分かったときに、どの Docker コンテナで動いているプロセスなのか知りたいことがある。

docker ps -q | xargs -n1 docker top で全ての container id と PID の対応をリストアップして探すことでも達成できるけど、Docker はコンテナを起動するときに cgroup を作ってそのパスに container id を使っているので、/proc/$PID/cgroup を見れば一発で container id がわかることに気付いた。

% cat /proc/29823/cgroup
9:freezer:/docker/e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e
8:cpu,cpuacct:/docker/e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e
7:net_cls:/docker/e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e
6:cpuset:/docker/e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e
5:blkio:/docker/e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e
4:pids:/system.slice/docker.service
3:devices:/docker/e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e
2:memory:/docker/e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e
1:name=systemd:/docker/e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e

こんなかんじになっていれば、PID 29823 が動いている container id は e9e7fa08af0c5478ac379ca587ad2850ffd2a0b72b97b05201d45f3337f4750e だと分かる。

alias_method_chain と prepend を同時に同じメソッドに適用できない問題

Rails 5.0 のリリースが近づいてきてますが、Rails 5.0 から alias_method_chain を使っていると deprecation warning が出るようになりました https://github.com/rails/rails/pull/19434 。 単純に alias_method_chainprepend に書き換えればいいかと思いきや、かなりのレアケースではあるけれども、以前実際に失敗した事例の紹介です。

#!/usr/bin/env ruby
require 'active_support/all'

module M
  def foo
    p 'M#foo'
    super
  end
end

class C
  def foo
    p 'C#foo'
  end
end

class C
  def foo_with_modified
    p 'C#foo modified'
    foo_without_modified
  end

  alias_method_chain :foo, :modified
  prepend M
end

C.new.foo

これを実行すると

"M#foo"
"C#foo modified"
"C#foo"

という出力結果が得られる。予想通り。

#!/usr/bin/env ruby
require 'active_support/all'

module M
  def foo
    p 'M#foo'
    super
  end
end

class C
  def foo
    p 'C#foo'
  end
end

class C
  def foo_with_modified
    p 'C#foo modified'
    foo_without_modified
  end

  prepend M
  alias_method_chain :foo, :modified
end

C.new.foo

これを実行すると SystemStackError。変わったのは alias_method_chainprepend の順序だけ。

言われてみればたしかにそうなるのはわかるけれども、たとえば activerecord をモンキーパッチで拡張するような gem が複数あった場合、たまたま同じメソッドに対する alias_method_chainprepend が混在していると、初期化順でエラーになったりならなかったりする。