Wednesday, July 31, 2013

gnupack(emacs-24.x)でw3mを使う

私は Windows 上では長らく Meadow を使っていたのだが、Meadow は emacs-22系であるほか、IME回りでたまに落ちることがあったので、そろそろ乗り換えるか…ということで、最近 emacs-24系ベースの gnupack (11.00) に移行してみた。
(もちろん、Meadow の開発者の皆さまには、とても^2感謝しております!これまでどうもありがとうございました!)
移行作業自体は、ほぼ無問題だったのだが、HTMLメールのレンダリングに使っていた emacs-w3m 関連で微笑^H小な TIP があったのでメモ。

emacs-w3m の最新リリース版は 1.4.4 であり、以下からダウンロードできる。

http://emacs-w3m.namazu.org/#download

しかし、リリース版では emacs のバージョンごとに処理を分けている部分が emacs-21 系までしか対応しておらず、

http://emacs-w3m.namazu.org/ml/msg08719.html

にあるようなエラーが出てしまう。(上記は、なんと2006年の記事だ!)
そして、emacs-24 系の現行 gnupack (11.00)環境で、上記の記事で紹介されている work around で逃げようとすると、今度は emacs lisp に非互換があるらしく、やはりうまくいかないのだった。

さて、どうしようか…と調べていたところ、上記の emacs-w3m ダウンロードページに記載のある CVS リポジトリにある開発最新版では、w3m-e19.el や w3m-e20.el や w3m-e21.el といったファイルが w3m-ems.el という名前で統合されており、emacs-24 系でも問題なく使えそうなことがわかった。

