2015年11月18日水曜日

impylaでqmark置換がうまく動かない

データベースで遊ぶならImpalaどうですか?と言われ使ってみることに。

いずれはPythonでいろいろするつもりだったので、Clouderaが提供しているPythonバインディングであるimpylaモジュールの使い勝手の確認も兼ね、手元のSqlite3データベースから行単位でレコードをコピーしていくPythonプログラムを書いてみました。ちなみに全レコードを単に載せ替えるだけなら、CSVなりに変換して直接HDFSからインポートする方が100万倍(未計測)速いです。

テーブルの構成は単純で、(INT, TIMESTAMP, STRING, STRING) の4カラムのみです。ところが、このレコードをimpyla経由で追加しようとしてもどうしてもうまくいかない。

cursor.execute('insert into table1 values (?,?,?,?)', (intvalue1, datetimevalue1, stringvalue1, stringvalue2))

どこにも間違う要素のない処理なのですが、必ずエラーが発生してしまいます。調べてみると、Impalaへ投げるSQL文を構築する途中で、datetimevalue1で置換されるべき部分が想定しない値になっていることが判明しました。

impylaモジュールは、他のPython用データベースアクセスモジュールと同様、Python DB-API 2.0に準拠しています。execute()の第一引数で渡したSQL文の?が第二引数で渡した値に置き換えられる、という部分はその仕様の一部であり、qmark paramstyleと呼ばれる書式です。SQL文の文字列置換の方式には幾つかあり、%sで指定した場所が置き換えられるformat paramstyle、:1:2など第二引数の位置に応じて置き換えられるnumbered paramstyle、:nameと指定してnameをキーとする辞書を第二引数に渡すnamed paramstyleなどがあります。(もう一つ、pyformatという書式がありますが、これはimpylaでは対応していません)

qmark、format、numbered paramstyleでの置換は、impyla/interfaces.py_replace_numeric_markers()関数で実装されていますが、ここが問題でした。元の関数には以下のような記述があります。

operation = replace_markers('?', operation, string_parameters)
operation = replace_markers(r'%s', operation, string_parameters)
for index in range(len(string_parameters), 0, -1):
    operation = operation.replace(':' + str(index),
                                  string_parameters[index - 1])

operationにはexecute()の第一引数の内容が、string_parametersには第二引数で指定されたタプルを文字列リストに変換した情報が入っています。

?で指定された部分を置き換え、続いて%sで指定された部分を置き換え、さらに:数値で指定された部分を置き換える、という処理を意図したものだと思われますが、明らかに変です。これでは、置換結果に別の書式の置換マークが含まれていた場合に正しい結果が得られません。例えば、qmarkで置換した文字列に%s:1などの部分文字列が含まれていると、その後の置換処理で意図しない置換が起きてしまいます。僕のコードが正しく動作しなかったのも、時刻文字列に:数値が含まれていたため、時刻の一部が再置換されてしまったことが理由でした。

_replace_numeric_markers()にはもう一つ問題があります。関数内部で入れ子定義されているreplace_markers()関数の処理はおおよそ以下のようになっています。

def replace_markers(marker, op, parameters):
    marker_index = 0
    while op.find(marker) > -1:
        op = op.replace(marker, parameters[marker_index], 1)
        marker_index += 1

これも場合によっては正しく動作しません。置換マークをfind()メソッドで検索しているため、置き換えた後の文字列に、置換マークが含まれていると再度マッチしてしまうためです。

上記の問題を修正するためのパッチを作ってpull requestしてみました。改善されるといいなぁ。

ちなみに、named paramstyleを用いた場合は別の関数で処理されるようになっており、そちらは問題なさそうです。

2015年1月22日木曜日

Python SimpleXMLRPCServerでHTTP/1.1による永続接続

え?まだ1.1使ってるの?いやいやその前に今時XMLRPCなの?という本質的な疑問はとりあえず横に置いておいて。

