2007/04/16(月)Debian 4.0(amd64) で Firefox が固まる問題

iceweasel ですが、about(iceweaselについて)を開くだけでも、ブラウザが固まってしまうという状況でまったく使いものになりませんでした。

原因は標準の日本語入力フロントエンドを削除して SCIM+Anthy をいれていたせいで、SCIMが固まりアプリが落ちたのが原因。

SCIMのフロントエンド設定から「全てのアプリケーションで同一入力メソッドを使用」のチェックを外すことで解決しました。本来、about などでは日本語入力がオフになっているようで、このチェックをいれておくと無理矢理 Anthy が起動しようとして不整合が起こっていたようです。

2007/04/05(木)PL2303X + Linuxで高速通信はできるか

FT232RLはとりあえず諦めて、PL-2303Xのドライバの動作を検討しました。

パッチを当ててインストール

Linuxソースの drivers/usb/serial/pl2303.c がデバイスドライバです。カーネルを再構築するのは大変なので、FT232のデバイスドライバから Makefile と Rules.make を拝借し、適当に書き換えてカーネルモジュールを作り増した。

1Mbpsは対応していませんので、ドライバをごにょごにょと書き換えます。

baud = 0;
switch (cflag & CBAUD) {
	case B0:	baud = 0;	break;
	case B75:	baud = 75;	break;
	case B150:	baud = 150;	break;
	case B300:	baud = 300;	break;
	case B600:	baud = 600;	break;
	case B1200:	baud = 1200;	break;
	case B1800:	baud = 1800;	break;
	case B2400:	baud = 2400;	break;
	case B4800:	baud = 4800;	break;
	case B9600:	baud = 9600;	break;
	case B19200:	baud = 19200;	break;
	case B38400:	baud = 38400;	break;
	case B57600:	baud = 57600;	break;
	case B115200:	baud = 115200;	break;
	case B230400:	baud = 230400;	break;
	case B460800:	baud = 460800;	break;
	default:
		err ("pl2303 driver does not support the baudrate requested (fix it)");
		break;
}
baud = 1000000;
dbg("%s - baud = %d", __FUNCTION__, baud);
if (baud) {
	buf[0] = baud & 0xff;
	buf[1] = (baud >> 8) & 0xff;
	buf[2] = (baud >> 16) & 0xff;
	buf[3] = (baud >> 24) & 0xff;
}
<中略>
i = usb_control_msg (serial->dev, usb_sndctrlpipe (serial->dev, 0),
		     SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, 
		     0, 0, buf, 7, 100);

ソースをみれば分かりますが、このデバイスはかなり変わっていて、通信速度(Baud rate)を直接デバイスに送る(設定する)仕組みになっています(普通は分周カウンタの値を送る)。

さてこれで通信……としてみたものの、データがすべて化けてしまって全くだめでした。100Kbpsでもダメでした。これは通信速度が合っていないときに起こる症状です。どうやら、うまく分周されていないようなのですが、データシートには 6Mbps まで対応と書かれています。

原因

ここでみつけたPL-2303の資料(PDF)(Xなし)によれば(表3の少し手前)、

The programmable baud rate generator supports baud rates up to 1.2M bps

とのことで、どうやらドライバが PL-2303X に対応しておらず、PL-2303互換デバイスとして使用しているためではないかと思うのですが。つまり、PL-2303Xのデバイスドライバをみつければよいのではないかと……。

訂正。PL-2303X資料(pdf)によれば、高速な baud として設定可能な値は

921600, 1228800, 2457600, 3000000, 6000000

だそうです。微妙に使えない PL-2303X。

2007/04/02(月)FT232RL を Linux kernel 2.4.17 で認識させる

FTDI製FT232RL(FT232R)とは、3Mbpsまでの速度に対応した高速USBシリアルチップ(Serial Conveter)です。USBに接続するだけで、TTLレベルで高速シリアル通信が実現できます。

デバイスドライバの組み込み

ドライバ配布サイトでWindows用、Mac用、Linux用のドライバが配布されています。Linux kernel 2.4.20以降には標準でドライバが組み込まれていますが、2.4.17には組み込まれていません。

実験中ドライバの利用