そうすると、後は組み込んで使うだけなのだが、ここで問題が一点。
CVSの開発版でも、ドキュメントには emacs (gnupack) の環境定義ファイル(.emacs)
から

   (require 'w3m-load)

して使うべし…と書いてあるのだが、本記事執筆時点の CVS リポジトリからは w3m-load.el が消えてしまっている!

さて、どうするのが正しいのだろうか...と考えたのだが、この記事

http://d.hatena.ne.jp/minazoko/20110307/1299516952

を参考に、「emacs-w3m のリリース版には w3m-load.el あるから、これをコピってくればいいんじゃね?」と安易に考えてやってみたところ、何の問題もなく動いたのだった(笑)

本来はきちんと追求するべきなのだが、いろいろばたばたしている折でもあり、とりあえずここまでとした。

Wednesday, July 24, 2013

書評: Software Defined Storage with OpenStack Swift


"Software Defined Storage with OpenStack Swift", Joe Arnold

昨今は、なんでもかんでも Software Defined ほげほげな世の中になってしまった。
本書でいう Software Defined な Storage は、Amazon でいうところの S3 に相当する Object Storage のことである。

著者の Joe Arnold 氏は、元CloudScaling所属で、現在は SwiftStack という自分の会社をやっている人である。SwiftStackには、Swift の PTL の John Dickinson も合流し、今、オブジェクトストレージ系では一押しのベンチャーではないかと思う。

この本は、要するに OpenStack Swift を使ってオブジェクトストレージの基盤を構築・運用するにあたり、SwiftStack のコントローラを使うと、うまくSoftware Define Storage のCプレーンの制御を実現できますよ…というストーリーで構成されている。

したがって、(残念ながら)オープンソース版のSwiftを使ってシステムを作って運用するのにあたって、この本の手順の通りに Swift を運用すればよいというわけでは「ない」。 しかし、それでも著者の豊富なSwift案件対応経験に基づいた、非常に示唆に富んだ本である。

本書の構成は以下の通りである。

1. 背景とSwiftの概要
2. Swiftの動産
3. Swiftの使い方
4. Swiftのインストール
5. SwiftStack コントローラによる Software Definrd Storage
6. SwiftStack クラスタの設定
7. SwiftStack クラスタの運用
8. SwiftStackと他コンポーネントのインテグレーション
9. テストとベンチマーク
10. チューニング

タイトルの Software Defined Storage ひ関連するくだりは、5章まで出てこない。しかも SDN について一般的に言われるのと同様に、制御面を分離して、集中制御しましょうと言っているだけである。ここで注意が必要なのは、オープンソースの Swift は制御される側であって、制御面にいるのは SwiftStack 社のコントローラだということだ。

Swift はシンプルで割り切った設計で、動きも理解しやすい。しかし、お客さんのデータを預かって、しかもそれなりな規模で運用するのに必要なノウハウを持つことと、ただ動かすのとは別次元の問題である。これを解決するのが SwiftStack のコントローラ…というわけだ。

Swift について、ring ファイル作成時の留意事項のレベルで理解している読者、具体的には例えば part_power と全体キャパシティの関係を理解している読者は、6章以降から読めば、システムの設計・構築・運用にあたって、充分に有意義な知見、またはヒントを得ることができるだろう。例えば、ストレージノード(account/container/object)のHDD故障の時とノード故障の時の挙動の違いや、留意事項、対処について解説した文献がこれまであっただろうか?その他、運用時に見るべき指標など、なぜこの本に書かれていることがベストプラクティスなのか、考えながら読むと、とても高いレベルまで達することができるように思う。
自分としても、この本を読んではじめて気が付いたことはたくさんあるので、具体的にまとめて公開したいところなのだが、それをやってしまうと「おっと誰か来...」てしまうので、このくらいにしておく。

Swift について学ぶところからはじめる読者は、英文は平易な上に分量もそんなに多くないので、普通に先頭から通読するのを薦めたい。

だいぶ不完全燃焼感があるのだが、このくらいで。

Saturday, May 11, 2013

Virtual Ethernet Tunnel (veth) の組を知りたい

Linux には、OpenVZ由来の Virtual Ethernet Tunnel (veth) というものがある。

名前が示す通り、仮想的な Ethernet Interface の組を作成して、つないでくれる。

これは、VMやContainerを動かす時に便利な機能なのだが、たくさん veth I/F を作っていると、へたをすると、どれが veth なのか?とか、どれとどれが対応するのかわからなくなる。

ip コマンドなどで調べられればいいのだが、ざっと調べた限り発見できなかった。

しかたないので、ソースの

  ./driver/net/veth.c

を軽く調べてみると、以下をの通り、ethtool で veth 特有の統計情報を出力しているらしい。


 39 /*
 40  * ethtool interface
 41  */
 42
 43 static struct {
 44     const char string[ETH_GSTRING_LEN];
 45 } ethtool_stats_keys[] = {
 46     { "peer_ifindex" },
 47 };

peer_ifindex という統計値を出力しているドライバは、少なくとも現状は他に存在しないようだ。

なお、調査を行ったのは CentOS 6.4 で、バージョンはこんな感じ。

# modinfo veth
filename:       /lib/modules/2.6.32-358.el6.x86_64/kernel/drivers/net/veth.ko
alias:          rtnl-link-veth
license:        GPL v2
description:    Virtual Ethernet Tunnel
srcversion:     84BEC28E0EAB8E31E4CACC2
depends:
vermagic:       2.6.32-358.el6.x86_64 SMP mod_unload modversions


それで、実際にやってみた結果が以下。

まず、調査対象のサーバでは以下のようなインタフェースが作成されている。


# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
    link/ether 00:0c:29:ba:96:73 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
    link/ether 00:0c:29:ba:96:7d brd ff:ff:ff:ff:ff:ff
4: eth2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 00:0c:29:ba:96:87 brd ff:ff:ff:ff:ff:ff
5: br-eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
    link/ether 00:0c:29:ba:96:7d brd ff:ff:ff:ff:ff:ff
6: br-int: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether b2:48:56:9c:8f:43 brd ff:ff:ff:ff:ff:ff
10: qbr88886a31-17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
31: phy-br-eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether e6:f1:56:69:fb:f2 brd ff:ff:ff:ff:ff:ff
32: int-br-eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 76:a5:94:6e:ff:f6 brd ff:ff:ff:ff:ff:ff
42: qbr30aed268-b6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 8e:4c:83:bf:36:df brd ff:ff:ff:ff:ff:ff
43: qvo30aed268-b6: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether d2:c1:85:94:31:83 brd ff:ff:ff:ff:ff:ff
44: qvb30aed268-b6: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 8e:4c:83:bf:36:df brd ff:ff:ff:ff:ff:ff
45: tap30aed268-b6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 500
    link/ether fe:16:3e:b1:6c:03 brd ff:ff:ff:ff:ff:ff

実際は qvo30aed268-b6 と qvb30aed268-b6 が veth で組になっているのだが、この出力ではわからない。

ソース調査の結果からは、ethtool -S でかたっぱしから統計情報を出力してやると、veth だけpeer_ifindex が出力され、相手がわかるはずである。

# ethtool -S  qvo30aed268-b6
NIC statistics:
     peer_ifindex: 44

# ethtool -S qvb30aed268-b6
NIC statistics:
     peer_ifindex: 43

…ということで、少なくとも、上記の2つがペアになっていることはわかった。

他のデバイスについては何も表示されないものと期待できるのだが、代表的なところをやってみると、以下の通り。

Linux Tun/Tap

# ethtool -S  tap30aed268-b6
no stats available


OpenvSwitch で作成した bridge
# ethtool -S  br-int
no stats available

生の ethernet カード
# ethtool -S  eth0
no stats available

brctl で作成した bridge
# ethtool -S  qbr88886a31-17
no stats available


ということで、最低限の目的は遂げることができるようだ。


Monday, May 6, 2013

MySQLの権限設定について


DBMSについてはずぶの素人の告白…というか、メモということで。
(たぶん誤解が入っているはずなので、ご指摘いただけると幸いです...)

さて、自分が使っているソフトが制御データを永続化するためにRDBMSを使っていることも多いと思う。
ご多分にもれず、自分がいじっているOpenStackの各種サービスくんたちもRDBMS(通常はMySQL)に依存しまくりである。

で、まず最近ようやく気が付いたのだが、GRANT を使ってユーザに対してアクセス権を与える時の以下のような注意がある(らしい。なにか誤解があるような気がするのだが...)

まず、

  mysql> create database mydb;

データベースを作るのはいいとして、ユーザ作成/アクセス権限を付与する時に

  mysql> grant all privileges on mydb.* to 'user1'@'%' identified by 'PASSWORD';

としただけでは、実は localhost は含まれないので、localhost(127.0.0.1)でアクセスする可能性もあるなら

  mysql> grant all privileges on mydb.* to 'user1'@'locahost' identified by 'PASSWORD';

も必要というのが1つ。

さらに、ここから誤解が入っている気がしてならないのだが、
上記ではアクセス権は細粒度でやったほうがいいかな?ということで、
データベース名 mydb を指定し、データベース単位での権限を設定しているつもり。

この場合、

  mysql> use mysql
  mysql> select * from mysl.user where user='user1'\G;

とかしてみると、権限が軒並み N になっているのが気になるのだが、
自ホストから(localhost ではなくて) IP やホスト名でアクセスできない。

…ので、上述の grant 文2行に加えて、

  mysql> grant all privileges on mydb.* to 'user1'@'DB_HOST_NAME' identified by 'PASSWORD';

が必要になる。
DB_HOST_NAME は MySQL がまさに動作しているホストのホスト名である。

一方、grant 文の 'on DB_NAME.TABLE_NAME' を指定する際に '*.*' とすると
データベース単位から(MySQLインスタンス内)グローバルになるわけだが、
権限が軒並み Y に変わり、アクセスできるようになる。


さて、何が足りない(or 誤解な)のだろう?

追記
バージョンは、古いのだけど CentOS6.4/EPEL6付属の MySQL 5.1系の話。

Sunday, March 31, 2013

えぼりゅーしょん3


この話の続き。

まだ python のプログラムはいじっていなくて、データ処理の話。

家系図は書いてみたので、次は genes のクラスタ分析をしたらどうなるか?…ということで、Rを使ってひとしきりやってみた結果。

1000updateかけた後、生存animal数 152、死亡animal数 466、残plant数 53、生存animalの平均年齢(update) 197.4、生存animalの平均保持 energy 88.5…という状態がお題。

生存animalを対象にすると、genes は8要素のベクターなので、これが152個あることになる。練習としては、まあたいしたことはないデータ量だ。

まず、階層型クラスタリングの R の hclust を使って書いてみたのがこれ。


ふーん、と思いつつ、このへんを参考にk-meansでクラスタ数2~15で変化させながら、あたりをみてみたのがこれ。

クラスタ数5くらいでいいのかしら?と思いながら分類させてみたのがこれ。


実は、Rをそれっぽく使ってみたのは初めてだったのだが、データ入出力はいまいち使い勝手が悪い(or 整理されていない)な…というのが感想だったりする。

まあ、そもそも論として、この環境で種の分化ってどう定義するの?なんて話もあるわけだし、先は長い...

Saturday, March 30, 2013

python 版 mysqlbench

python の練習の続き。

やっている仕事の関係上、python から RDBMS、特に MySQL を触る必要があることが多い(多かった...?)のだが、練習がてら、まずは素直に python-mysqldb を使い、pgbench のMySQL移植の mysqlbench を、さらに python に移植してみた。
なお、mysqlbench のオリジナルは PostgreSQLの石井さんが作った TPC-Bライクなスタンドアロン型のベンチマークツール pgbench であることは言うまでもない。

現状版がこれ↓


で、移植元は、お名前がわからないのが残念なのだが日本の MySQL ユーザ会の有志の方が作成されたこれ↓である。



まず、移植元の mysqlbench の実行結果がこんな感じなのに対して、
(微妙にエラーが出ているのは愛嬌)

$  ~/mysqlbench-0.1/mysqlbench  -U username -P password -h 192.168.1.1 -c 10 -t 1000 pgbench
starting vacuum...OPTIMIZE TABLE tellers: Commands out of sync; you can't run this command now
DELETE FROM history: Commands out of sync; you can't run this command now
OPTIMIZE TABLE history: Commands out of sync; you can't run this command now
end.
all connection OK
go
 end.

transaction type                    . . . : TPC-B (sort of)
scaling factor                      . . . : 1
number of clients                     . . : 10
number of transactions per client         : 1000
number of transactions actually processed : 10000/10000
tps (include connections establishing)  . : 264.557585
tps (exclude connections establishing)  . : 264.608737


今回の python 移植版はこんな感じになる。

$ python python-mysqlbench.py -U username -P password -h 192.168.1.1 -c 10 -t 1000 -D pgbench
all threads completed
transaction type                    . . . : TPC-B (sort of)
scaling factor                      . . . : 1
number of clients                     . . : 10
number of transactions per client         : 1000
number of transactions actually processed : 10000/10000
tps (include connections establishing)  . : 270.410621
tps (exclude connections establishing)  . : 270.462350


だいたい誤差くらいの範囲で同じ値が出るようになったようだ。

まだエラー処理が足りない(特に SQL 発行部分をちゃんと try ~ exception にしていない部分があるの)と、コマンドラインシンタックスが微妙に違う(dbname の指定方法とか...)とか、いろいろあるのだが、それはまあぼちぼちということで。(そういうわけで、まだβ版です...)

なお、また「pep8 でエラーがでるじゃねえか」とワカモノから怒られるような気がするのだが、以下の通り移植元のURLを記載した行と、大元の石井さんのPostgreSQL用のC実装のファイルヘッダに含まれる $Id$ 行がひっかかる。

$ pep8  python-mysqlbench.py
python-mysqlbench.py:9:80: E501 line too long (112 characters)
python-mysqlbench.py:24:80: E501 line too long (86 characters)

まあ、これはしかたないよねぇ...ということで。

Thursday, March 28, 2013

えぼりゅーしょん2

またこの話の続き。

今度は、これ↓

https://github.com/thatsdone/junkbox/blob/master/python/evolution.py

をもう少しいじって、animal に id をつけ、parent のidと誕生/死亡のタイミングをトラッキングできるようにしてみた。

で、やりたいのは、クラスタリングとか、そういう処理をできるようにした上で、いずれは genes まわりの仕組みを入れ替え可能にしてGAっぽくして評価してみたい…という話になるのだが、まずは id と parent を記録できるようにしたので、exact な家系図をかいてみましょうという話。

graphviz をつかって素直にプロットしただけなので、いまいちみにくいのだが、1000 update かけた時点で、生きている animal を黄色、すでに死んでしまった animal を青でいろづけして書いてみた結果がこれ。

現状は、genes が効いてくるのは turn のところで、ほぼランダムな効果でしかないようになっているので、もうすこし均一になるかも…と、思ったのだが、それでも淘汰が働いているせいか、クラスタっぽく分かれてきているようだ。噂の種の分化(?)を見るためには genes の距離関数はどうしようか…とか、TODOはあれこれ広がるのだが、まだしばらく遊べそうだということで...w

Saturday, March 23, 2013

えぼりゅーしょん

前回のこの話の続き。
ようするに、このスクリプトの話。

https://github.com/thatsdone/junkbox/blob/master/python/evolution.py

あのあと断続的にいろいろいじっていて、致命的なバグをとったり、pep8 でいろいろ出るじゃねーかとワカモノからケチをつけられてみたり、animalのトラッキングをできるようにしてみたりとか、バッチモードを追加したりとかして遊んでいたのだが、進化のメカニズムそのもののところはオリジナルから手をつけずにいた。

まあ、まだまだ python らしくするために手を入れるところはたくさんあるのだが、いったんこんなグラフが書けたのではりつけておく。





















全 animal について turn/move/eat/reproduce をかける作業を 1update として、
世界を 4000 回updateかけている。
この条件だと、animal数は150くらい、食べ物のplant数は50くらい、animal の平均寿命は 180-200 update くらいで落ち付いてくるようだ。
少なくともオリジナルの Lisp 版では種の分化が起きたりするらしく、これでも同じことが起きるはずなのだが、まだそこまでは手をつけていない。

もうちょっといじったら、進化のメカニズムの面でも、いろいろ遊んでみよう。

Tuesday, March 19, 2013

Land of Lisp とか Common Lisp とか Python とか...


巷では "Land of Lisp: Learn to Program in Lisp, One Game at a Time!" が話題(?)だが、お小遣い制なので本もなかなかなかなか買えない私だったりする。

ところで、この本に出てくるさまざまなゲームのコードは著者のサイト

http://landoflisp.com/source.html

から入手できるようになっているので、scheme 実装の一つである Gauche に移植してみた…なんていう人もいる。

http://practical-scheme.net/wiliki/wiliki.cgi?l=jp&p=Gauche%3aLandOfLisp
http://blog.practical-scheme.net/shiro/20130223-land-of-gauche

スゲー...

私も、特に evolution.lisp に心惹かれてしまっていたのだが、Lisp はいまいち 土地勘がないのと、事情により Python力(ちから)を上げないといけなかったりするので、「それじゃあ、Common Lisp のソースを解読しつつ、Python に移植してみるか?」と、くだらないことを思いついてしまったのだった...
実際やりはじめたのは、March 8, 2013 くらいだったらしい...(たぶん)

そして、できあがったのがこれ↓である。

https://github.com/thatsdone/junkbox/blob/ed2c157524f6b0d058fe5b2a9286342faf1c0e5c/python/evolution.py

HEADはこっち↓
https://github.com/thatsdone/junkbox/blob/master/python/evolution.py


現状は、無限ループをつくるのに末尾再帰を使っているところまで Lisp のコードそのまんまなのだが、一応、readline を使って prompt まわりをマシにしてみたりとか、animals と plants のダンプを表示するコマンドくらいは追加してみた。進化させてみると、それっぽく動いている(ようだ)。
暇をみて、もうちょっと Python らしくする予定。

やってみての感想は、

  • Common Lisp (だけじゃないんだろうな、たぶん)って、なんか面白い :)
  • 意外に global variables を多用するのね
  • 竹内先生のあの本をちょっとかじっていたからこそ読めたのかなぁ...