XMLRPCを使ったプロトタイプをPythonで作る場合、Python付属のSimpleXMLRPCServerxmlrpclibを使うのが一番簡単な方法ではないかと思います。Pythonのドキュメントにもこれらのライブラリを使ったXMLRPCサーバーおよびクライアントの解説があります。

ドキュメントに書かれている内容に従ってサーバーを実装すると、HTTPバージョンとして1.0が使われます。もちろん、普通に動作するのですが、RPCの度にTCP (HTTP) 接続を確立するので効率が気になります。短時間に大量のRPCを発行する場合は特にそうです。HTTPバージョン1.1では一度確立したHTTP接続で何度でもコマンドのやりとりが出きるので、できればHTTPバージョン1.1を使いたいと思っていたのですが、軽く検索してみてもなかなかこれといった情報が見つからず、かといって自前でHTTPバージョン1.1対応するのも面倒で放置していました。

先日Pythonのドキュメントを見ていたら、BaseHTTPRequestHandlerprotocol_versionという変数があり、これをHTTP/1.1に設定することでBaseHTTPRequestHandlerを使っているSimpleHTTPServerなどがHTTPバージョン1.1対応になるという記述を公式ドキュメントに発見しました。対象ライブラリの説明だけでなく、きちんと親クラスの説明も読まないとダメですね。SimpleXMLRPCServerもPRCリクエストの処理にBaseHTTPRequestServer由来のSimpleXMLRPCRequestHandlerを使っているのでなんとかなりそうです。


from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler

class HTTP11SimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
    protocol_version = 'HTTP/1.1'

class RPCMethods(object):
    def add(self, a, b):
        return a + b

server = SimpleXMLRPCServer(('', 8000), requestHandler=
HTTP11SimpleXMLRPCRequestHandler)

server.register_instance(RPCMethods())
server.serve_forever()


これでめでたくXMLRPCサーバーがHTTPバージョン1.1で応答するようになりました。xmlrpclibを用いたクライアントの方は、どうやら標準でHTTPバージョン1.1を使うらしく、サーバー側を対応するだけで永続接続が使えるようになります。

検索してでてこないということは、誰も使っていない (あるいは当たり前すぎて質問もされない) ということかもしれませんが、将来の自分のためのメモとして残しておきます。


2015年1月13日火曜日

Force10にOpen Network Linuxをインストール (その2)

Open Network Linux (ONL) で配布されている公式バイナリは、現在のところパケット転送機能を提供していません。これは2014年5月に開催されたOpen Compute Engineering WorkshopでBig Switch NetworksのRob Sherwood氏の資料にも明記してあります (14ページ)。実際、ONLのバイナリをForce10にインストールしただけの状態では管理用のイーサネットポートのみしか見えていなかったのは前回の記事に書いた通りです。

ハードウェアレベルでパケット転送をするためのルール設定にはASICとの通信が必要になります。通常、ASICへのアクセス情報を入手するためにはNDAの締結が必要になるため、ハードウェアアクセス部分を切り離してオープンソース化されているようです。

Rob Sherwood氏の資料によれば、以下の3種類のパケット転送モジュールが (2014年5月時点での) 近日中にリリースされると書かれています。

  1. Indigo2ベースのオープンフローエージェント
  2. OF-DPAベースの転送エージェント
  3. おもちゃの転送エージェントOpen Route Cache (ORC)
このうち、ORCは最新のONL配布物に含まれており、基本的な機能しか使えないものの、ONL箱をルータとして使うことができます。OF-DPAに関しては、githubで資料と参照コードが公開されているものの、現時点では簡単に利用できるものではなさそうです。

ORC自体のソースコードは公開されているのですが、Force10が採用しているBroadcomのASICにアクセスするためのライブラリはバイナリ状態でのみ提供されています。ORCはそのライブラリを動的にリンクし、ASICの操作を実現しています。

以下の手順でORCを起動します。

root@onl-powerpc:~# orc --daemon