Kernel 2.4.17にはFTDI USB Serialのドライバが実験中として組み込まれています。

USB support
 -> USB Serial Converter support
   -> <*> USB FTDI Single Port Serial Driver (EXPERIMENTAL)
usbserial.c: FTDI 8U232AM converter detected
usbserial.c: FTDI 8U232AM converter now attached to ttyUSB0 (or usb/tts/0 for devfs)

カーネルにこれを組み込むことで一応認識はするのですが、次の問題が起きました。

  • 9600, 19200, 38400, 78600……921600(最大)といった系列しか指定できない。*1
  • 試しに460800に設定したところ、使用したマシンが非力なのかKernelごと落ちた(応答しなくなった)

公式配布ドライバの使用

配布されているドライバソース ftdi_sio.tar.gz を展開し make します。make するためにはカーネルのソース(の一部、ヘッダ類)が必要です。

kernelソースを展開し適切にパスを設定後 make してみると、コンパイルエラーが発生しました。どうやらkernelが古いせいか

	.owner =		THIS_MODULE,

という項目が存在しない*2のが原因のようです。これらの行をすべて削除(またはコメントアウト)することでコンパイルできるようになります。make が無事成功すると、ftdi_sio.oというファイルができます。これがドライバ本体(カーネルモジュール)です。

このドライバをカーネルに組み込むためには root で次のようにコマンドを実行します。

組み込み
# insmod ftdi_sio.o
Kernelから削除
# rmmod ftdi_sio.o
組み込み済カーネルモジュールの確認
# lsmod

組み込んでみたのですが、エラーが出てしまい、ドライバが動作しません。

ftdi_sio.c: v1.3.5r1:USB FTDI Serial Converters Driver
hub.c: USB new device connect on bus1/1, assigned device number 3
usbserial.c: descriptors matched, but endpoints did not
usb.c: USB device 3 (vend/prod 0x403/0x6001) is not claimed by any active driver.

0x403/0x6001は、FTDIの8U232AMを示し、ドライバソースには実際その記述があるのですが、「but endpoints did not」と出て認識しません。endpoints とは何であるかということですが、調べてみるとUSBデバイスがホスト側と通信する際に利用するバッファのことのようです。

要するにエンドポイントの設定がおかしいと言っているのですが、ドライバのソースにはきちんと記述されています。

static struct usb_serial_device_type ftdi_8U232AM_device = {
/*	.owner =		THIS_MODULE, */
	.name =			"FTDI 8U232AM Compatible",
	.id_table =		id_table_8U232AM,
	.num_interrupt_in =	0,
	.num_bulk_in =		1,
	.num_bulk_out =		1,

usbserial.c をみてみると

/* verify that we found all of the endpoints that we need */
if (!((interrupt_pipe & type->needs_interrupt_in) &&
      (bulk_in_pipe & type->needs_bulk_in) &&
      (bulk_out_pipe & type->needs_bulk_out))) {
	/* nope, they don't match what we expected */
	info("descriptors matched, but endpoints did not");
	return NULL;
}

ということで、この項目が設定されてないことが原因のようです。

公式配布ドライバへのパッチ

ftdi_sio.c の中に記述されているすべてのデバイス情報(8U232AM, FT232BM等々)について、次のような書き換えを行います。

static struct usb_serial_device_type ftdi_8U232AM_device = {
/*	.owner =		THIS_MODULE,	*/
	.name =			"FTDI 8U232AM Compatible",
	.id_table =		id_table_8U232AM,
	.needs_interrupt_in =	DONT_CARE,
	.needs_bulk_in =	MUST_HAVE,
	.needs_bulk_out =	MUST_HAVE,
	.num_interrupt_in =	0,

色が変わっているところが追加部分です。これをコンパイルしてカーネルモジュールを作成しインストールすると、

usbserial.c: FTDI FT232BM converter detected
usbserial.c: FTDI FT232BM converter now attached to ttyUSB0 (or usb/tts/0 for devfs)

として認識されます。

FT232RLと表示されませんが仕様です。コンピューターデバイスではよくあることで、デバイスとして異なってもソフトウェア的に同一の場合は、同じプロダクトIDを持つことがあります。また1つのデバイスで仮想的に(ソフト的に)複数のデバイスが存在するということもあるため、デバイスの型とソフト的に見える型の不一致などが頻繁に起きます。

*1 : あたらしいドライバならば任意の速度が設定出来ることはソースより確認済

*2 : struct usb_serial_device_type

ターミナルによる通信 2007/04/03

シリアルポートの設定を変更したり、任意の速度で通信するためにはターミナルや設定ソフトが必要ですが、minicom や stty などはお節介なことに"9600, 19200……"系列しか指定できません。kermit もダメでした。

いろいろ調べたところ、cu というコマンドで指定できました。cu コマンドは uucp と一緒に配布されています(リンク先、左下のソースから uucp_1.07.orig.tar.gz をダウンロード)。

# tar zxvf uucp_1.07.orig.tar.gz
# cd uucp-1.07
# ./configure
# make
# make install
# mkdir /usr/spool
# mkdir /usr/spool/uucp
# chown uucp /usr/spool/uucp

利用方法は次のようになります。

# cu -l /dev/ttyUSB0 -s 38400

1Mbps通信はできるか?

早速

# cu -l /dev/ttyUSB0 -s 1000000 -d
cu: fconn_open: Opening port /dev/ttyUSB0 (speed 1000000)
cu: fconn_set: Changing setting to 1, 2, 2

と設定してみます(-d はデバッグフラグです)。ところが、公式ドライバでも、2.4.17付属ドライバでも通信速度が変更できないようです。ドライバソースを改造してデバッグメッセージを出力させてみると、

ftdi_sio.c: Set to 9600

などと出てしまいます。カーネルの段階(tty?)で通信速度が書き換えられている模様です(詳細不明)。

仕方がないので、ドライバのソース(ftdi_sio.c)を書き換えて、無理矢理 1Mbps 通信をさせてみました。