云々とか、そんな感じ。
Lisp の黒魔術をかいま見るまでは、まだまだ遠い(笑)

余談だが、OpenStack Advent Calendar のこんな記事(by @irix_jp さん)とかこんな記事(by @saito_hideki さん)とかの凄腕な人たちのネタに触発されているというのはヒミツw

しかし、Land of Lisp ほしいなぁ…(fade out)

Friday, March 8, 2013

python-dnspython を使う場合のタイムアウトについて

python から DNS を触りに行ったりしていて気が付いたことがあったのでメモ。

python には dnspython というライブラリがあって、DNS Update 機能等も含めて便利に使えるようになっている。Ubuntu/Debian では、python-dnspython という名前のパッケージを apt-get install すればよい。

さて、DNSサーバも落ちたりするわけで、そういう場合に備えて、特に自前で運用しているDNSサーバなら、単純には

/etc/resolv.conf

にこんな感じで、

nameserver  192.168.0.1
nameserver  192.168.0.2
search  example.com
options  timeout:5  attempt:1

複数DNSサーバを並べて、options で timeout やリトライ回数を書いておくとか、そういう感じかと思う。

さて、このへんから本題。
素人の私は、上記のように書いておけば、dnspython が nslookup なり dig なりのように中から参照してくれるのかと思い込んでいたのだが、そうではないのだった。

(現行stable版の 1.10.0の)ドキュメント


を読んでみてもよくわからないので、最初は「もしかしたら、timeoutを指定できないのか?」と思ったら、とんだ濡れ衣で、ちゃんとできた。:o

以下のような感じで、名前解決なら Resolver オブジェクトを作ったあと、timeout とか lifetime といった attribute に秒単位で設定すればよい。

    def lookup(hostname):
        import dns.resolver
           :
        resolver = dns.resolver.Resolver()
        resolver.timeout = 5.0
        resolver.lifetime = 5.0
           :

実は、このへんは以下で参照可能なソースの