ifconfig (もしくは ip link) コマンドでインターフェースを確認してみると、orcXXという名前で52個のインターフェースが作成されていることが確認できます。インターフェースにアドレスを設定すれば、他の機器と通信できます。また、ip routeコマンドなどで経路を設定すればパケット転送もできます。ハードウェア転送されていることを確認するには、tcpdumpコマンドでインターフェースをモニターしてみるとよいでしょう。Linuxカーネルにパケットが上がってこないため、tcpdumpでは転送されたパケットを観測することができないはずです。

ORCはLayer 3のパケット転送を補助するもので、Layer 2の機能は利用できません。スイッチ機器では複数のポートをひとつのブリッジにまとめて利用することが多いと思いますが、ORCではブリッジ設定ができません。残念ながら実用的な処理をさせることは難しいでしょう。

ORCのソースコードを見てみると、どうやって経路制御プログラムとASICの間で情報をやりとりすればよいかがうっすらと見えてきます。ORCでは、カーネルの経路制御操作時に発行されるNETLINKメッセージを観測し、インターフェースのアドレス情報、転送に必要となるFIBの情報などを適宜ASICライブラリを経由してハードウェアに落とし込んでいます。ASICライブラリはORC専用のもので、汎用的なアクセスライブラリが提供されているわけではないため、同じASICライブラリを別の用途で再利用するのは困難だと思われます。やはり本格的なスイッチを作ろうと思ったら、専用のアクセスライブラリを自作する必要があるようです。

Open Network Linuxは、ダウンドードしてきてそのまま使うソフトウェアというよりは、スイッチベンダー、SDN、NFV開発ベンダーが独自の機能を持った高性能なパケット転送装置を自作するための部品として考えるのが良さそうです。運用部分に慣れ親しんだLinuxを使い、なおかつ必要に応じてハードウェアアクセラレーション機能を組み込める箱が必要な場合には魅力的な選択肢になりそうでうs。

2014年12月8日月曜日

Force10にOpen Network Linuxをインストール

DELLのONIE版Force10Open Network Linux (ONL)を入れてみました。当然サポート対象外になりますので、ご自分の責任において実行しましょう。

ONIE版Force10だと、Cumulus Linuxを使っている人が一般的(?)かと思いますが、同じONIE準拠ならONLも入るんじゃね?と言われ試してみることに。Force10にはx86系を使ったS6000-ONとPowerPCを使ったS4810-ONがあります。ONLでプリビルドされて配布されているものはPowerPC用になるので、S4810-ONが対象です。

素の状態のONIE版Force10なら、最初に起動したときにONIEプロンプトに落ちるはずですが、一旦Cumulus Linuxをインストールしてしまうと、そちらからブートしてしまいます。まずはブートプロンプトに落とすため、U-Bootのイメージブート待ちカウントダウンの段階で、なにかキーを押してU-Bootプロンプトに落とします。

U-Bootには、ONIEを実行するための環境変数がいくつか事前に設定されているので、それを使ってONIEを起動します。

dni_7448-> run onie_bootcmd
(ONIEブートメッセージ)
ONIE:/ # 

ONIEプロンプトが出てきたら、ONLのインストール手順に従って作業を進めます。