  • Linux 2.4.17付属ドライバ …… 99%程度取りこぼし、使い物にならない
  • 公式配布ドライバ …… 通信速度の設定によらず、1byteも受信できない

以上うまく通信できませんでした。Linux 2.4.17のドライバは未成熟である可能性は捨てきれず、逆に公式配布ドライバはカーネルが古すぎるために(非互換により)通信すら行えていない可能性が高いと思います。

Linux 2.4.17のドライバを改変し受信したデータ

00001,00001,00001,00001,00001,00001,00001,00001,00001,00001

00002,00002,00002,00002,00002,00002,00002,00002,00002,00002

00003,00003,00003,00003,00003,00003,00003,00003,00003,00003

00004,00004,00004,00004,00004,00004,00004,00004,00004,00004

00005,00005,00005,00005,00005,00005,00005,00005,00005,00005

00006,00006,00006,00006,00006,00006,00006,00006,00006,00006

00007,0000720,359205921,35921,35921,35921,35921,35921,35921,35921

35922,35922,35935922,35922

35923,35923,35923,35923,35923,35923,35923,35923,3535924,35924,35924,35924,35924,35924

35925,35925,35925,35925,35

35926,35926,35926,35926,35926,35926,35926,359

FT232Rとして認識しない原因 2007/04/04

ftdi_sio.h では

#define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */

となっていて、いくつかの製品で同じプロダクトID(PID)を持っています。複数の製品をどうやって見分けているのかなと思ったのですが、productversion という2バイトの値で識別しているようです。

USB_DEVICE_VER (vendorId, productId, lo, hi)
	... like USB_DEVICE with lo <= productversion <= hi
<略>
static struct usb_device_id id_table_8U232AM [] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_IRTRANS_PID, 0, 0x3ff) },
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0, 0x3ff) },
<略>
static struct usb_device_id id_table_FT232BM [] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_IRTRANS_PID, 0x400, 0xffff) },
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0x400, 0xffff) },
<略>
static struct usb_device_id id_table_FT232R[] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0x600, 0xffff) },

0xffff0x5ff に書き換えることで、FT232R と認識しますが、特に挙動は変わりませんでした。usb_serial_device_type をみても表示文字列以外、内部処理に特に違いはないようです。

