仙石浩明の日記

2010年4月20日

Linux サーバ同士をシリアルケーブルでつなぐには寡黙な getty が必要

私の自宅 LAN (GCD) の対外ルータである 2台の Linux サーバ (senriasao) は、 双方のシリアルポート (つまり DSUB9ピンの RS-232C) 同士がつながっている。 いまさら RS-232C ? と思う人が大半だと思うが、 最後の命綱としての安心感は測り知れない。

つまりサーバをリモートから (ネットワーク経由で ssh ログインして) いじっていると、 ちょっとしたミスで操作不能に陥ってしまうことがある。 例えば NIC を down してしまった、 iptables で DROP する設定にしてしまった、 sshd を殺してしまった、 等々。 あるいは、 リモートから再起動を行なう場合、 起動スクリプトのほんの些細な設定ミスで、 リモートからは操作不能になってしまう。

こんなとき、 2台のサーバのシリアルポート同士を RS-232C クロスケーブル (シリアルケーブル) で接続しておけば、 生きている方のサーバからシリアルケーブル経由でログインできて、 リモートからは操作不能に陥ったサーバのネットワーク設定を修復したり、 sshd を起動し直したり、 あるいは再起動の場合であれば、 GRUB を操作することができる (ML115 などであれば BIOS 設定までいじれる)。

職場のサーバ群は、 もちろん IPMI を利用していて大抵の操作は IPMI でできてしまうのでシリアルケーブルの出番はあまりないのだが、 それでもやはり最後の命綱がある安心感は何ものにも代えがたい。 しかも数百円のケーブル一本で済むのだから、 掛けておくべき保険だろう。

シリアルケーブル経由でサーバにログインするには、 そのサーバの /dev/ttyS0 (シリアルポート COM1 の場合) で getty を動かしておく必要がある。 例えば GCD の場合 mgetty を使っているので、 /etc/inittab に

# Dialin lines
s0:25:respawn:/usr/sbin/mgetty ttyS0

などと設定している。 シリアルケーブルでつながった 2台の Linux サーバ senri と asao において、 asao から senri へシリアルケーブル経由でアクセスすると、 senri のシリアルコンソールが表示される:

asao:/home/sengoku % cu ttyS0
Connected.

Welcome to Linux 2.6.31.13-x86_64.


senri.gcd.org!login: root
otp-md5 299 se5047 ext
Response or Password:
Last login: (null) on /dev/ttyS0

senri.gcd.org:/root # tty
/dev/ttyS0
senri.gcd.org:/root # 

たとえ senri のネットワーク機能が全滅していたとしても、 mgetty は /sbin/init から直接起動されるので影響を受けない。 シリアルコンソールから root でログインして復旧作業を行なうことができる。 逆に、 asao のネットワーク機能が全滅したときは、 senri へ ssh ログインした上で asao へシリアルケーブル経由でアクセスすればよい。 ネットワーク機能が麻痺する可能性があるような危険な作業を senri と asao の両方で同時に行なったりしないようにすれば、 何が起きてもどちらかは生きていることが保証できる。

さて、 両サーバで mgetty が動いているときは以上で問題無いのだが、 どちらかのサーバを再起動するときなど、 mgetty 以外のものが動くときは状況が変わってくる。

例えば asao を再起動すると、 まず GRUB2 が動く。

以前は GRUB Legacy を使っていたのだが、 ハードディスクを入れ替えたとき (数年ごとに 2〜3倍の容量のハードディスクと交換している) 勢い余って root パーティションまで ext4 にしてしまい、 GRUB Legacy だと ext4 をサポートしていないことに気付き、 慌てて GRUB2 をインストールした。

GRUB2 では、 /boot/grub/grub.cfg に

serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
terminal_input serial console
terminal_output serial console

