remotediskd(8) は、Sony NEWS の PROM/ブートローダが利用する 「rd(remote disk)」プロトコル によるネットワークブートを提供するデーモンである。
- NEWS 実機からの
bo rd等の操作に応答し、 指定されたディスクイメージファイルを「リモートディスク」として提供する。 - 将来的に NEWS 以外の “rd 風” プロトコルクライアントにも拡張可能な汎用設計を目指す。
-
NEWS の rd プロトコルは、以下の 2 種の UDP 通信を利用する:
- SRQ 要求(ポート 3073/UDP) → サーバ探索、サーバ IP 告知
- RPC 形式の rd 要求(ポート 3072/UDP)
→
RPCRDOPEN,RPCRDREAD,RPCRDCLOSE等
-
プロトタイプ
newsrd_bootdで既に以下は確認済み:- SRQ 要求のフォーマットと応答
- RPC のフレーム形式(ONC RPCv2 相当)
RPCRDOPEN→ 512 バイト READ → 8KB READ シーケンス(boot00 / boot0 相当)
-
/etc/remotediskd.confを読み、どのクライアント(MAC)に対してどのディスクイメージをどのユニットで提供するかを決定する。 -
/etc/ethersを使って MAC アドレスとホスト名の対応を解決する。 -
クライアントからの
rd要求に対し、対象ディスクイメージファイルを- 遅延 open/mmap
- 三段階のクローズ条件(
RDCLOSE,RDOPEN, idle timeout) で管理しながら READ データを返す。
- 実行ファイル名:
remotediskd - セクション:
remotediskd(8) - 通常は root 権限で起動することを想定。
-i interface
-
指定された場合、そのインターフェースでのみ SRQ/RPC の受信・送信を行う。
-
省略時は
rbootd(8)と同様に、- システムのインターフェースリストから
- 「loopback 以外」で「UP」のインターフェースを
- 番号の若い順に走査し
- 最初に見つかったものを採用
-
例:
-i re0
-d
-d -d (レベル2)
-
-dが 1 回以上指定された場合:- フォアグラウンドで実行(daemon化しない)
- ログを標準エラーにも出力
-
-dの回数でログレベルを変える案:- レベル 0(指定なし): 最小限(起動/致命的エラー/重要な警告)
- レベル 1(
-d): 通常の動作ログ(クライアント接続、RDOPEN/RDREAD 等) - レベル 2(
-dd): パケットの詳細(必要なら hexdump など)
※ 初期実装ではレベル 0/1 を主に使い、2 はフックだけ用意しておいてもよい。
-c config_file
- 設定ファイルパスを指定。省略時は
/etc/remotediskd.confを用いる。
-s image_root
- RD 応答に用いるイメージファイルを置くディレクトリを指定。
- 省略時のデフォルトは
isibootd(8)に合わせて/tftpboot。 - 設定ファイルに記述されるイメージパスは、このディレクトリからの相対パス(もしくは絶対パスも許容する方針は実装時に決める)。
1 行 1 ホストエントリ:
# コメント
hostname image_list
-
hostname:/etc/ethersに記載されたホスト名を前提とする。- 実際には受信した MAC アドレスから
ether_ntohost(3)で hostname を解決し、 それと設定ファイルの hostname を照合。
-
image_list:-
RD のユニット番号順に対応するイメージファイル名をカンマ区切りで指定。
-
例:
newsclient1 news-disk0.img,news-disk1.img -
上記の場合:
- unit 0 →
news-disk0.img - unit 1 →
news-disk1.img
- unit 0 →
-
-
ファイル名にカンマを含めたい場合は
\でエスケープ:newsclient2 weird\,name.img -
実装は簡易でよく、「
\は次の 1 文字をリテラル扱い」程度で十分。
remotediskdは/etc/ethersを前提とし、ether_ntohost(3)で MAC→hostname を引く。- 起動時に
/etc/ethersの存在を確認し、ない・読めない場合は警告を出して終了(仕様として「必須」扱いにしてよい)。
- SRQ 用: UDP ポート 3073
- RPC 用: UDP ポート 3072
remotediskd はこの 2 つに対し、別々のソケット を用意する。
srq_sock: 3073/UDP に bindrpc_sock: 3072/UDP に bind
メインループでは select() または poll() で両ソケットを同時監視する。
-
フォーマットはプロトタイプ
newsrd_bootdと同一。 -
クライアントからの SRQ(ニュース PROM からのブロードキャスト)を受け取り、 対象インターフェースの IP アドレスを
<server_ip>としてエンコードし、 クライアントの送信元ポート(4096)へ unicst で応答する。 -
仕様変更ポイント:
-
応答に埋め込む
<server_ip>は、- 元々の
-sオプションによる手動指定を廃止し - 実際に応答を送信しているインターフェースの IP アドレスを用いる
- 元々の
-
-
RPC は ONC RPCv2 ベース。UDP 上で XID 付き。
-
対応するプロシージャ:
RPCRDOPEN(proc=1)RPCRDREAD(proc=3)RPCRDCLOSE(proc=?; プロトタイプで判明している値を流用)
-
解析・応答フォーマットはプロトタイプ
newsrd_bootdの実装を踏襲する。
想定ファイル構成:
-
main.c- コマンドラインパース、デーモン化、メインループ
-
config.c/config.h/etc/remotediskd.confパース/etc/ethers利用による MAC→hostname 解決
-
backend.c/backend.h- クライアントごとの状態管理
- イメージファイルの open/mmap/close 管理
- idle timeout 管理
-
proto.c/proto.h- SRQ パケット解析・応答
- RPC パケット解析・応答(rd プロトコル)
-
util.c/util.h- ログ出力、hexdump(デバッグ用)、ユーティリティ
/* remotediskd 全体の設定 */
struct rd_config {
char *rc_ifname; /* 使用インターフェース名 (NULL = 自動選択) */
char *rc_conf_path; /* 設定ファイルパス */
char *rc_image_root; /* イメージディレクトリルート */
int rc_debug_level; /* 0,1,2 */
int rc_daemonize; /* 0=foreground, 1=daemon */
time_t rc_idle_timeout; /* idle timeout 秒 (0以下で無効) */
/* クライアント定義リスト */
struct rd_client_conf *rc_clients; /* 単方向リスト or 配列 */
};
/* クライアント毎の設定エントリ (設定ファイル由来) */
struct rd_client_conf {
char *rcc_host; /* hostname (設定ファイルの左側) */
int rcc_nunits; /* ユニット数 */
char **rcc_images; /* [0..nunits-1] = イメージファイル名 (相対パス) */
struct rd_client_conf *rcc_next;
};/* (client, unit) ごとのランタイム状態 */
struct rd_unit_state {
int ru_unit; /* ユニット番号 */
int ru_fd; /* open された fd; -1 = 未 open */
void *ru_base; /* mmap ベースアドレス */
size_t ru_size; /* ファイルサイズ */
int ru_valid; /* 設定上このユニットが有効か (1/0) */
time_t ru_last_access; /* 最終アクセス時刻(open/READ 時に更新) */
};
/* クライアント 1 台に対するランタイム状態 */
struct rd_client_state {
struct rd_client_conf *rc_conf; /* 対応する設定エントリ */
/* MAC アドレス(比較用) */
unsigned char rc_mac[6];
/* 単純実装として hostname 文字列も持っておく */
char rc_host[NI_MAXHOST];
/* ユニット配列 */
int rc_nunits;
struct rd_unit_state *rc_units;
/* unknown client ログ抑止用 */
time_t rc_last_unknown_log; /* 最後に「unknown client」とログした時刻 */
struct rd_client_state *rc_next;
};
/* サーバー全体状態 */
struct rd_state {
int rs_srq_sock;
int rs_rpc_sock;
struct rd_config rs_conf;
struct rd_client_state *rs_clients;
};-
main()にてオプション解析-i,-d,-c,-sを解析- コマンドラインレベルで判明するエラー(存在しない設定ファイルパス指定など)はここで即エラー終了。
-
/etc/ethersの存在確認- 読めない場合はログを出し、終了(rd は /etc/ethers 前提とする)。
-
設定ファイル読み込み (
rd_config_load())-
remotediskd.confパース -
ここでは ファイルの存在チェックは行わない。
- 理由: イメージは unit ごとに lazy open するため、 実際にその unit に対して RD 要求が来たときにエラーとする。
-
-
ソケットオープン・バインド
- インターフェース決定(
-i指定/自動選択) - 3073/UDP, 3072/UDP に bind
- インターフェース決定(
-
-dが指定されていなければ daemon 化(fork / setsid 等)。 -
メインループへ。
疑似コードイメージ:
for (;;) {
struct timeval tv;
fd_set rfds;
time_t now = time(NULL);
/* タイムアウト値を適当に設定 (例: 1〜5秒) */
tv.tv_sec = 2;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(rs_srq_sock, &rfds);
FD_SET(rs_rpc_sock, &rfds);
maxfd = max(rs_srq_sock, rs_rpc_sock);
n = select(maxfd+1, &rfds, NULL, NULL, &tv);
now = time(NULL);
rd_backend_gc_idle(&rs_state, now); /* idle timeout GC */
if (n <= 0)
continue;
if (FD_ISSET(rs_srq_sock, &rfds))
rd_handle_srq(&rs_state);
if (FD_ISSET(rs_rpc_sock, &rfds))
rd_handle_rpc(&rs_state);
}-
イメージファイルは「必要になったとき」に unit 単位で open/mmap する。
-
具体的には:
-
クライアントからの最初の
RPCRDOPEN受信時-
そのクライアントに対するセッション開始とみなし、
- 対象クライアントの 全ユニット の既存
fd/mmapを close/munmap - ただしこの時点ではまだ new open しない(後続 READ まで lazy でもよい)
- 対象クライアントの 全ユニット の既存
-
ru_last_access = nowの初期化
-
-
RPCRDREAD受信時-
対象
(client, unit)のru_validを確認0なら「no such unit」的エラーを RPC エラーで返す。
-
ru_fd < 0(未 open or 既に close)なら、open()→fstat()→mmap()を実行- 成功したら
ru_fd,ru_base,ru_sizeを更新
-
READ 対応オフセット範囲チェック
off + cnt > ru_sizeなら末尾を超えないように調整するか、 RPC エラーとするかは、プロトタイプnewsrd_bootdと同じ挙動に揃える。
-
正常に送信したら
ru_last_access = nowに更新。
-
-
-
クライアントから
RDCLOSEproc を受け取った場合、- 対象
(client, unit)のru_fdが有効ならmunmap()→close()し、 ru_fd = -1,ru_base = NULL,ru_size = 0にリセット。ru_validは変更しない(設定上の有効性なので)。
- 対象
-
OPENは「このクライアントの新しいセッション開始」とみなし、- 対象クライアントの 全ユニットの
fd/mmapを一括 close/munmap する。 - これはプロトタイプ
newsrd_bootdの挙動を踏襲。
- 対象クライアントの 全ユニットの
-
その後、
ru_last_access = nowで初期化(ユニット全体)。
-
SIGHUPを受けたら:-
シグナルハンドラでフラグを立てる(ハンドラ内では最小限の処理)
-
メインループの安全なタイミングでフラグを検出し、以下を実施:
-
旧設定(
rd_config)に紐付く全クライアント状態の 全ユニットのfd/mmapを close/munmap -
旧クライアント状態(
rd_client_state)をすべて破棄 -
設定ファイルを再パース
-
パースに成功したら、新しい
rd_configと新しいrd_client_stateリストをセット -
失敗した場合:
- 旧設定での運用を継続するか、
- それともエラーとしてデーモンを終了するかは設計選択。
- 安全志向なら「旧設定のまま継続」が現実的。
-
-
-
main()の終了処理で、rs_clientsの全(client, unit)についてfd/mmapを close/munmap する。
rc_idle_timeout(秒)を導入。初期値はコンパイル時定数として:
#define RD_IDLE_TIMEOUT_DEFAULT 300 /* 秒, 0以下で無効 */-
設計上:
- 初期実装ではコマンドラインオプションは設けず固定値 300 秒。
- 将来拡張として
-t sec等で変更可能にする余地は残す。
-
ru_last_accessは以下のタイミングで更新する:-
RPCRDOPEN正常処理後- 対象クライアントの全ユニットに対して
ru_last_access = nowをセット
- 対象クライアントの全ユニットに対して
-
RPCRDREAD正常処理後- 対象ユニットの
ru_last_access = nowを更新
- 対象ユニットの
-
-
メインループ内で
select()のタイムアウト(例: 2 秒)ごとにrd_backend_gc_idle(&rs_state, now)を呼ぶ。 -
rd_backend_gc_idle()の疑似コード:
void rd_backend_gc_idle(struct rd_state *rs, time_t now)
{
time_t timeout = rs->rs_conf.rc_idle_timeout;
struct rd_client_state *cs;
int i;
if (timeout <= 0)
return; /* idle timeout 機能無効 */
for (cs = rs->rs_clients; cs != NULL; cs = cs->rc_next) {
for (i = 0; i < cs->rc_nunits; i++) {
struct rd_unit_state *ru = &cs->rc_units[i];
if (ru->ru_fd < 0)
continue;
if (now - ru->ru_last_access >= timeout) {
/* idle timeout 発動: close/munmap */
rd_backend_close_unit(cs, ru);
log_info("client %s unit %d: idle timeout (%ld sec), closing image",
cs->rc_host, ru->ru_unit, (long)timeout);
}
}
}
}- これにより、RDCLOSE が来なくても 一定時間アクセスがなければ自動的に close される。
-
idle timeout により
fd/mmapを close 済みの状態で、同一クライアントから再びRPCRDOPENやRPCRDREADが来た場合:-
RPCRDOPENの場合:- セッションリセットとして全ユニット close(既に close 済みであれば no-op)
- その後の
RPCRDREADで通常どおり lazy open/mmap が行われる。
-
RPCRDREADの場合:ru_valid == 1 && ru_fd < 0なら、再度open()/mmap()を行う。- これにより、
「最初のブートから長時間放置 → idle timeout で close →
また
bo rdしても正常にブートできる」 というサイクルを保証する。
-
-
設定ファイルに該当 hostname がない/MAC に対応する hostname が
/etc/ethersに存在しない等の unknown client が rd 要求を送ってきた場合:-
初回は警告ログを出力:
remotediskd: unknown client xx:xx:xx:xx:xx:xx (host=<resolved-or-unknown>) -
rc_last_unknown_logに記録(time(NULL)) -
同一クライアント(MAC)から短時間に大量にリトライが来ることを想定し、 一定時間(例えば 60 秒)以内に同じクライアントからの unknown はログを抑止する。
-
-
「どの MAC をどう識別するか」は簡易実装として:
rc_macとrc_hostをキーとみなす- or unknown 用に別の簡易テーブルを持っても良いが、NEWS の台数は少ない前提なので、 単純に「最後に unknown ログを出した時刻」を 1 グローバルにするだけでも実用上問題ない。
-
-cで指定された設定ファイルパスが存在しない/読めない場合:- 起動時に即エラー終了(syslog + 標準エラーにメッセージ)。
-
-sで指定されたイメージディレクトリが存在しない/ディレクトリでない:- 起動時にチェックして即エラー終了。
-
パース時:
-
文法エラー(カラム数が足りない、トークン分割に失敗など)があれば、 その行をスキップし、警告ログを出す。
-
hostname だけ書いて
image_listが空(イメージが 1 つもない)場合:- 「対象ホストに対応するイメージがない」と警告ログを出し、そのホストエントリを無効としてスキップする。
-
-
イメージファイル名の存在チェック:
-
起動時には行わない。
-
実際に
(client, unit)に対してRPCRDREADが来た際に open して初めて存在確認を行う。 -
その時点で open に失敗した場合、
- RPC レベルのエラーとしてクライアントに返す(具体的なエラーコードはプロトタイプに合わせる)。
- サーバ側ログに「イメージファイルが見つからない/open できない」旨を出力。
-
- 起動時に
/etc/ethersの存在と読み取り可否を確認。 - 読めない場合は「/etc/ethers が読めないため hostname 解決ができない」とエラーを出し、起動失敗とする。
-
SIGHUP受信時:-
設定ファイル再読み込みを試みる。
-
再パースに成功した場合:
- 旧設定/旧クライアント状態の全ファイルを close/munmap したうえで、 新設定を反映して再開。
- 「config reloaded」ログを出力。
-
再パースに失敗した場合:
-
保守性重視の方針としては「旧設定で動作継続」が無難。
-
どちらの挙動にするかは明示的に仕様化しておく。
- ここでは「旧設定継続」を採用することを推奨。
-
-
-
単一クライアント・単一ユニットでの正常ブート
-
条件:
- remotediskd.conf に
newsclient1の unit0 イメージを設定
- remotediskd.conf に
-
手順:
- NEWS
newsclient1からbo rd実行
- NEWS
-
期待結果:
RPCRDOPEN→ 512B READ → 8KB READ(等)が正しく実施される- 適切なイメージファイルから読んだデータでブートが進行する
- サーバログに OPEN/READ の情報が出る
-
-
複数ユニット設定のブート
- unit0, unit1 など複数イメージを設定し、 PROM が unit0 のみ使うケースを確認(unit1 は open されない)。
-
hostname のみ指定で image_list が空
- そのホストエントリは無視される
- 警告ログを出すが、他の正しいエントリの動作には影響しない
-
image_list のファイルが存在しない
- 該当ユニットに対して
RDREADが来たときに open に失敗 - RPC エラーを返す
- サーバログに「open 失敗」のメッセージが出る
- 該当ユニットに対して
-
/etc/ethers が存在しない/読めない
- 起動時にエラー終了することを確認
-
/etc/ethersに存在せず、かつremotediskd.confにも記述されていない MAC からのアクセス- 初回に「unknown client〜」のログが出る
- その後短時間に繰り返しアクセスしても、ログがスパム状態にはならない (一定時間抑止ロジックが効いている)
-
正常な新設定への切替
- 起動中に
remotediskd.confを編集し、ユニット定義を変更 kill -HUP <pid>を送る- 新設定でのブート動作に切り替わること
- 旧設定由来のイメージ fd がすべて close されていること
- 起動中に
-
新設定が文法エラーの場合
- SIGHUP 後にパースエラーが発生した場合、
- 旧設定で動作が継続することをログで確認
-
タイムアウトで close される
-
bo rdを一度実行して正常にブートさせる -
その後クライアントからのアクセスを止めて 300 秒以上放置
-
サーバログに
client <host> unit 0: idle timeout (300 sec), closing imageのようなメッセージが出る
-
fstat/lsof等でイメージ fd が閉じられていることを確認
-
-
タイムアウト前にアクセスが続く場合 close されない
- テスト用クライアントまたは PROM を利用し、
たとえば 1 分おきに
RDREADを送り続ける - 10 分程度観測しても idle timeout ログが出ないこと
- テスト用クライアントまたは PROM を利用し、
たとえば 1 分おきに
-
タイムアウト後の再 open
- 1 回目の
bo rd→ idle timeout で close - その後、再度
bo rdして正常にブートできることを確認
- 1 回目の