その後の検討とまとめ

  • 公式ドライバの ftdi_read_bulk_callback() 内の "port->open_count" の条件文を無効化(常に真)することでデータ通信はできましたが、Linux 2.4.17ドライバ同様に99%取りこぼしでした。
  • 試しに usbserial.c を別バージョンのカーネルから引っ張ってきましたが、案の定コンパイルすらできませんでした。
  • Linux 2.4.17で 100kbps で受信させてみましたが、それでも多少取りこぼしが発生しました。
  • Linux 2.6.17カーネルで試したところ、何の問題*3もなく動作しました。
    Linux version 2.6.17
    [17179838.796000] usb 1-1: configuration #1 chosen from 1 choice
    [17179838.800000] ftdi_sio 1-1:1.0: FTDI USB Serial Device converter detected
    [17179838.800000] drivers/usb/serial/ftdi_sio.c: Detected FT232BM
    [17179838.800000] usb 1-1: FTDI USB Serial Device converter now attached to ttyUSB0
    
    # stty -F /dev/ttyUSB0 1000000
    # cat /dev/ttyUSB0
    

Linux 2.4.17時代にはおそらく高速シリアル通信は考えられておらず、そのためまともに通信できないのではないかと思います。

もっとも、使用したマシンが非力すぎるという可能性も捨てきれない面はありますが、PowerPCベース200MHz程度あれば、いくら何でも100KB/sぐらい受信してよさそうですので(LANは3Mbps以上でてますし)。

残された道

  • Linux 2.4.17カーネルのドライバを適当に最新カーネルのものに入れ替える。
  • Linux 2.4.17カーネルのドライバを元に地道にパッチを当てる。
  • Linux 2.4.34等の最新カーネルをベースに、特殊ハード(組み込み)向けの改造を加える。
  • VCPドライバではなく、D2XXドライバの利用を検討する。x86バイナリのみの提供のため利用不可。

*3 : 取りこぼしもおそらくなし

2007/03/05(月)攻撃されるcgiと止まるサーバ

SPAMの激しい攻撃

どうもサーバがやけに重たいなぁーと思っていろいろ調べてみたら、とあるところに設置されている cgi が大量の攻撃を受けていた。

設置者が放置している掲示板で「まあ実害はないだろう」と思ってそのままにしておいたのですが*1、気付いたらそのcgiのおかげで1GBぐらいあるハズのサーバメモリが食いつぶされて、さらにswapまで食いつぶされていた(汗)

どれくらいSPAM投稿があったかと言うと、1分間に

[Tue Mar 06 03:03:03 2007] [client 84.19.176.62]
[Tue Mar 06 03:03:11 2007] [client 221.208.14.248]
[Tue Mar 06 03:03:22 2007] [client 222.33.65.154]
[Tue Mar 06 03:03:29 2007] [client 220.212.135.69]
[Tue Mar 06 03:03:32 2007] [client 82.141.146.20]
[Tue Mar 06 03:03:46 2007] [client 165.228.131.12]
[Tue Mar 06 03:03:46 2007] [client 220.212.135.69]
[Tue Mar 06 03:03:47 2007] [client 72.232.229.118]
[Tue Mar 06 03:03:58 2007] [client 72.232.206.66]

だけのアクセス。これがずーーーーっと続くわけで(以下略)。cgiの実行権限を外しました。

*1 : 利用者のユーザー領域に設置されているものですから、勝手にどうにかするのはあまりよろしくない

Ruby cgiが攻撃をうけている?

top でプロセス監視していると、たまに立ち上がる ruby プロセス(www権限)が 500MB ほど食っている模様。犯人はどこの cgi だ……(汗)

……これまた放置された tDiary でした。各記事に2000件ほどのSPAMが付いてまして、ここに対して書き込み動作をしたときのメモリ消費が次のとおりです。

USERNAME  SIZE   RES STATE  C  TIME   WCPU COMMAND
www       644M  583M kserel 0  0:07 20.14% ruby

放置されたcgiほど恐ろしいものはないと身にしみました(汗)*2

*2 : たかだか30MBのログを処理するのにこれだけメモリを食うtDiaryもどうかと思いますが……

cgi使用可能メモリの制限

ユーザーが設置するcgiをいちいち監視してられない上、この状況は恐ろしすぎるので cgi で利用可能なメモリをApache 側で制限することにしました