などと設定しておくことにより、 VGA コンソール (/dev/tty1) とシリアルコンソール (/dev/ttyS0) の両方で GRUB の操作 (つまり起動するパーティション/カーネルを選択する等) を行なうことができる。 つまり VGA コンソールで操作しているときも、 同じ画面がシリアルコンソールでも出力され、 途中からシリアルコンソールで操作することも可能。

GRUB Legacy では、 最初にキー入力があった側のコンソールでの操作が有効になる仕様だった。 最初に VGA コンソール (つまり PC に直接接続された PS/2 or USB キーボード) からキー入力を行なえば、 以後キーボードからの操作が有効になり、 最初にシリアルコンソールからキー入力を行なえば、 以後シリアルケーブル経由の操作が有効になる。

一見 GRUB2 の仕様のほうが便利のように見えるが、 二つの Linux サーバをシリアルケーブルでつないでいると、 GRUB2 の仕様は少し困る。 つまり以下のような GRUB2 のメニュー画面が常にシリアルケーブルへも出力されてしまい、 それが相手サーバの mgetty への入力として扱われてしまうので、 mgetty が余計な動きをする。


                             GNU GRUB  version 1.97

 +--------------------------------------------------------------------------+
 |GNU/Linux 2.6.31.13-x86_64                                                |
 |GNU/Linux 2.6.31.13-i386                                                  |
 |GNU/Linux 2.6.31.12-x86_64                                                |
 |GNU/Linux 2.6.31.12-i386                                                  |
 |GNU/Linux 2.6.31.11-x86_64                                                |
 |GNU/Linux 2.6.31.11-i386                                                  |
 |GNU/Linux 2.6.30.9-x86_64                                                 |
 |GNU/Linux 2.6.30.9-i386                                                   |
 |GNU/Linux 2.6.27.34-x86_64                                                |
 |GNU/Linux 2.6.27.34-i386                                                  |
 |Memtest86+                                                                |
 +--------------------------------------------------------------------------+

      Use the ^ and v keys to select which entry is highlighted.
      Press enter to boot the selected OS, 'e' to edit the commands before
      booting or 'c' for a command-line.


すなわち mgetty は何かキー入力があると、 前述したような 「Welcome to Linux ...」 メッセージとログインプロンプト 「senri.gcd.org!login: 」 を出力する。 この出力がそのまま相手サーバの GRUB2 への入力として扱われてしまう。

つまり 「Welcome to Linux ...」 メッセージは前と後ろに 「改行コード」 が出力されるので、 GRUB2 へ改行が入力されてしまう (改行コード以外のアルファベットが GRUB2 によってコマンドとして認識されると、 もっと面倒なことになる)。 すなわち VGA コンソールに表示された GRUB2 メニューにおいて、 どの OS を起動しようか選ぼうとする間もなく、 改行キーが (シリアルコンソールから) 入力されたことになってしまってデフォルトの OS が勝手に起動してしまう。

一方、 GRUB Legacy の場合は、 何かキー入力があるまでシリアルケーブル側へは何も出力されない (正確に言うと 「Press any key to continue.」 というメッセージのみ出力される)。 相手サーバの mgetty が何か出力すると破綻してしまうが、 以下のような簡単なパッチを GRUB Legacy にあてることにより、 「any key」 ではなく mgetty が出力しないコードを入力したときのみ シリアルコンソールへ切り替えるようにできる。 以下のパッチでは ^G (コントロールキーを押しながら G) をシリアルコンソールで入力したときのみ、 GRUB Legacy のメニュー画面がシリアルコンソールに表示されるようにしている。

diff -ur grub-0.97.org/stage2/builtins.c grub-0.97/stage2/builtins.c
--- grub-0.97.org/stage2/builtins.c	2005-02-16 06:58:23.000000000 +0900
+++ grub-0.97/stage2/builtins.c	2009-04-06 13:31:02.288106510 +0900
@@ -4208,10 +4208,13 @@
 		{
 		  if (term_table[i].checkkey () >= 0)
 		    {
-		      (void) term_table[i].getkey ();
-		      default_term = i;
-		      
-		      goto end;
+                      int term_key_code;
+                      term_key_code = term_table[i].getkey();
+		      if( ( (char)term_key_code ) == 7 )
+                      {
+		      	default_term = i;
+		      	goto end;
+                      }
 		    }
 		}
 	    }
