Showing posts with label Linux. Show all posts
Showing posts with label Linux. Show all posts

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


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


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!" と自分で追加した処理が実行されていることがわかる。

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)

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, December 31, 2012

LinuxでNIC番号が思った通りにならない

よくあるトラブルのためのTIPSを1つ。

ディストロはRHEL系/Ubuntu系を問わず、表記のようなトラブルは多いと思う。

例えば、オンボードにEthernetが2port、拡張カードで 2port が搭載されているマシンにLinuxディストロをインストールするケースを考えよう。

この場合、ifconfig -a (等)でOSから見えるオンボードの2 port はそれぞれ eth0とeth1に、拡張ボード側は それぞれ eth2 と eth3 になってほしいと思うのが普通だと思う。

しかし、インストーラに任せておくと、往々にして以下のようなことが起こり、気付かないでいると、
正しく IP address 等を設定したはずなのに疎通がとれず、ハマることになる。

                               期待   実際
   オンボード1port 目   eth0   eth1
   オンボード2port 目   eth1   eth3
   拡張ボード1port 目  eth2   eth0
   拡張ボード2port 目  eth3   eth2

この場合、初期インストール時に udev が検出して ethX 等の名前を割りつけた順番がおかしいので、以下のファイルを直せばよい。

--------------------
認識順序がおかしかったものを手で修正後のサンプル。
[root@host08 ~]# cat /etc/udev/rules.d/70-persistent-net.rules
# This file was automatically generated by the /lib/udev/write_net_rules
# program, run by the persistent-net-generator.rules rules file.
#
# You can modify it, as long as you keep each rule on a single
# line, and change only the value of the NAME= key.
# PCI device 0x8086:0x105e (e1000e)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:15:17:96:fe:42", ATTR{type}=="1", KERNEL=="eth*", NAME="eth2" ★インストール直後は "eth0" になっていた。

# PCI device 0x14e4:0x165a (tg3)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:22:19:b0:ce:e6", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0" ★インストール直後は "eth1" になっていた。
# PCI device 0x8086:0x105e (e1000e)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:15:17:96:fe:43", ATTR{type}=="1", KERNEL=="eth*", NAME="eth3" ★インストール直後は "eth2" になっていた。
# PCI device 0x14e4:0x165a (tg3)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:22:19:b0:ce:e7", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1" ★インストール直後は "eth3" になっていた。
--------------------

ところで、ここで問題になるのが、オンボードNICに対応するデバイス(ドライバ)がどれなのかを確認する方法。

完璧ではないのだが、私はこんな感じでやっている。

まず、OSから認識されている Ethernet Controller の一覧を調べる。
lspci コマンドを使い、以下のように grep で適当にフィルタする。

----
[root@host08 ~]# lspci | grep Ethernet
01:00.0 Ethernet controller: Broadcom Corporation NetXtreme BCM5722 Gigabit Ethernet PCI Express
02:00.0 Ethernet controller: Broadcom Corporation NetXtreme BCM5722 Gigabit Ethernet PCI Express
05:00.0 Ethernet controller: Intel Corporation 82571EB Gigabit Ethernet Controller (rev 06)
05:00.1 Ethernet controller: Intel Corporation 82571EB Gigabit Ethernet Controller (rev 06)
先頭がバス番号/デバイス番号。
----
このケースではオンボード+拡張ボードで合計4portの Ethernet port があり、バス/デバイス番号が若いもの(≒オンボード)は Broadcom のチップ、増設は Intel のチップだということが分かる。

特に、例えばIntel等、同じベンダ製のNICが6portとか付いている場合、へたをするとデバイスドライバが全部一緒になってしまい、判別に困ることになるが、現在OS上で認識されている ethX デバイスが、物理的にどのバス/デバイス番号に対応しているのかは、以下のように ethtool で
調べることができる。

----
[root@host08 ~]# ethtool  -i eth0
driver: tg3 ★デバイスドライバ
version: 3.124
firmware-version: 5722-v3.08, ASFIPMI v6.02
bus-info: 0000:01:00.0  ★バス番号
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: no

[root@host08 ~]# ethtool  -i eth1
driver: tg3 ★デバイスドライバ
version: 3.124
firmware-version: 5722-v3.08, ASFIPMI v6.02
bus-info: 0000:02:00.0  ★バス番号
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: no

[root@host08 ~]# ethtool  -i eth2
driver: e1000e ★デバイスドライバ
version: 2.1.4-k
firmware-version: 5.6-2
bus-info: 0000:05:00.0  ★バス番号
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: no

[root@host08 ~]# ethtool  -i eth3
driver: e1000e ★デバイスドライバ
version: 2.1.4-k
firmware-version: 5.6-2
bus-info: 0000:05:00.1  ★バス番号
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: no


----


サーバ系では最近は Intel か Broadcom の2択になることが多いと思うが、ethtool -i の出力の
driver: 欄に表示されたデバイスドライバが、具体的にどんなベンダのデバイス(Ethernet controller)
に対応するのかは、modinfo コマンドで調べることができる。

以下のような感じ。


----
[root@host08 ~]# modinfo tg3
filename:       /lib/modules/2.6.32-343.el6.x86_64/kernel/drivers/net/tg3.ko
firmware:       tigon/tg3_tso5.bin
firmware:       tigon/tg3_tso.bin
firmware:       tigon/tg3.bin
version:        3.124
license:        GPL
description:    Broadcom Tigon3 ethernet driver
author:         David S. Miller (davem@redhat.com) and Jeff Garzik (jgarzik@pobox.com)
srcversion:     554D5ED929C129F90580555
alias:          pci:v000010CFd000011A2sv*sd*bc*sc*i*
  : 
[snip]
----
なお、キッティングまで自分で行う場合は、ボード上にMAC addressが書いてあることが多いので、ifconfig -a の出力の中に HWaddr  と表示されている MAC address をみて付き合わせるのが一番である。
余談までに、同じベンダの同じ型番のNIC(=ドライバも同じ)で清一にした場合は、この事象はおきない「はず」…なのだが、例外をご存じの方は教えてほしい…。
---- ★以下のサンプルの下位3バイトはサンプルとして手で書き換えてある。
[root@host08 ~]# ifconfig -a | grep HWaddr
eth0      Link encap:Ethernet  HWaddr 00:22:19:01:02:10 ←最下位バイト
eth1      Link encap:Ethernet  HWaddr 00:22:19:01:02:11 ←最下位バイト(上と連番になるはず)
eth2      Link encap:Ethernet  HWaddr 00:15:17:03:04:20 ←最下位バイト
eth3      Link encap:Ethernet  HWaddr 00:15:17:03:04:21 ←最下位バイト(上と連番になるはず)
----
まとめ
表記のようなトラブルが起きた場合、以下のようなコマンドを駆使しておちついて対処しよう。
 * エディタ  (/etc/udev/rules.d/70-persistent-net.rules を編集する)
 * lspci
 * ethtool -i
 * ifconfig -a
 * modinfo <driver name>
特に、「あれ?ちゃんと設定したのに通信できない…」と思った場合は、ifconfig -a をたたいてみて、HWaddress の並びを確認してみよう。サーバ機であって、2port のカードを増設している場合など、MAC address が2枚ずつきれいにそろっていない限り、この事象を疑ってみる価値がある。