# cgi limit is 64MB 
RLimitMEM 67108864 67108864

PPPoE ルータ同志で IPsec を構築するときの問題

はてブ数 2007/01/31Linux

IPsecで一部の通信が遮断される謎

IPsec構築時の問題点に書いたことですが、一部の通信が遮断される問題の原因を追求しました。一言で言えば、IPsecパケットが PPPoE(フレッツ網) を通るには大きすぎてフラグメントしていることが原因でした。

より詳細な原因

ネットワーク図
192.168.10.0/24 <--LAN--> FreeBSD  <--Internet--> Linux 2.6 <--LAN--> 192.168.20.0/24

192.168.10.0 内のWindowsマシンから出るパケットは MTU(パケット最大サイズ)として 1454 が設定されています。しかし、フレッツ網を PPPoE で抜ける際に PPP でくるまれ、さらに IPsec でくるまれるためにこのサイズのパケットは通過できません(参考)。

次のようにIPパケットの断片化が発生しています。

# tcpdump -i tun0 esp
21:35:40 IP FreeBSD > Linux: ESP(spi=0x01053609,seq=0x450), length 268
21:35:40 IP FreeBSD > Linux: ESP(spi=0x01053609,seq=0x451), length 1432
21:35:40 IP FreeBSD > Linux: esp
21:35:40 IP FreeBSD > Linux: ESP(spi=0x01053609,seq=0x452), length 1044
21:35:40 IP Linux > FreeBSD: ESP(spi=0x0129b031,seq=0x402), length 76
21:35:40 IP FreeBSD > Linux: ESP(spi=0x01053609,seq=0x453), length 1432
21:35:40 IP FreeBSD > Linux: esp

本来、IPパケットが断片化されようと、相手側のLinuxマシンで再構築され復元されるため、断片化により通信速度は低下しますが、通信自体は問題ありません。

 生パケット → IPsecカプセル化 → IPフラグメント(分割)  → Internet
 → IPパケット再構築 → IPsecのカプセルを解く → 生パケット

しかし 192.168.20.0 内にはパケットが届いておりません。Linuxマシンのインターフェイスまでは届いているのですが、iptables なのか、その先の IPsec なのか、ともかくうまく行っていないようです。

tcpmss を設定する

tcpはコネクション時に、TCPの中に含まれるデータサイズMSS(Maximum Segment Size、通常 MTU-40byte)をネゴシエーションします。

# tcpdump -i fxp0 port 8080
21:33:25 IP pc11.5955 > pc22.8080: S xxx(0) win 5840 <mss 1414,sackOK,timestamp 4032556 0,nop,wscale 2>
21:33:25 IP pc22.8080 > pc11.5955: S xxx(0) ack yyy win 65535 <mss 1460,nop,nop,sackOK>

このときの小さい方の数字をTCPの通信において使用します。これを tcpmss と言います。

IPsecやPPPoEのトンネル化などによりヘッダサイズが増大してしまうなら、この mss の値を MTU に対して小さく設定してあげればいいことになります。この操作をルータ側で強制的に行ってしまえば、結果的にIPsecにおける断片化(フラグメント)を防ぐことができます。

MTU > mss + IPsec & IP & TCPヘッダサイズ

通信路MTUの調査方法

ping コマンドでパケットサイズを指定することで、通信路のMTUを調べることができます。

# Linux/FreeBSDの場合
$ ping -l 1370 192.168.20.2
# Windowsの場合
> ping -s 1370 192.168.20.2

この場合、どちらも1370バイトのpingパケットを 192.168.20.2 に投げます。無事パケットが返ってくる限界値+28byteが経路MTUになります。mssはMTU-40になります。

Linux側による解決策

必要なmssの値はMTUから計算で出せるハズですが、なぜかそれではうまく行かず*1、結局試行錯誤で次のように設定しました。

iptables -A FORWARD -p tcp -s 192.168.10.0/24 --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1344
iptables -A FORWARD -p tcp -d 192.168.10.0/24 --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1344

試行錯誤は、tcpdump を用いて esp の断片化が起きないサイズを探り出すことにしました。IPsecのフッタの一部がサイズ可変らしく(?*2)、pingによる調査では不十分でした(それよりも低い値になった)。