@@ -4227,7 +4230,7 @@
 		    if (term_bitmap & (1 << i))
 		      {
 			current_term = term_table + i;
-			grub_printf ("\rPress any key to continue.\n");
+			grub_printf ("\rPress Ctrl+G key to continue.\n");
 		      }
 		  
 		  /* Restore CURRENT_TERM.  */

GRUB2 でも同様のパッチを作ろうと思ったのだが、 GRUB2 は GRUB Legacy と違って、 シリアルと VGA を切り替える仕組みにはなっておらず、 GRUB Legacy の時のような一行パッチでは済みそうにない。

また、 GRUB2 はまだ開発途上ということもあり、 バージョンが上がるたびにパッチを作るのも大変そうである。 GRUB2 以外のブートローダに乗り換える可能性もあるかもしれない。 一方 mgetty は既に枯れたソフトウェアと言ってよい。 そこで mgetty にパッチをあてて、 mgetty が 「Welcome to Linux ...」 メッセージとログインプロンプトを出力するのを抑制することにした。 mgetty が寡黙になりさえすれば、 相手サーバで GRUB2 だろうがその他のブートローダだろうが、 何が動いていても問題は起きない。

シリアルコンソールから ^G を入力したときのみ、 mgetty が 「Welcome to Linux ...」 メッセージとログインプロンプトを出力し、 ^G が入力されない限りは何も出力しないようにするパッチを作ってみた:

--- mgetty.c.org	2006-01-02 02:13:18.000000000 +0900
+++ mgetty.c	2010-04-17 14:20:54.149714734 +0900
@@ -706,6 +706,11 @@
 	       
 	    if ( c_bool(direct_line) )			/* no RING needed */
 	    {
+		char ch;
+		signal(SIGALRM, sig_goodbye);
+		alarm(ring_chat_timeout);
+		int r = mdm_read_byte(STDIN, &ch);
+		if (r <= 0 || ch != 0x07) break;	/* ^G */
 		mg_get_ctty( STDIN, devname );		/* get controll.tty */
 		mgetty_state = St_get_login;
 		break;

このパッチにより、 ^G が入力された時 (ch != 0x07 が不成立) のみ状態 「St_get_login」 へ遷移する。 つまりシリアルコンソールへログインプロンプトを出力する。 ^G 以外が入力されると、 上記パッチ中の if 文の条件が成立し、 状態変数 mgetty_state が更新されず、 状態 「St_wait_for_RINGs」 に留まる。 つまり何も出力されない。

蛇足だが、 状態 「St_wait_for_RINGs」 というのは、 mgetty がモデムからの 「RING」 送信を待っている状態。 モデムに (公衆回線から) 着呼要求が届いたとき (いわゆる電話機が鳴ってる状態)、 モデムは RS-232C 経由で PC に対して 「RING」 を一定間隔で送信する。 mgetty は RING の回数を数えて指定回数を超えれば、 「ATA」 コマンドをモデムに送信して着呼する (いわゆる受話器を取った状態)。
Filed under: システム構築・運用 — hiroaki_sengoku @ 07:48

2件のコメント »

  1. RS232Cケーブルは数百円では売っていない。

    コメント by ちょっと — 2012年9月3日 @ 15:34

  2. 今日見たお店では、480円で売っていました > RS232Cケーブル
    https://www.facebook.com/photo.php?fbid=279710295476011

    通販でも売っているようですね:
    http://kakaku.com/search_results/99FFC18/

    コメント by hiroaki_sengoku — 2012年9月7日 @ 00:42

この投稿へのコメントの RSS フィード。

コメントする