ONIE:/ # wget http://opennetlinux.org/binaries/latest.installer
latest.installer 100% |*******************************| 188M 0:00:00 ETA
ONIE:/ # sh latest.installer
(インストールログメッセージ)
Partition Map for MMC device 0 -- Partition Type: DOS
Part Start Sector Num Sectors UUID Type
1 16 31280 00000000-01 83
2 31296 125056 00000000-02 83
3 156352 15883584 00000000-03 83
reading onl-loader
6775021 bytes read in 356 ms (18.1 MiB/s)
WARNING: adjusting available memory to 30000000
## Booting kernel from Legacy Image at 10000000 ...
Image Name:
Image Type: PowerPC Linux Multi-File Image (gzip compressed)
Data Size: 6774957 Bytes = 6.5 MiB
Load Address: 00000000
Entry Point: 00000000
Contents:
Image 0: 3384879 Bytes = 3.2 MiB
Image 1: 3382017 Bytes = 3.2 MiB
Image 2: 8041 Bytes = 7.9 KiB
Verifying Checksum ... OK
## Loading init Ramdisk from multi component Legacy Image at 10000000 ...
## Flattened Device Tree from multi component Image at 10000000
Booting using the fdt at 0x10674184
Uncompressing Multi-File Image ... OK
Loading Ramdisk to 2fcc6000, end 2ffffb01 ... OK
Loading Device Tree to 03ffb000, end 03ffff68 ... OK
setup_arch: bootmem
delta_7448_setup_arch()
arch: exit
Open Network Linux Loader [Open Network Linux 48f7842 (powerpc,2014.10.05.03.03,
48f7842bf909c43a4840e8d827e55edc113c299e)]
Open Network Linux installer [Open Network Linux 8f3e53e (powerpc.all,2014.11.04
.22.55,8f3e53ebcdcd5edea042e15ec852c904a6cafd6c)]

Press Control-C now to enter loader shell
Booting flash2:onl-powerpc.swi
FDT_ERR_NOSPACE: Unable to add reserve for cpu-release-addr!
Starting new kernel
setup_arch: bootmem
delta_7448_setup_arch()
arch: exit
INIT: version 2.88 booting
Using makefile-style concurrent boot in runlevel S.
Starting the hotplug events dispatcher: udevd.
Synthesizing the initial hotplug events...done.
Waiting for /dev to be fully populated...done.
Setting up block and net devices...done.
Setting kernel variables ...done.
Setting up sensors configuration...done.
Setting up resolvconf...done.
Configuring network interfaces...done.
Restoring persistent files...done.
Creating SSH2 RSA key...done.
Creating SSH2 DSA key...done.
Creating SSH2 ECDSA key...done.
INIT: Entering runlevel: 2
Using makefile-style concurrent boot in runlevel 2.
Not starting fancontrol; run pwmconfig first. ... (warning).
Starting enhanced syslogd: rsyslogd.
Starting Fault Agent: faultd.
Loading empty startup-config...failed.
Starting network management services: snmpd.
Starting OpenBSD Secure Shell server: sshd.
Open Network Linux 8f3e53e (powerpc.all,2014.11.04.22.24,8f3e53ebcdcd5edea042e1
5ec852c904a6cafd6c)
onl-powerpc login:
標準のブートイメージでは、ハードウェアに搭載されているスイッチポートは使えません。別途ライセンスを結ぶなどしてチップドライバーを組み入れる必要があります。素のONLをインストールした直後に見えているインターフェースは以下のものでした。

root@onl-powerpc:~# ifconfig -a
dummy0    Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX
          BROADCAST NOARP MTU:1500 Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1 Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING MTU:65536 Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:456 (456.0 B) TX bytes:456 (456.0 B)

ma1       Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX
          BROADCAST MULTICAST MTU:1500 Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
          Base address:0xa000

sit0      Link encap:IPv6-in-IPv4
          NOARP MTU:1480 Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

ひとまず起動はしたということで。

2014年4月16日水曜日

OpenStackでNative VXLANテナントを使う

久方ぶりの更新でBloggerの使い方も忘れてるくらいの勢いです。

しばらく前からOpenStackなどが流行っているようですが、すっかり流れに乗り遅れてしまいました。諸般の事情により、最近ようやくOpenStackに触り始めています。とはいうものの、いまさらOpenStackの導入とか書いてもネットの海に埋もれるだけです。なので、まだあまり見かけないNative VXLANをテナントネットワークとして使う設定を、自分のメモという意味も含めて記録しておこうと思います。

