抽象名前空間

Gaucheの勉強がてらD-Busを使ってみようとしたところ抽象名前空間(abstract namespace)なるものに悩まされたのでメモ。


Linuxでは2.2からUnixソケットで抽象名前空間というものをサポートしている。
これはソケットのパス名としてファイル名に依存しない文字列を使用するものでパス名の先頭には必ずNUL('\0')が来ることになっている。

D-Busではユーザ専用のsession busのパス名は環境変数DBUS_SESSION_BUS_ADDRESSに設定されている。例えば今現在自分の環境では

$ echo $DBUS_SESSION_BUS_ADDRESS
unix:abstract=/tmp/dbus-d5iRzoQzt9,guid=ec54977c5b2c90e40fef96314846acdf

となっており、unix:abstractの部分が抽象名前空間のパス名となっている。*1
上の例では実際のパス名は"\0/tmp/dbus-d5iRzoQzt9"になる。


さっそくGaucheで接続してみるが、

gosh> (use gauche.net)
#<undef>
gosh> (make-client-socket 'unix "\0/tmp/dbus-d5iRzoQzt9")
*** SYSTEM-ERROR: connect failed to #<sockaddr unix "">: Connection refused
Stack Trace:
_______________________________________

見事に失敗。
当初sockaddrのパスが空文字になっているのでGaucheがパスを正しく認識していないのかと考えGaucheのソースを眺めたみたが、表示が空文字になっているだけで内部的には正しくパス名を保持していた。
試しにCでも接続するコードを書いてみたが、こちらもConnection refused
途方に暮れていたところunix(7)に気になる記述を見つける。

sun_path が NULL バイト ('\0') で始まる場合、これは Unix プロトコルモジュールで管理されている抽象名前空間を参照する。この名前空間におけるソケットのアドレスは、 sun_path の残りのバイトで表される。抽象名前空間に置ける名前は NULL で終端されていないことに注意。

NULLで終端されていない??ではどうやって関数connectは正しいパス名を得るというのか。
いろいろ悩んで試行錯誤の末ついに判明。Cでは通常sockaddr_un型のaddrに対しては次のようにconnectを呼ぶことになっている。

connect(sock, (const struct sockaddr*)&addr, sizeof(addr));

しかし抽象名前空間の場合はこれではだめで、connectの第3引数にはaddrの先頭からパス名の最後のバイトの長さを渡してやらないといけなかったらしい。
つまりこういうこと。

/* addr.sun_path[] = "\0/tmp/dbus-d5iRzoQzt9\0\0\0\0...." */
connect(sock, (const struct sockaddr*)&addr, sizeof(addr) - sizeof(addr.sun_path) + strlen(&addr.sun_path[1]) + 1/*先頭のNUL分*/);

これでようやく抽象名前空間に接続ができた。


Gaucheでも抽象名前空間アドレスの長さを修正してやったところ無事に接続できるようになった。以下パッチ。

diff -ur Gauche-0.8.13.orig/ext/net/addr.c Gauche-0.8.13/ext/net/addr.c
--- Gauche-0.8.13.orig/ext/net/addr.c   2007-09-30 17:44:48.000000000 +0900
+++ Gauche-0.8.13/ext/net/addr.c        2008-05-22 23:43:40.000000000 +0900
@@ -136,7 +136,8 @@
 {
     ScmObj path = Scm_GetKeyword(key_path, initargs, SCM_FALSE);
     ScmSockAddrUn *addr;
-
+       int addrlen = 0;
+
     if (!SCM_FALSEP(path) && !SCM_STRINGP(path)) {
         Scm_Error(":path parameter must be a string, but got %S", path);
     }
@@ -156,8 +157,14 @@
         }
         memcpy(addr->addr.sun_path, cpath, size);
         addr->addr.sun_path[size] = '\0';
+
+               /* abstract namespace path */
+               if('\0' == addr->addr.sun_path[0]){
+                       addrlen = sizeof(struct sockaddr_un)
+                               - sizeof(addr->addr.sun_path) + size;
+               }
     }
-    addr->addrlen = sizeof(struct sockaddr_un);
+    addr->addrlen = (addrlen > 0)? addrlen : sizeof(struct sockaddr_un);
     return SCM_OBJ(addr);
 }

しかし先頭のNULで処理わけるのがきもちわるいなぁ。。。

*1:unix:abstractではなくunix:pathになっているときは通常のパス名…のはず