FreeBSD側による解決策

FreeBSDのpppデーモンには、すでにtcpmssfixupという機能が付いているのですか、pppd を通過する段階ではすでにIPsecでカプセル化されているため無力です*3。同じ理由で、ipnat.conf でも、mssを調整する機能があるのですが、パケットが出て行くときしか書き換えられないためにIPsec では使用出来ません。

最初後述するように ipfw を使っていたのですが、pf できました。*4

デフォルトのカーネルで使用出来ると思います。rc.conf には次のように書きます。

pf_enable="YES"

pf.conf には次のように書きました。LAN側インターフェイスの 入力時 と 出力時 にそれぞれフィルタすることに注意してください。

int_if="fxp0"		# LAN interface
scrub in  on $int_if from 192.168.10.0/24 to 192.168.20.0/24 max-mss 1344
scrub out on $int_if from 192.168.20.0/24 to 192.168.10.0/24 max-mss 1344

/etc/rc.d/pf start」 する際に ALTQ がどうのと怒られますが、ALTQ関連機能を使用しなければ問題ないので気にしないでください。

# /etc/rc.d/pf start
Enabling pf.
No ALTQ support in kernel
ALTQ related functions disabled

FreeBSD側による別の解決策(非推奨)

今更 ipfw の時代でもないので非推奨の方法です(動作自体は問題ないのですが煩雑です)。

結局、ipfw + tcpmssd を使用しました。tcpmssd は /usr/ports/net/tcpmssd よりインストールしました(パッケージでも構いません)。起動スクリプトがありませんので、tcpmssd.sh.txt/usr/local/etc/rc.d/tcpmss.sh として保存し使用してください。

カーネルのコンパイルオプションです。

options         IPFIREWALL
options         IPFIREWALL_VERBOSE
options         IPDIVERT

rc.conf です。mssではなくMTUを与えることに注意。

firewall_enable="YES"
firewall_type="/etc/ipfw.sh"
tcpmssd_enable="YES"
tcpmssd_flags="-p 50 -m 1372"

/etc/ipfw.sh です。IPsecを通過する tcpコネクションパケット を tcpmssd に飛ばし mss を書き換えます。

add 64900 divert 50 tcp from 192.168.10.0/24 to 192.168.20.0/24 tcpflags syn
add 64902 divert 50 tcp from 192.168.20.0/24 to 192.168.10.0/24 tcpflags syn

pfによる方法と同様に、FreeBSDマシンから直接 192.168.20.0/24 へ向かうパケットは ipfw のルールを通過せず、tcpmss を設定することは不可能です。

*1 : 時折パケットがロストする

*2 : 詳しい人補足願います

*3 : 出て行くときにはすでにIPsecでカプセル化されているので、書き換えようがない

*4 : IPFilter の機能不足で困ることが多かったのですが、今度から pf 使おう(汗)

さらなる疑問

IPsecにおいて、カプセル化後にIPパケットのフラグメントが発生した場合、本来ならば受信側がパケットを再構築して通信できるはずだと思うのですが……(速度が遅くなるのは仕方ないとしても)。

  • FreeBSD ←→ FreeBSDなら問題ないのか?
  • Linux ←→ Linuxなら問題ないのか?
  • FreeBSD(6.x) ←→ Linux(2.6.x)で通信可能にする手段は存在しないのか?

そもそも、Linux側には ip_conntrack (コネクション追跡)がインストールされているのでフィルタリングより前にESPパケットが再構築されているハズなのですが*5、フラグメントしたESPパケットの到着は確認できても、IPsecのカプセル化を解いた後のパケットは確認できていません。

色々調べたところ、Linuxはカプセル化する前にパケットを分割しますが、FreeBSDはカプセル化してからパケットを分割しています。この辺のミスマッチが怪しいですね。ip_conntrack がESPのフラグメントパケットを修復する際に、ヘッダ書き換え(?)か何か悪さしてるんじゃないかと思いますが……。

*5 : Linux 側 iptablesで確認しましたが、フラグメントパケットは記録されていませんでした。iptables にESPパケットが届いているのは確認できます