ここではOpenStackの導入については「全く」述べません。そのあたりは、巷に溢れているブログなりを参考にしていただければと思います。もっとも、公式のドキュメントがかなりよくできているので、ゼロから一歩一歩導入を試したい人はそちらをご覧になることをお勧めします。簡単に導入したい人向きにはRed Hat Inc.のRDOなども人気が高いようです。

Native VXLAN (以後、単にVXLANと呼びます) をテナントネットワークとして使うためには、いくつか準備が必要です。

  • iproute2がVXLANに対応している
  • NeutronでML2プラグインが使え、かつML2プラグインがVXLANに対応している
以上の条件を整えます。ML2プラグインは最新のOpenStackなら設定するだけで使えるはずです。ML2プラグインへの切り替えについては、あまり情報がないので、簡単にメモを残します。

まず、/etc/neutron/neutron.confでML2プラグインを利用するように設定します。

core_plugin = neutron.plugins.ml2.plugin.Ml2Plugin
service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin


続いて、/etc/default/neutron-serverneutron-serverが起動時に読み込む設定ファイルをML2プラグインにします。(Ubuntu 12.04の場合)

NEUTRON_PLUGIN_CONFIG="/etc/neutron/plugins/ml2/ml2_conf.ini"

これらの設定は、今後OpenStackでは標準設定になっていくと思われます。Icehouse以降では設定不要になっているかもしれません。

VXLANでテナントネットワークを構成するためには、L2エージェントとしてLinux Bridgeエージェントを使う必要があります。世にある情報の多くは、Open vSwitchエージェントとGREトンネルによるテナントネットワークの構成例となっているようですので、NeutornのL2エージェントを導入する際に気をつけてください。

ML2プラグインの設定ファイル/etc/neutron/plugins/ml2/ml2_conf.iniは以下のようになります。

[ml2]
tenant_network_types = vxlan,flat
type_drivers = vxlan,flat
mechanism_drivers = linuxbridge

[ml2_type_flat]
flat_networks = public0

[ml2_type_vxlan]
vni_ranges= 1000:2000

[securitygroup]

firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver

Linux Bridgeエージェントの設定ファイル/etc/neutron/plugins/linxbridge_conf.iniは以下のようになります。

[linux_bridge]
physical_interface_mappings = public0:eth0
[vxlan]
enable_vxlan = True
local_ip = (ノードのIPアドレス)
[securitygroup]
firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver

なお、ML2プラグインの設定はneutron-serverが動作するノード (通常は管理ノード) のみで大丈夫です。またLinux Bridgeエージェントの[linux_bridge]セクションの設定はゲートウェイとなるノード (L3エージェントやDHCPエージェントを動作させているノード) で設定するだけで大丈夫です。eth0はゲートウェノードのインターネット側の物理インターフェース名です。コンピュートノードには[vxlan]セクションの設定だけを入れておきます。

VXLANを使う上で注意しなければならないことは、VXLANのカプセル化のため、ペイロードが50バイト小さくなることです。Linuxで普通にVXLAN仮想インターフェースを構成すると、MTUが1450になっていると思います。このままだと、OpenStackの仮想ブリッジ内でMTU 1500のインターフェースと1450のインターフェースが混じることになり、パケットが通らない場合が発生します。これを回避するため、ゲートウェイノードとコンピュートノードが通信する物理インターフェースのMTUをあらかじめ1550に設定しておく必要があります。

テナント共用となる外部ネットワークを作成する場合、外部ネットワークをflatタイプで構成します。以下は外部ネットワーク名をexternal1として、external1-subnet1というサブネットに172.16.0.0/24が割り当てられている場合の構成例です。

neutron net-create external1 \
        --tenant-id ${SERVICE_TENANT_ID} \
        --provider:network_type flat \
        --provider:physical_network public0 \
        --router:external=True
neutron subnet-create external1 172.16.0.0/24 \
        --name external1-subnet1 \
        --tenant-id ${SERVICE_TENANT_ID} \
        --gateway 172.16.0.1 \
        --allocation-pool start=172.16.0.64,end=172.16.0.254 \
        --disable-dhcp \
        --dns-nameserver 172.16.0.1