以下のあたりのResolver クラスの定義をつらつら眺めていて判明したのだった。:o

 394 -class Resolver(object): 
 395      """DNS stub resolver 
    (snip)
 408      @ivar timeout: The number of seconds to wait for a response from a 
 409      server, before timing out. 
 410      @type timeout: float 
 411      @ivar lifetime: The total number of seconds to spend trying to get an 
 412      answer to the question.  If the lifetime expires, a Timeout exception 
 413      will occur. 
 414      @type lifetime: float 

ところで、ここでもう一つ注意がある。

timeout や lifetime の型は float であって int ではないことに注意。実際、上記のサンプルの 5.0 のところは、最初 5 と書いていたのだが、私が動かしてみた限りは思った通りに timeout してくれず、(たぶん)デフォルト値の 30 秒のままだった :(

文字列と判断されて無視されたのかなぁ...とか、つらつら思いつつ、初心者の日々は流れていくのであった。

Thursday, March 7, 2013

GNU ld の--no-as-neededと--as-neededオプションについて


GNU ld の挙動で気が付いたことがあったのでメモ。

例によって少し前置きが長くなるが、端的にいえば、system call 等の他人が提供する関数呼び出しをフックして、処理を横取りしたくなることがたまにある。

例えば、system call の write(2) をフックしたいなら、以下のようなプログラムを書くことになる。

  1 #include <dlfcn.h>  /* これが必要 */
  2 #include <unistd.h> /* 今回hookしたい write(2)が定義されている */
  3 #include <stdio.h>
  4 #include <errno.h>
  5
    ↓ hook したい関数、この場合はwrite(2) と同じようにする。
  6 ssize_t (*org)(int fd, const void *buf, size_t count) = 0;
  7
    ↓main()実行の前に呼ばれる初期化ルーチン
  8 __attribute__((constructor))
  9 static void init_hook()
 10 {
 11     org = (ssize_t(*)(int, const void *, size_t))dlsym(RTLD_NEXT, "write");
 12     printf("%s: constructor called! %p\n", __func__, org);
 13 }
 14
    ↓main()から抜けた後に呼ばれる終了ルーチン
 15 __attribute__((destructor))
 16 static void fini_hook()
 17 {
 18     printf("%s: destructor called!\n", __func__);
 19 }
 20
    ↓write(2)の処理を横取りするルーチン
 21 ssize_t write(int fd, const void *buf, size_t count)
 22 {
 23     int ret;
 24
 25     /*
 26      * Do what you like to do here
 27      */
 28     printf("%s: hook!\n", __func__);
 29     ret = (int)org(fd, buf, count);
 30     /*
 31      * Do what you like to do here
 32      */
 33     return ret;
 34
 35 }

これを、hook.c として保存しておき、hook.so という名前の shared object を作成する。この際、初期化ルーチンの中で、オリジナルの write(2)のエントリを探すのに使っている dlsym(3)のために、-ldl オプション(と -D_GNU_SOURCE)が必要になる。

  $gcc  -D_GNU_SOURCE  -fPIC -shared -ldl hook.c -o hook.so

ぐぐると日本語の解説もいくつか出てくるので参照してほしいのだが、上記のようにして shared object 作り、プログラムの実行時に LD_PRELOAD 環境変数で読み混ませてやれば目的を達することができる。

…はずだった。これまでは。

しかし、上記を(少なくとも) Ubuntu 12.10 上で作成して、実行してみると

  $ env LD_PRELOAD=./hook.so cat /tmp/foo
  cat: symbol lookup error: ./hook.so: undefined symbol: dlsym

…と、エラーになってしまう。
オリジナルの write(2)を探すのに使っていて、-ldl でちゃんと指定しているはずの dlsym()が見つからないとか言っている。

なんで?
…ということで確認してみると、当然リンクされているはずの libdl.so がリンクされていない!

  $ ldd hook.so
          linux-vdso.so.1 =>  (0x00007fff8edba000)
          libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f06d3745000)
          /lib64/ld-linux-x86-64.so.2 (0x00007f06d3d1a000)

どういうことなの?
…と、ぐぐっていて出てきた以下のトピックを読んで初めて知ったのだが、

  http://os.inf.tu-dresden.de/pipermail/l4-hackers/2011/005078.html

実は、少なくとも現行の Ubuntu ではリンカ (GNU ld) の挙動が変更されており、以下のように、--no-as-needed というオプションを指定しないといけないことが分かった。
(脱線するが、L4のMLって、まだ普通に生きているのだなぁ...と、遠い目になったのはひみつ)

  $ gcc -D_GNU_SOURCE -Wl,'--no-as-needed' -fPIC -shared -ldl hook.c -o hook.so
  $ ldd hook.so
          linux-vdso.so.1 =>  (0x00007fff5494d000)
          libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f2d72be2000)
          libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2d72823000)
          /lib64/ld-linux-x86-64.so.2 (0x00007f2d72ffc000)

赤字部分のように、今度は意図の通り、リンクされている。

現行のデフォルトの挙動は'--no-as-needed'の逆の'--as-needed'で、これは、リンカ(ld)に -ldl 等と渡された shared object の一覧の中から、プログラムの中で実際に使われているシンボルが入っている shared object のみ実際にリンクするという意味になるようだ。

でも、ちゃんと使っているじゃん?…と思うかもしれない。
これについては、ソースレベルでは(まだ)未確認なのだが、今回の場合は、_init ルーチンの中で dlsym() を使っており、'--as-needed' の場合に探索される範囲から外れているから…というのが真相らしい。

さて、ここでもうひとつ注意がある。'--no-as-needed' をコマンドラインのどの順番で指定するかには意味がある。この例では -ldl の前に指定する必要がある。
実際、-ldl の後ろに指定して shared object を作ってみると

  $ gcc  -g -Wall -D_GNU_SOURCE -fPIC -shared -ldl -Wl,'--no-as-needed' hook.c -o hook.so
  $ ldd hook.so
          linux-vdso.so.1 =>  (0x00007fffea666000)
          libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb722dc8000)
          /lib64/ld-linux-x86-64.so.2 (0x00007fb72339d000)

の通り、また libdl.so がリンクされなってしまうので注意のこと。


ちなみに、以下は man ld からの引用なのだが、赤字のところが --no-as-neededの場合は逆になる…ということのようだ。
# そんなの気がつかねーよ...orz

LD(1)                        GNU Development Tools                       LD(1)

NAME
       ld - The GNU linker

SYNOPSIS
       ld [options] objfile ...

DESCRIPTION
  (snip)
       --as-needed
       --no-as-needed
           This option affects ELF DT_NEEDED tags for dynamic libraries
           mentioned on the command line after the --as-needed option.
           Normally the linker will add a DT_NEEDED tag for each dynamic
           library mentioned on the command line, regardless of whether the
           library is actually needed or not.  --as-needed causes a DT_NEEDED
           tag to only be emitted for a library that satisfies an undefined
           symbol reference from a regular object file or, if the library is
           not found in the DT_NEEDED lists of other libraries linked up to
           that point, an undefined symbol reference from another dynamic
           library.  --no-as-needed restores the default behaviour.


最後に、今回作った hook.so を実際に使ってみるとどうなるかも載せておく。

write(2) を使うできるだけ単純なプログラムということで、cat を使って

$ cat /tmp/bar
FOO

というファイルを表示してみると、

$ env LD_PRELOAD=./hook.so  cat /tmp/bar
init_hook: constructor called! 0x7fd48f0228f0
write: hook!
FOO

$

ということで、冒頭のサンプルに書いてある通り、実際の write(2)が走る前に "write: hook!" と自分で追加した処理が実行されていることがわかる。

Saturday, March 2, 2013

RHELでもコネクション単位にTCPの再送タイムアウトを制御したい


前に書いた「TCPの再送タイムアウトを制御したい」の続き。

要は、コネクション単位で再送タイムアウトを調整したい件である。

国内で仕事でLinuxを使っていると、どうしても Redhat にバイアスがかかる事情があって、これはこれでなんとかしたいのだが、それはまた別の話。

それで、2/21(現地時間)にGAがアナウンスされた RedHat 6.4 では、

http://www.redhat.com/about/news/press-archive/2013/2/red-hat-announces-general-availability-of-next-minor-release-of-red-hat-enterprise-linux-6

kernel version は 2.6.32-358 まであがっている。もちろん、以下からソース(SRPM)も入手できる。

http://ftp.redhat.com/pub/redhat/linux/enterprise/6Server/en/os/SRPMS/

さて、本日のお題は例の setsockopt() で指定する TCP_USER_TIMEOUT が使えるかどうか?…ということで、さっそく上記から

  kernel-2.6.32-358.el6.src.rpm

を入手して調べてみたところ、backport されているようだ。:)

kernel.spec を眺めてみると、bugilla の ID: 819610 で feature request されたと思しき記述があるのだが、

| - [net] tcp: Add TCP_USER_TIMEOUT socket option (Jiri Benc) [819610]

一般には見られないようだ。残念…というか、まあ特定の顧客対応だったものと思われる。


余談だが、RHEL6.4 のカーネルは、2/21にGAがアナウンスされた後、2/26には RHSA が出てアップデートがかかっている。

https://rhn.redhat.com/errata/RHSA-2013-0567.html

ptrace まわりで脆弱性が見つかったらしい。

なお、さらに余談なのだが

https://lwn.net/Articles/539394/

でも触れられている

https://rhn.redhat.com/errata/RHSA-2013-0496.html

って、発行は2/21じゃん…と思ったのだが、よくみてみたらリリース版のバージョンそのものなので、これを同梱してGAということだったらしい。

#あるある...www

Sunday, February 24, 2013

WindowsでもTZ環境変数を設定しよう


Windowsにおける言語環境の設定について、見落としていたことがあったのでメモ。

まず、私は自宅では、Windows7(64bit)上で Meadow を入れて mew でメールの読み書きをしている。

MUAの表示はこんな↓感じ。

| X-Mailer: Mew version 6.1 on Emacs 22.1 / Mule 5.0 (SAKAKI)

さて、本日のお題は mew で送信されるメールの Date: フィールドの TimeZone 表示である。
実は、つい最近まで私が自宅から送信するメールにはこんな Date: フィールドが付いていたのだった。

| Date: Fri, 22 Feb 2013 23:28:12 +0900 (東京 (標準時))

最後の Time Zone名が、JST になってほしいところが「(東京 (標準時))」と日本語表示になってしまっている。 :(
一応、生ヘッダ的には

| Date: Fri, 22 Feb 2013 23:28:12 +0900
|  =?iso-2022-jp?B?KBskQkVsNX4bKEIgKBskQkk4PWA7fhsoQikp?=

ということで、encoding 処理はされてはいるのだが、これはうれしくない。

調べてみたところ、実はこの話は10年以上前に mule の MLで議論されていたことがわかった。

http://www.m17n.org/mlarchive/mule-ja/200102/msg00071.html

環境変数で TZ を設定しない限り、Meadow や mew の設定で回避する方法は今でもなさそうだ。ただ、本人としては、各種ツールの都合があるので、そもそも TZ を設定しているつもりで抜けていたという手落ちが今になって発覚したというオチなのだった...orz

「コンピュータ」のプロパティ→「システムの詳細設定」→「環境変数」から、

  TZ=JST-9

を設定し、送信したメールの Time Zone名表示が

| Date: Sat, 23 Feb 2013 23:54:43 +0900 (JST)


と期待通りに変わったことを確認して作業完了。

しかし、さかのぼってみると、結構前からこの望ましくない状態が続いていたことが発覚し、へこんでいる今日このごろ...orz

Monday, February 18, 2013

不審な外部向けアクセスが...

socket APIのトレースを見ていて妙なことに気が付いたことがあったのでメモ。

connect の第2引数にポインタでわたってくるstruct sockaddr_in の中身が

  sin_family = 1
  port       = 12150
  sin_addr   = 97.114.47.114

なんていうコネクション確立要求が出ているらしい。

完全孤立した外部につながらない環境だったし、8.8.8.8 みたいなコネクティビティチェックに無造作に使われるアドレスでもないので、「これはっ!?」と思って色めきだったのだった。

まずは「このIPって何?」ということで調べてみると...

| nslookup 97.114.47.114
| Server:         8.8.8.8
| Address:        8.8.8.8#53
| Non-authoritative answer:
| 114.47.114.97.in-addr.arpa      name = 97-114-47-114.roch.qwest.net.

うーん、なんだかよくわからない。

じゃあ…ということで、IPとportでググってみると、
こんなの↓とか、
http://comments.gmane.org/gmane.os.freebsd.devel.sparc/479
こんなの↓とか、
http://sourceforge.jp/projects/ultramonkey-l7/lists/archive/develop/20081208/000221.html
出てきた。
しかも2つめは日本語の L7-UltraMonkey のコミュニティの記事な上に、ずばありこんな記述が...

|  ○ 原因
|  
|      UNIX ドメインソケットなので sun_path を表示すべきところを,sin_addr と
|    sin_port を表示していた.

そう。
上記のログは、自前で socket()まわりの動きを確認していたつもりが、sin_family の値をきちんとチェックしていなかったために、こんなことになっていたのだった。

実際、冒頭で書いた sin_family = 1 は AF_UNIX でずばり。見落としてました...orz

なお、97.114.47.114:12150 というのは、どうも resolver 系で典型的に使う UNIXドメインソケットのパス名(の一部)に対応しているらしい。

大騒ぎする前に、ちゃんと追いかけておいて良かった...orz

Tuesday, February 12, 2013

UbuntuのsleepenhとRedHatのusleepの違い

いろいろ検証っぽいことをしていると、1秒単位でしか指定できない sleep コマンドでは足りなくて、usleep を使うことがよくある。

ただし、これは RedHat 系固有のコマンドなので、Ubuntu (や Debian)でも使いたいと思って調べて初めて知ったことがあったのでメモ。

話は簡単で、sleepenh コマンドを使う。

私はこの記事で見つけた。

現行の 12.10 (Quantal) に対応するパッケージはこれ


で、man page はこんな感じ。


文字通り拡張版sleepということのようだが、コマンド名を変えているだけあって仕様に違いがある。

SLEEPENH(1)                                                        SLEEPENH(1)

NAME
       sleepenh - an enhanced sleep program.

SYNOPSIS
       sleepenh [initial-time] sleep-time

DESCRIPTION
       sleepenh  is a program that can be used when there is a need to execute
       some functions periodically in a shell script. It was not  designed  to
       be  accurate  for  a  single sleep, but to be accurate in a sequence of
       consecutive sleeps.
       After a successful execution, it returns to  stdout  the  timestamp  it
       finished running, that can be used as initial-time to a successive exe-
       cution of sleepenh.

OPTIONS
       There are no command line options. Run it without any option to  get  a
       brief help and version.

ARGUMENTS
       sleep-time is a real number in seconds, with microseconds resolution (1
       minute, 20 seconds and 123456 microseconds would be 80.123456).
       initial-time is a real number in seconds, with microseconds resolution.
       This number is system dependent. In GNU/Linux systems, it is the number
       of seconds since midnight 1970-01-01 GMT. Do not  try  to  get  a  good
       value  of  initial-time. Use the value supplied by a previous execution
       of sleepenh.
       If you don't specify initial-time, it is assumed the current-time.

usleep の場合は単純にマイクロ秒単位の整数値で指定できる版のsleepなのだが、sleepenh の場合は、引数(上記の 'sleep-time' )に秒単位で指定する。じゃあ、500ms sleep したい場合はどうするか?と言えば、赤太字で強調したように、引数は整数ではなく「実数」ということになっているので、0.5 と指定すればよい。精度は、仕様上はマイクロ秒まで。

なお、精度が「仕様上」マイクロ秒まで…と書いたのには理由があって、ライブラリ/kernelの内部的な処理には適当な丸めが入るので、マイクロ秒単位の精度で指定したからといってもかならずしもそうなるわけではない。このあたり、書きはじめるとけっこうな量になるので、さてやりますか…と、思ってまずは関連文献の調査ということで、ぐぐったら、id:naoya さんのすばらしい記事が出てきた... :o

「Linux のスリープ処理、タイマ処理の詳細を見る」
  http://d.hatena.ne.jp/naoya/20080122/1200960926

さすが…

(この精度のくだりのあたり、@_hito_ さんからご指摘をいただいて追記しました。@_hito_ さん、どうもありがとうございます!)

なお、man page を長々と引用したのにも理由があって、実数を2つ指定していろいろ凝ったことができる...、特に途中で(可変時間の)処理をはさみながらも、決まった間隔でループをまわしたい場合等に便利な仕様になっているので、良く読んで使うべしということで。(指定する時間の単位の違いより、実はこっちのほうが重要な気がする....)

Saturday, February 9, 2013

TCPの再送タイムアウトを制御したい


TCPの再送タイムアウトで最近まで知らなかったことがあったのでメモ。

たとえば、APサーバとDBサーバがあるとする。
AP-DB間のDBCPで使うTCPコネクションは、DBサーバがノードダウンしたのであればさっさと再送リトライをあきらめて切れてほしいと思うのが普通だろう。
このTCPの再送リトライ処理は、何もいじらないと15分以上続くので、
できればTCPコネクション単位で細かく調整させてほしいと思うのは人情だと思う。

Linux の場合、昔はこの調整をしようと思うと、sysctl を使って /proc/sys/net/ipv4/tcp_retries2 の値を書き換えるしかなかった。
これは、リトライ回数でしか指定できず、そのリトライ間隔が回数ごとに変わっていく(長くなる)のでわかりにくい上に、そのOS上の全TCPコネクションで有効になってしまうという問題があった。つまり、このオプションは「TCPコネクション切れやすさ」を調整しているとも言えるので、例えばインターネット向けと内部LAN向けのTCPコネクションは別の切れやすさの設定にしたい…といった場合に困ったことになるのだった。

なお、tcp_retries2 の指定値によるタイムアウト時間の変化については、日本語でも詳しい解説がたくさんある。例えば以下の記事とか。(あれ?良く見てみたら、@int128 さんの記事じゃん... :o)

http://d.hatena.ne.jp/int128/20100514/1273865819


さて、Stackoverflow で見つけた以下の記事に紹介があるのだが、

http://stackoverflow.com/questions/5907527/application-control-of-tcp-retransmission-on-linux

linux-2.6.37 から、setsockopt(2)で指定可能なオプションに TCP_USER_TIMEOUT というものが追加されている。

upstream の commit log はこれ。

http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=dca43c75e7e545694a9dd6288553f55c53e2a3a3


これを使うと、TCPコネクション単位で、しかもリトライ回数ではなくミリ秒単位で、TCPの再送処理をあきらめて ETIMEDOUT でエラーにするまでの時間を調整できる。
socket API としては、Linux 固有機能になるので移植性はなくなるのだが、現実問題としてはとても便利な機能である。


ここからはやや余談。

setsockopt() で IP や TCP のパラメータをいじる時には、

       #include <sys/types.h>
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <netinet/tcp.h>

といったシステムヘッダを使うことになるのだが、少なくとも Ubuntu 12.10 (kernel は3.5系)では、本来あるべき /usr/include/netinet/tcp.h で TCP_USER_TIMEOUT は未定義なので、コンパイルエラーになってしまう。

ではどこで定義されているのか?と言えば、 以下のように /usr/include/linux/tcp.h に定義が存在する。

 /usr/include/linux/tcp.h より

102 #define TCP_QUICKACK            12      /* Block/reenable quick acks */
103 #define TCP_CONGESTION          13      /* Congestion control algorithm */
104 #define TCP_MD5SIG              14      /* TCP MD5 Signature (RFC2385) */
105 #define TCP_COOKIE_TRANSACTIONS 15      /* TCP Cookie Transactions */
106 #define TCP_THIN_LINEAR_TIMEOUTS 16     /* Use linear timeouts for thin streams*/
107 #define TCP_THIN_DUPACK         17      /* Fast retrans. after 1 dupack */
108 #define TCP_USER_TIMEOUT        18      /* How long for loss retry before timeout */
109 #define TCP_REPAIR              19      /* TCP sock is under repair right now */
110 #define TCP_REPAIR_QUEUE        20
111 #define TCP_QUEUE_SEQ           21
112 #define TCP_REPAIR_OPTIONS      22

ただし、ここで問題があって、/usr/include/linux/tcp.h は、device driver 等の kernel module が使うものであって、基本的にユーザプログラムが使ってはいけないことになっている。

ではどうすればよいのか?

実は、TCP_USER_TIMEOUT はマクロでしかないので、18 と直書きする…のがあんまりだと思えば、以下のようにしておくと、将来 netinet/tcp.h を include するだけで TCP_USER_TIMEOUT が使えるようになった時にもそのままビルドできるし、可読性も確保できる(はず)

(動作確認用に作った toy sample より)
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <unistd.h>
  5 #include <sys/socket.h>
  6 #include <netinet/in.h>
  7 #include <netinet/tcp.h>
★ここ↑で代わりに <linux/tcp.h> を include してはいけない。
  8 #include <arpa/inet.h>
  9 #include <errno.h>
 10
 11 int main(int argc, char *argv[])
 12 {
 13         int sockfd, ret, option;
 14         struct sockaddr_in saddr;
   :
 32 #ifndef TCP_USER_TIMEOUT
 33 #define TCP_USER_TIMEOUT 18
 34 #endif
 35         option = 5 * 1000;  /* 5 seconds */
 36         ret = setsockopt(sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT,
 37                          (void *)&option, sizeof(option));
 38         printf("setsockopt TCP_USER_TIMEOUT returns %d/%d\n", ret, errno);
 39         if (ret < 0) exit(0);


参考までに Ubuntu 12.10 に付属している /usr/include/netinet/tcp.h は

itoumsn@gateway:~/src/linux-3.3.1-tag$ dpkg -S /usr/include/netinet/tcp.h
libc6-dev:amd64: /usr/include/netinet/tcp.h
itoumsn@gateway:~/src/linux-3.3.1-tag$ dpkg -l | grep libc6-dev
ii  libc6-dev:amd64   2.15-0ubuntu20  amd64  Embedded GNU C Library: Development Libraries and Header Files

という感じなのだが、いつごろ取り込んでもらえますかねぇ...

あともうひとつ気になるのは、RHEL6 の kernel にバックポートされてくれると嬉しいのだがなぁ...
まだ確認していないのだが、どうかなぁ...

TCP_USER_TIMEOUT 未サポートの kernel の場合は、setsockopt(2)が ENOPROTOOPT (92) でエラーになるようだ。
(fade out)

Sunday, February 3, 2013

MySQL版の pgbench ってないの?


先日、ふとしたことから MySQL に軽く負荷をかけてみる必要が発生した。
できれば単発のSQLを投げるだけではなくて、複数threadで、しかもきちんと transaction になっているようなベンチマークが望ましい…というのが要件。
なお、当然 sysbench は試したのだが、あれは正常に動いていても一定確率で deadlock が発生する性質のもののようなので、今回の目的のためには使えなかった。

それならTPC系のOSSなベンチで何か手ごろなのがないかなぁ…と考えて、PostgresSQLならお手軽に pgbench (TPC-B相当)があるのだが、MySQLではどうなの?…というのが本日のお題。

さていきなり余談だが、マルチプラットフォームで同じベンチが動けばいいなら、JDBCBench という Java で書かれた TPC-B もあることはある


のだが、事情でむかーし使ってみた時には、この移植って少し問題があって修正が必要だったような記憶があったのと、今回は環境の都合でJVMなしでお手軽に動かしたかったのでググってみたところ、日本MySQLユーザ会の有志の方が移植したものを発見。
(当然みんな同じことを考えるんだねぇ…ということで :)


PostgreSQL 付属の pgbench を移植したもののようだ。

ではさっそく…ということで、MySQL 5.5 で動かしてみたところ、DBの初期化のところでエラーになってしまった...orz

ざっと調べたところ、これはそもそも2004年の仕事で、対応している MySQL のバージョンが古く、create table するところでInnodbとかのstorage engineを指定するDDLの syntax が古いだけだということが判明。

mysqlbench.c に以下のようなところがあるので、403行目の TYPE= を ENGINE= に修正するだけで動くようになる。

    372 /*===================================*/
    373 /* create tables and setup data */
    374 static void init(MYSQL *mysql)
    375 {
    376   MYSQL    *con;
    377   int        res;
      : 
    399   for (i = 0; i < (sizeof(DDLs) / sizeof(char *)); i++)
    400   {
    401     if (strncmp(DDLs[i], "CREATE", 6) == 0)
    402     {
    403       sprintf(sql, "%s TYPE=%s", DDLs[i], engine);
    404     }
    405     else



とかあるようだし、もっと探せばどこかで最新版がメンテされているのかなぁ…と、思いつつ、目的は果たせてしまったので、ここまでとした。

MySQLのDDL syntax がいつ変わったのかわからないのだけど、サーバのバージョンをみて、TYPE= と ENGINE= を切り替えるような処理をするのがよさげですね。

Thursday, January 17, 2013

sysctl.confのtokenにピリオドを使いたい

最近、LVS(UltraMonkey)の設定をしていた時に気付いたことがあったのでメモ。
この時は、事情でVLAN なインタフェース(例: eth0.123)にをつかって、LVSの DR(Direct Route)モードで設定を行う必要があった。

この場合、振り分け先のサーバ(ldirectord.cf の real= 行に記述するサーバ。いわゆる「リアルサーバ」)上に lo:0 にも /32 で VIP を設定する必要がある。
これに伴い、lo:0 が持っている VIP への ARP リクエストを無視させる/ARPを出す時にVIPを使わないように制限する必要があるので、
/etc/sysctl.conf 
に、例えばこんな感じの設定を行う必要がある。
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.eth0.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.eth0.arp_announce = 2
参考情報としては、例えば


の「Real Servers」あたりを参照のこと。

さて、ここからが本題である。
上記の eth0 の部分が、VLAN 123 に対応するインタフェースで eth0.123 だったらどうなるか?

そのまま書いてしまうと、token の部分の要素のセパレータの . (ピリオド)と、eth0 と VLAN番号の間の . (ピリオド)が被っているので /proc/sys 以下にマッピングされるときに、

net.ipv4.conf.eth0.123.arp_ignore

なら

/proc/sys/net/ipv4/conf/eth0/123/arp_ignore 

と変換されてしまい、エラーになる。クォートもできないようだ。

そこで、man  sysctl を良く読んでみると、
SYSCTL(8)                    System Administration                   SYSCTL(8)
NAME
       sysctl - configure kernel parameters at runtime
SYNOPSIS
       sysctl  [options] [variable[=value]] [...]   sysctl -p [file or regexp]
       [...]
DESCRIPTION
       sysctl is used to modify kernel parameters at runtime.  The  parameters
       available  are  those  listed under /proc/sys/.  Procfs is required for
       sysctl support in Linux.  You can use sysctl to  both  read  and  write
       sysctl data.
PARAMETERS
       variable
              The  name  of  a key to read from.  An example is kernel.ostype.
              The '/' separator is also accepted in place of a '.'.

と書いてある。特に最後の赤字で強調した行に注目。
全体としてどういう仕様になるのか、いまいちあいまいなのだが、とにかく . (ピリオド)の代わりに / (スラッシュ)も使えるように見える。

実際やってみると、上記のパターンであれば、
net.ipv4.conf.all.arp_ignore = 1
net/ipv4/conf/eth0.1110/arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net/ipv4/conf/eth0.1110/arp_announce = 2
の2行目と4行目のように書いてやればよく、挙動としては、最初にあらわれた / (スラッシュ)か . (ピリオド)が、トークンの部分の要素のセパレータとして使われる。…ので、上記は正常に反映できるし、これは行ごとにばらばらでよい。

余談だが、sysctl は(Ubuntuでは) procps パッケージに含まれており、当該部分の処理は、sysctl.c の
 73 static void slashdot(char *restrict p, char old, char new)
 74 {
 75         int warned = 1;
 76         p = strpbrk(p, "/.");
 77         if (!p)
 78                 /* nothing -- can't be, but oh well */
 79                 return;
 80         if (*p == new)
の 76行目で strpbrk(3)関数に "/."  (スラッシュとピリオド)を渡しているところが対応する。
(procps のバージョンは 1:3.3.3-2ubuntu3)

実は、strpbrk なんて初めて知ったのだが、man してみると以下の通り。
STRPBRK(3)                 Linux Programmer's Manual                STRPBRK(3)
NAME
       strpbrk - search a string for any of a set of characters
SYNOPSIS
       #include <string.h>
       char *strpbrk(const char *s, const char *accept);
DESCRIPTION
       The  strpbrk() function locates the first occurrence in the string s of
       any of the characters in the string accept.
  :
第2引数の文字列に含まれる任意の文字が、第一引数に渡した文字列中に最初にあらわれる位置を返す…と。
いや、Cの標準ライブラリも奥が深いですね...

Sunday, January 13, 2013

L2 over SSH


このところ ssh tunnel ネタが多いが、実は ssh を使って port forwarding だけではなく、L3トンネルや L2トンネルを作れるのはそんなに知られていないように思う。実際、自分も Software Design 2012/10月号の、川本(@togakushi)さんの記事「SSH力をつけよう!」を読むまで知らなかった。

余談だが、この記事は本当にすばらしい。chef 特集につられて買った号だったのだが、編集部の気合いを感じた一冊だった。


まず、L3トンネルのやり方から。
port forwarding とは違い、今度はオプションに -L ではなく -w を使う。
例えば

  $ ssh -w 0:0   root@remote_host

とすれば、ローカルとリモートの双方に tun0 という tunnel device (tun) が作成され、
-w の指定値は、

  $ ssh -w any:any   root@remote_host

のように、any:any とすれば、tun1 とか tun2 とか、ローカルリモートそれぞれで tunnel device 番号の空きの若いものから自動的に採番してくれる。

こうして作成した tunX にそれぞれ適切に IP address を割り当てれば IP レベル(L3 レベル)で通信できるようになる。


ところで、この仮想化の時代、L3トンネルが張れるなら、L2 トンネルも張れないのか?と思うのが、人情だと思う。Software Design の記事では、スペースの関係か触れられていないのだが、実は、以下のように ssh のオプション指定によって、tunnnel device (tunX)だけでなく、Encapsulation が Ethernet に見えるような L2 tunnel、つまり tap も作成できる。

  $ ssh -w any:any  -o Tunnel=ethernet  root@remote_host

tap を作成する場合もやはり -w は必要で、指定値は tun の時と同じように使われる。
つまり、tap1 なり tap9 なりと(空いていれば)好きな番号の tap device を作れる。
(1/17追記)
なお、/etc/ssh/sshd_config や /etc/ssh/ssh_config に TunnelPermit や Tunnel 等の指定値が明示的に記述されていると強制的に tun になってしまうことがあるので注意のこと。


さて、実はここからが本題である。
下図のようにHOST1 と HOST2 があるとして、この2台の間は、proxy なり、NATなり、(多段)port forwarding なりの何らかの手段で ssh 接続可能だとする。(多段)port forwarding については前にも書いたので参考まで。

図の赤い線で結んだssh 接続の間には proxy があっても NATがあってもよい。とにかく、HOST1とHOST2の間でssh接続し、上で説明したオプションを指定して tap を接続してしまうことによって、仮想的に1枚のL2面を作れたことになる。(通信品質についてはいわずもがななので、そこはつっこまないように)

さらに図のように、作成した tap をbridge で VMの仮想NICに接続してしまえば、踏み台の向こうの環境で動いているVMに対して、L2レベルで接続できるので、例えば HOST2 から HOST1 上のVMに対してDHCPでアドレスを払いだす…等といったことも可能である。
(HOST2の構成が図とはちょっと違うのだが)実際やってみたら、問題なく動くし、やろうと思えば3ノード以上でフルメッシュ構成にするのも可能である。(ループが発生しないように若干注意が必要だが...)
世の中では、VXLAN だの STT だの NVGRE といった L2 over L3 技術や、SPB やら TRILL (やそれらの拡張)といった L2 over L2 技術で百花繚乱状態だが、こういう poor man's solution もあるよということで(笑)

Wednesday, January 9, 2013

sshトンネル作成時の -L 指定値

昨日も触れた ssh トンネルの張り方は、ぐぐると非常にたくさん解説が出てくるのだが、以下の図のように、作成したsshトンネルの入り口を仮想的にターゲットのサーバのようにみたてて使いたい場合、ぐぐってもあまり出てこない留意事項があるのでメモ。

「ローカルPC」から「乗り込み先」に tcp 443 (SSL)で接続したいのだが、「ローカルPC」上で tcp 443 が他アプリに使われているとか、複数人で共有したいため、sshトンネルの入り口は「踏み台(自拠点)」に開設したいということである。

ぐぐって出てくる一般的な解説では、上記のトンネル#1を開設する際に ssh に指定するパラメータは、

  -L 443:10.0.0.123:65443

ということになっている。(65443 は例)

しかしこれだと、図の「踏み台(自拠点)」マシン上で、 127.0.0.1:443 (tcp) で LISTEN 状態のTCPソケットが作成されてしまい、図の「ローカルPC」から接続できない。

さて、man ssh して -L の説明を読むと、こんなことが書いてある。

     -L [bind_address:]port:host:hostport

             Specifies that the given port on the local (client) host is to be
             forwarded to the given host and port on the remote side.  This
             works by allocating a socket to listen to port on the local side,
             optionally bound to the specified bind_address.  Whenever a con-
             nection is made to this port, the connection is forwarded over
             the secure channel, and a connection is made to host port
             hostport from the remote machine.  Port forwardings can also be
             specified in the configuration file.  IPv6 addresses can be spec-
             ified by enclosing the address in square brackets.  Only the
             superuser can forward privileged ports.  By default, the local
             port is bound in accordance with the GatewayPorts setting.  How-
             ever, an explicit bind_address may be used to bind the connection
             to a specific address.  The bind_address of ``localhost'' indi-
             cates that the listening port be bound for local use only, while
             an empty address or '*' indicates that the port should be avail-
             able from all interfaces.

赤字で強調した(省略可能な) [bind_address:] のところがポイントで、図のように使いたい場合は

  -L 0.0.0.0:443:10.0.0.123:65443

と、bind する IP address を明示的に指定してやる必要がある。
0.0.0.0 は言うまでもなく INADDR_ANY で、任意のIP addressにマッチする。
図の「踏み台(自拠点)」が複数のNICを持っていて、特定の IP に制限したい場合は、

  -L 192.168.100.2:443:10.0.0.123:65443

等と、適切な unicast IP address を書くこと。

さて、話はまだ続く。

     -L [bind_address:]port:host:hostport

の赤字強調した2番目の host には、一般的には接続先の IP address を書くと解説されているし、実際、man page の説明もそう読める。

しかし、ここはどんな IP address を書いても無視され、コマンドラインとしては

  $ ssh   -L [bind_address:]port:host:hostport    user@remote_host

と指定した時の remote_host (図のトンネル#1では「踏み台(他拠点)」のインターネット側IP addressに対応)が使われる。(ので、実は、どんな IP address を書いてもかまわない)

# Apr. 27, 2013 追記 begin

上記の記述に誤りがありました。
この記事では、


  $ ssh   -L [bind_address:]port:destination_host:hostport    user@remote_host

と記述した場合の destination_host と remote_host が同じサーバである場合を想定していました。
なので、この場合に限っては上に書いたとおりになるのですが、remote_host に中継させて、
さらに remote_host から IP reachable な別の destination_host に対して forwarding させることも
できます(というか、本来の SSH port forwarding の趣旨はこちらになります)
ですので、「さて、話はまだ続く」以下は間違っています。
destination_host の指定値には、とても重要な意味があります。

訂正してお詫びいたします...
# Apr. 27, 2013 追記 end





いや、紛らわしいですね...

Monday, January 7, 2013

ssh tunnel 越しに vSphere Client 接続する

昨年、こんな記事(http://thatsdone-j.blogspot.jp/2012/12/windows-netstat-ps.html)を書いたのだが、肝心の冒頭の件をまとめていなかったのでメモ。

いろんな案件で作業していると、多段の踏み台を経由しないと乗り込めない環境をあてがわれることも多い。もっと具体的には、自分の手元から IP reachable ではないが、ssh tunnel なら多段に張れないこともない…というような状況。

たぶん、FAQに近いような話だと思うのだが、そういう奥まったところで ESX/ESXi が動いている場合、どうやって vSphere Client で接続しようか?という話になる。

答は簡単で、
  1. tcp 443 -- 制御用
  2. tcp 902 -- VMのコンソール接続用
の2つのポートを ssh トンネルでつないでやればいい。
VMのコンソールをとる気がなければ tcp 443 だけでもOK。

ところで、ESXi に向かってどうやって ssh tunnel をはるか?

これも簡単で、管理用の ESXi shell を有効 + ssh を有効にしておいて、ssh で remote login できるようにしておくだけでOK。
ESXi shell 環境に向かって、tcp 443/tcp 902 の ssh tunnel を張っておけば vSphere Client でアクセスできるようになる。

Saturday, January 5, 2013

VMware Player での仮想ネットワークに関するあれこれ

手元での各種の作業に VMware Player を使っている人は多いと思う。

私は、最新の 5.0系より1つ古い 4.0.4 というバージョンを使っているのだが、
最近まで知らなかったことがあったのでメモ。

(1) 仮想ネットワークに nat を使っている場合にも static にIPを割りつけたい
 (たぶんこっちはFAQレベル)

 言うまでもなく、VMware (Player/Workstation等)の仮想ネットワークのNATでは、
 内蔵DHCPサーバからprivate IPをもらうことができ、VMからNATで外に出ていくことができる。

 ただ、いろいろいじっていると、VMに固定IPを割りつけたいこともよくある。
 VMware の内蔵DHCPサーバが覚えていて、同じIPを割り振ってくれることも多いのだが、
 何台もVMを作って起動・停止を繰り返していると、必ずしも必ず固定というわけにはいかない。

 単純には、一度内蔵DHCPから払いだされたIPをそのまま static に定義してしまえ…という
 乱暴な解決策が思いつくが、これはうまくいかない。NATで認識してくれない上に、
 ホスト側からも接続できなくなる。

 答は単純(…たぶんFAQで自分が知らなかっただけな気がする)で、
 内蔵DHCPサーバが払いだすIPのレンジは、NATで使えるIPのレンジのほぼ半分で、
 (通常は /24 なので第4オクテットホスト部が 129 以上の範囲から払いだされ、
 128以下のうち 1 (ホスト用) と 2 (NAT gateway用)は予約で、これら以外はゲストOS上で
 static に設定すれば普通にホストとの直接通信も、NAT経由の外部通信も可能になる。

 なお、根拠については、以下(の、特に p179 あたり)くらいしか見つからなかったのだが、

 http://www.vmware.com/jp/pdf/server_vm_manual.pdf
 
 このへんの仕様は不変だと思われる。
 (し、次の vmnetcfg の出力を見ても大丈夫だと思われる)


(2) 仮想ネットワークの設定をいじりたい

(2-1) 仮想ネットワーク設定操作用のツール(vmnetcfg)を追加インストールする

2.x系くらいの古いVMware Player の時代には、仮想ネットワークエディタというものが
標準でついてきていた。少なくとも私が使っている 4.0.4 ではインストールされないのだが、
実は配布アーカイブには同梱されており、追加インストール可能である。


情報源はこのへん↓(ぐぐると日本語でもそれなりに記事が出てくるようだ)

 http://communities.vmware.com/message/1853079


以下が手順。
  1. VMware Player の配布アーカイブを -e オプション付きで起動し、中身を取り出す

    C:\tmp>VMware-player-4.0.4-744019.exe -e c:\tmp\vmwp

    -e には展開先のフォルダを指定する。
  2. 出てきた network.cab に含まれる vmnetcfg.exe を取り出す
    C:\tmp>dir vmwp
     [略]
    2013/01/05  15:17    <DIR>          .
    2013/01/05  15:17    <DIR>          ..
     [略]
    2013/01/05  15:17         1,486,370 micros~2.cab
    2013/01/05  15:17           589,824 module_core.dll
    2013/01/05  15:17           356,352 module_ws.dll
    2013/01/05  15:17           216,071 nat.cab
    2013/01/05  15:17           207,483 net32.cab
    2013/01/05  15:17           173,955 net64.cab
    2013/01/05  15:16         2,855,112 network.cab  ★この cab をひらいて中身を取り出す
    2013/01/05  15:17        19,262,287 ovftoo~1.cab
    2013/01/05  15:17        15,132,466 ovftoo~2.cab
     [略]
                  48 個のファイル         242,418,155 バイト
                   2 個のディレクトリ  23,789,588,480 バイトの空き領域
  3. vmnetcfg.exe を VMware Player のインストールディレクトリにコピーする
    私の場合は以下。

    C:\Program Files (x86)\VMware\VMware Player

    VMware Player のインストールディレクトリにコピーせずに vmnetcfg.exe を単独で
    起動すると「sigc-2.0.dll が存在しない」という趣旨のエラーが出るので注意のこと。

(2-2) 仮想ネットワーク設定を変更する。

 以下のような画面がでてくれば(2-1)のインストール作業は正解である。
 あとは見てわかるとおりで、例えば、NATで使うIPの network address を変更するなど、
 わりと細かい設定変更も可能である。


 下段右のほうの「DHCP設定(C)」をクリックして出てくるダイアログが以下で、


 前半で説明した、DHCPで払いだす IP address のプールの指定(等)を変更できることがわかる。