テナントネットワークはVXLANで構成します。外部ネットワークが作成されていれば、テナントネットワークはDashboardからも作成できますが、コマンドラインで実行する場合は、例えば下のようになります。以下の例は、テナントネットワークとしてdemo-private1を構成し、demo-private1-subnet1という名前で10.0.0.0/24を割り当てる場合の構成例です。

neutron net-create demo-private1 \
        --tenant-id ${TENANT_ID} \
        --provider:network_type vxlan \
        --provider:segmentation_id 1000
neutron subnet-create demo-private1 10.0.0.0/24 \
        --name demo-private1-subnet1 \
        --tenant-id ${TENANT_ID}


2010年12月24日金曜日

App Inventorを使ってみた

Google Japan Developer Relations BlogでApp Inventorが公開βになったと紹介されていたので使ってみました。ちょうど、最近ブログの更新もさぼっていたし。

とりあえず、加速度センサーの値を表示させるアプリを作ってみます。iPhoneでも昔作ったけど、まぁそれなりに手間のかかるコーディングが必要でした。App Inventorでは、なるほど広告通り、あっという間にアプリが出来上がります。一昔前に流行った(いや、流行り損ねた?)Java Beansを彷彿とさせるプログラミング。

まず手元のコンピュータに必要なツール類をインストールする必要があります。ツールはApp Inventor Setupページからダウンロードしましょう。今のところ、Mac、Linux、Windowsをサポートしているようです。あと、Java環境も必要です。SetupページにJava環境のチェックができるページへのリンクが張られているので、手元のコンピュータのJava環境を整備しておきます。

次にAndroid端末の設定をします。エミュレータも提供されているようですが、ちょうど手元にNexus Oneがあるので、これを使うことにします。端末側の設定としては、「提供元不明のアプリ」を許可し、「USBデバッグ」モードをオン、「スリープモードにしない」をオンに設定するだけです。なお、コンピュータと接続中にUSBストレージモードになっているとうまく動作しないため、接続直後にストレージモードになる設定をしている場合は、コンピュータに接続後に手動でストレージモードをオフにする必要があります。

あとは、App Inventorホームでプログラム開始です。とりあえず加速度センサーの値が表示されればいいので、以下のような画面をデザインしました。



テーブルビューを配置して、その中にラベルを6つはめ込んだだけです。あと、不可視コンポーネントとしてAccelerometerSensor1を追加しています。

次に、これらの間の関係を定義するために、画面右上の(ちょっと切れていますが)Open the Blocks Editorをクリックしてブロックエディタを起動します。ここでビジュアルにプログラムするのですが、途中は省略して、最終的に以下のようなブロックダイアグラムを作ります。



AccelerometerSensor1.AccelerationChangedは、加速度センサーの値が変更されたときに実行されるモジュールですので、その中で、センサーの値を表示している3つのラベル(X、Y、Z軸)に各軸のセンサーの値を設定するだけです。

コンピュータに実端末を接続している場合は、ブロックエディタでConnect to Deviceをクリックすると、自動的にプログラムが走り始めます。

プログラムに問題がなければ、App Inventorのページに戻って、Package for Phoneをクリックすると、今作ったプログラムが端末に転送、インストールされます。

まぁ、とても簡単なのですが、果たしてこれ、どれくらいまで細かい内容をプログラムできるのか、もうちょっと遊んでみないと分からないですね。

2010年4月13日火曜日

IPv4 to IPv6, IPv6 to IPv4 translator

ちょっとした必要に迫られて、IPv4とIPv6のプロトコル変換プログラムを作りました。動作は至ってシンプルで、あるひとつのIPv6アドレスを、別のユニークなIPv4アドレスに変換(また、その逆変換)を行うものです。最近流行っている、NAT64のような、複数のIPv6ノードがひとつのIPv4アドレスを共有して通信するものとは異なります。

そもそもの目的は、IPv6のみを使って構築運用されているネットワークの内部ノードを、外部に公開することでした。もちろん、IPv6を使えば、普通に外部からアクセスできるのですが、まだまだIPv6の利用が一般的ではない現在、IPv4での公開も考えなければなりません。こうした目的の場合、一般的にはIPv4とIPv6のデュアルスタック環境にするところです。しかしながら、必ずしもすべてのIPv6ノードがIPv4でアクセスできる必要はありません。運用されているIPv6ノードの中で、サーバとして公開されるものは極わずかであり、多くのノードはそのサーバのサポートをしているだけだからです。そのようなノードに、貴重なIPv4アドレスを割り当てるのはもったいないですし、またデュアルスタックにすることで運用のコストも大きくなりかねません。

そこで、公開サーバの数を収容するために必要十分なIPv4アドレスを用意して、単純に1対1で対応させてしまおう、ということになります。言ってみれば、IPv6への移行が完了するまでの、つなぎの技術ですね。

仕組み自体はNAT64とDNS64のものとほとんど変わりません。違いはトランスポート層のポート番号の変換処理が必要かどうかです。アドレスを1対1で対応させる場合、ポート番号を処理する必要がないので、変換サーバがNATのような状態を持つ必要がなくなります。状態を持たなくてよいので、負荷分散あるいは耐障害性の向上のために、簡単に多重化できるという利点があります。欠点はNAT64に比べてたくさんのIPv4アドレスが必要になることです。なにせ1対1対応ですから。

実装はtunデバイスを使っています。最近のOSなら標準でサポートしているものも多いのではないでしょうか。tunデバイスを使えば、read(2)システムコールとwrite(2)システムコールで生のIPパケットを読み書きできるので、今回のような目的にぴったりです。ユーザ空間で実装できるので、開発やデバッグも簡単になります。当然、転送速度は遅くなると思いますが、開発の簡単さ、もともとの目的がつなぎの技術であることを考えると、そう悪い選択でもないでしょう。

動作はちょっと複雑です。まず、IPv6ノードがIPv4ノードにアクセスする場合から考えてみます。IPv6ノードはIPv4アドレスを持っていないため、まずIPv4アドレスとIPv6アドレスの対応を定義します。ここでは例として、IPv6ネットワークのプレフィックスを2001:db8:0:0::/64、IPv4グローバルアドレス空間を192.0.2.0/24と仮定しましょう。以下のようなアドレスの対応を定義します。

  • 192.0.2.1 <=> 2001:db8:0:0::100

つまり、外部のIPv4ノードが2001:db8:0:0::100と通信したいと思ったときは、192.0.2.1に対して接続すればよいということになります。192.0.2.1に届いたIPv4パケットは、IPv6に変換されて2001:db8:0:0::100に転送されます。このとき、変換後のIPv6パケットの始点アドレスには、通信相手のIPv4アドレスが識別できるよう、IPv6アドレスの一部にIPv4アドレスを埋め込んでおきます。始点アドレスとして使うIPv6プレフィックスはなんでもよいのですが、仮に64::/64を使うとしましょう。外部のIPv4ノードのアドレスが202.214.86.196だった場合、変換後の始点アドレスは64::cad6:56c4となります。IPv4アドレス4バイト分が、IPv6アドレスの下4バイトに埋め込まれた形になります。

逆方向の通信には、上記の手順を逆に適用します。2001:db8:0:0::100から64::cad6:56c4に対してパケットを送信すると、変換サーバでIPv6宛先アドレスの下4バイトからIPv4宛先アドレス202.214.86.196を取り出します。また、あらかじめ定義されたIPv4とIPv6アドレスの対応表から、始点アドレスとして使うIPv4アドレス(この場合は192.0.2.1)を取り出し、IPv4パケットとして転送するのです。

コードはgithubで公開しています。興味のある方は http://github.com/keiichishima/map646 にアクセスしてみてください。forkも大歓迎です。