Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save tsutsui/f6bee59c4f06193d8dd4368d46eb5df6 to your computer and use it in GitHub Desktop.

Select an option

Save tsutsui/f6bee59c4f06193d8dd4368d46eb5df6 to your computer and use it in GitHub Desktop.
SONY NEWS、具体的には CISC-NEWS NWS-1750 の PROM のネットワークブート仕様に対応するサーバー側デーモンの設計書

remotediskd(8) 設計仕様書(最新版)

1. 概要

1.1 目的

remotediskd(8) は、Sony NEWS の PROM/ブートローダが利用する 「rd(remote disk)」プロトコル によるネットワークブートを提供するデーモンである。

  • NEWS 実機からの bo rd 等の操作に応答し、 指定されたディスクイメージファイルを「リモートディスク」として提供する。
  • 将来的に NEWS 以外の “rd 風” プロトコルクライアントにも拡張可能な汎用設計を目指す。

1.2 前提

  • 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 相当)

1.3 役割

  • /etc/remotediskd.conf を読み、どのクライアント(MAC)に対してどのディスクイメージをどのユニットで提供するかを決定する。

  • /etc/ethers を使って MAC アドレスとホスト名の対応を解決する。

  • クライアントからの rd 要求に対し、対象ディスクイメージファイルを

    • 遅延 open/mmap
    • 三段階のクローズ条件(RDCLOSE, RDOPEN, idle timeout) で管理しながら READ データを返す。

2. 外部仕様

2.1 コマンド名・インストール

  • 実行ファイル名: remotediskd
  • セクション: remotediskd(8)
  • 通常は root 権限で起動することを想定。

2.2 コマンドラインオプション

2.2.1 インターフェース指定

-i interface
  • 指定された場合、そのインターフェースでのみ SRQ/RPC の受信・送信を行う。

  • 省略時は rbootd(8) と同様に、

    1. システムのインターフェースリストから
    2. 「loopback 以外」で「UP」のインターフェースを
    3. 番号の若い順に走査し
    4. 最初に見つかったものを採用
  • 例: -i re0

2.2.2 デバッグオプション

-d
-d -d    (レベル2)
  • -d が 1 回以上指定された場合:

    • フォアグラウンドで実行(daemon化しない)
    • ログを標準エラーにも出力
  • -d の回数でログレベルを変える案:

    • レベル 0(指定なし): 最小限(起動/致命的エラー/重要な警告)
    • レベル 1(-d): 通常の動作ログ(クライアント接続、RDOPEN/RDREAD 等)
    • レベル 2(-dd): パケットの詳細(必要なら hexdump など)

    ※ 初期実装ではレベル 0/1 を主に使い、2 はフックだけ用意しておいてもよい。

2.2.3 設定ファイル指定

-c config_file
  • 設定ファイルパスを指定。省略時は /etc/remotediskd.conf を用いる。

2.2.4 イメージ用ディレクトリ指定

-s image_root
  • RD 応答に用いるイメージファイルを置くディレクトリを指定。
  • 省略時のデフォルトは isibootd(8) に合わせて /tftpboot
  • 設定ファイルに記述されるイメージパスは、このディレクトリからの相対パス(もしくは絶対パスも許容する方針は実装時に決める)。

3. 設定ファイル仕様

3.1 /etc/remotediskd.conf の書式

3.1.1 基本フォーマット

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

3.1.2 エスケープ

  • ファイル名にカンマを含めたい場合は \ でエスケープ:

    newsclient2   weird\,name.img
    
  • 実装は簡易でよく、「\ は次の 1 文字をリテラル扱い」程度で十分。

3.2 /etc/ethers を利用した MAC ↔ hostname 解決

  • remotediskd/etc/ethers を前提とし、ether_ntohost(3) で MAC→hostname を引く。
  • 起動時に /etc/ethers の存在を確認し、ない・読めない場合は警告を出して終了(仕様として「必須」扱いにしてよい)。

4. ネットワークプロトコル

4.1 使用ポートとソケット

  • SRQ 用: UDP ポート 3073
  • RPC 用: UDP ポート 3072

remotediskd はこの 2 つに対し、別々のソケット を用意する。

  • srq_sock : 3073/UDP に bind
  • rpc_sock : 3072/UDP に bind

メインループでは select() または poll() で両ソケットを同時監視する。

4.2 SRQ メッセージと応答

  • フォーマットはプロトタイプ newsrd_bootd と同一。

  • クライアントからの SRQ(ニュース PROM からのブロードキャスト)を受け取り、 対象インターフェースの IP アドレスを <server_ip> としてエンコードし、 クライアントの送信元ポート(4096)へ unicst で応答する。

  • 仕様変更ポイント:

    • 応答に埋め込む <server_ip> は、

      • 元々の -s オプションによる手動指定を廃止し
      • 実際に応答を送信しているインターフェースの IP アドレスを用いる

4.3 RPC メッセージと rd プロシージャ

  • RPC は ONC RPCv2 ベース。UDP 上で XID 付き。

  • 対応するプロシージャ:

    • RPCRDOPEN (proc=1)
    • RPCRDREAD (proc=3)
    • RPCRDCLOSE (proc=?; プロトタイプで判明している値を流用)
  • 解析・応答フォーマットはプロトタイプ newsrd_bootd の実装を踏襲する。


5. 内部データ構造・モジュール設計

5.1 ソースファイル構成

想定ファイル構成:

  • 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(デバッグ用)、ユーティリティ

5.2 設定関連構造体

/* 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;
};

5.3 ランタイム状態構造体

/* (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;
};

6. 処理フロー(時間軸)

6.1 起動時

  1. main() にてオプション解析

    • -i, -d, -c, -s を解析
    • コマンドラインレベルで判明するエラー(存在しない設定ファイルパス指定など)はここで即エラー終了。
  2. /etc/ethers の存在確認

    • 読めない場合はログを出し、終了(rd は /etc/ethers 前提とする)。
  3. 設定ファイル読み込み (rd_config_load())

    • remotediskd.conf パース

    • ここでは ファイルの存在チェックは行わない

      • 理由: イメージは unit ごとに lazy open するため、 実際にその unit に対して RD 要求が来たときにエラーとする。
  4. ソケットオープン・バインド

    • インターフェース決定(-i 指定/自動選択)
    • 3073/UDP, 3072/UDP に bind
  5. -d が指定されていなければ daemon 化(fork / setsid 等)。

  6. メインループへ。

6.2 メインループ

疑似コードイメージ:

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);
}

7. イメージファイルのライフサイクル

7.1 open/mmap のタイミング(lazy open)

  • イメージファイルは「必要になったとき」に unit 単位で open/mmap する。

  • 具体的には:

    1. クライアントからの最初の RPCRDOPEN 受信時

      • そのクライアントに対するセッション開始とみなし、

        • 対象クライアントの 全ユニット の既存 fd/mmap を close/munmap
        • ただしこの時点ではまだ new open しない(後続 READ まで lazy でもよい)
      • ru_last_access = now の初期化

    2. 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 に更新。

7.2 close/munmap のタイミング

7.2.1 RPCRDCLOSE 受信時

  • クライアントから RDCLOSE proc を受け取った場合、

    • 対象 (client, unit)ru_fd が有効なら munmap()close() し、
    • ru_fd = -1, ru_base = NULL, ru_size = 0 にリセット。
    • ru_valid は変更しない(設定上の有効性なので)。

7.2.2 RPCRDOPEN 受信時

  • OPEN は「このクライアントの新しいセッション開始」とみなし、

    • 対象クライアントの 全ユニットの fd/mmap を一括 close/munmap する。
    • これはプロトタイプ newsrd_bootd の挙動を踏襲。
  • その後、ru_last_access = now で初期化(ユニット全体)。

7.2.3 SIGHUP による設定再読み込み

  • SIGHUP を受けたら:

    1. シグナルハンドラでフラグを立てる(ハンドラ内では最小限の処理)

    2. メインループの安全なタイミングでフラグを検出し、以下を実施:

      • 旧設定(rd_config)に紐付く全クライアント状態の 全ユニットの fd/mmap を close/munmap

      • 旧クライアント状態(rd_client_state)をすべて破棄

      • 設定ファイルを再パース

        • パースに成功したら、新しい rd_config と新しい rd_client_state リストをセット

        • 失敗した場合:

          • 旧設定での運用を継続するか、
          • それともエラーとしてデーモンを終了するかは設計選択。
          • 安全志向なら「旧設定のまま継続」が現実的。

7.2.4 デーモン終了時

  • main() の終了処理で、rs_clients の全 (client, unit) について fd/mmap を close/munmap する。

7.3 アイドルタイムアウトによる close

7.3.1 idle timeout パラメータ

  • rc_idle_timeout(秒)を導入。初期値はコンパイル時定数として:
#define RD_IDLE_TIMEOUT_DEFAULT 300  /* 秒, 0以下で無効 */
  • 設計上:

    • 初期実装ではコマンドラインオプションは設けず固定値 300 秒。
    • 将来拡張として -t sec 等で変更可能にする余地は残す。

7.3.2 last_access の更新

  • ru_last_access は以下のタイミングで更新する:

    • RPCRDOPEN 正常処理後

      • 対象クライアントの全ユニットに対して ru_last_access = now をセット
    • RPCRDREAD 正常処理後

      • 対象ユニットの ru_last_access = now を更新

7.3.3 GC の実行タイミングとロジック

  • メインループ内で 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 される。

7.3.4 timeout 後の再アクセス

  • idle timeout により fd/mmap を close 済みの状態で、同一クライアントから再び RPCRDOPENRPCRDREAD が来た場合:

    • RPCRDOPEN の場合:

      • セッションリセットとして全ユニット close(既に close 済みであれば no-op)
      • その後の RPCRDREAD で通常どおり lazy open/mmap が行われる。
    • RPCRDREAD の場合:

      • ru_valid == 1 && ru_fd < 0 なら、再度 open() / mmap() を行う。
      • これにより、 「最初のブートから長時間放置 → idle timeout で close → また bo rd しても正常にブートできる」 というサイクルを保証する。

8. unknown client ログ抑止

  • 設定ファイルに該当 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_macrc_host をキーとみなす
    • or unknown 用に別の簡易テーブルを持っても良いが、NEWS の台数は少ない前提なので、 単純に「最後に unknown ログを出した時刻」を 1 グローバルにするだけでも実用上問題ない。

9. エラーチェックと UI 動作(設定・ファイル関連)

9.1 コマンドラインオプション

  • -c で指定された設定ファイルパスが存在しない/読めない場合:

    • 起動時に即エラー終了(syslog + 標準エラーにメッセージ)。
  • -s で指定されたイメージディレクトリが存在しない/ディレクトリでない:

    • 起動時にチェックして即エラー終了。

9.2 設定ファイル remotediskd.conf

  • パース時:

    • 文法エラー(カラム数が足りない、トークン分割に失敗など)があれば、 その行をスキップし、警告ログを出す。

    • hostname だけ書いて image_list が空(イメージが 1 つもない)場合:

      • 「対象ホストに対応するイメージがない」と警告ログを出し、そのホストエントリを無効としてスキップする。
  • イメージファイル名の存在チェック:

    • 起動時には行わない

    • 実際に (client, unit) に対して RPCRDREAD が来た際に open して初めて存在確認を行う。

    • その時点で open に失敗した場合、

      • RPC レベルのエラーとしてクライアントに返す(具体的なエラーコードはプロトタイプに合わせる)。
      • サーバ側ログに「イメージファイルが見つからない/open できない」旨を出力。

9.3 /etc/ethers

  • 起動時に /etc/ethers の存在と読み取り可否を確認。
  • 読めない場合は「/etc/ethers が読めないため hostname 解決ができない」とエラーを出し、起動失敗とする。

9.4 SIGHUP 時の挙動

  • SIGHUP 受信時:

    • 設定ファイル再読み込みを試みる。

    • 再パースに成功した場合:

      • 旧設定/旧クライアント状態の全ファイルを close/munmap したうえで、 新設定を反映して再開。
      • 「config reloaded」ログを出力。
    • 再パースに失敗した場合:

      • 保守性重視の方針としては「旧設定で動作継続」が無難。

      • どちらの挙動にするかは明示的に仕様化しておく。

        • ここでは「旧設定継続」を採用することを推奨。

10. テスト仕様(抜粋)

10.1 基本動作試験

  1. 単一クライアント・単一ユニットでの正常ブート

    • 条件:

      • remotediskd.conf に newsclient1 の unit0 イメージを設定
    • 手順:

      • NEWS newsclient1 から bo rd 実行
    • 期待結果:

      • RPCRDOPEN → 512B READ → 8KB READ(等)が正しく実施される
      • 適切なイメージファイルから読んだデータでブートが進行する
      • サーバログに OPEN/READ の情報が出る
  2. 複数ユニット設定のブート

    • unit0, unit1 など複数イメージを設定し、 PROM が unit0 のみ使うケースを確認(unit1 は open されない)。

10.2 設定ファイル異常系

  1. hostname のみ指定で image_list が空

    • そのホストエントリは無視される
    • 警告ログを出すが、他の正しいエントリの動作には影響しない
  2. image_list のファイルが存在しない

    • 該当ユニットに対して RDREAD が来たときに open に失敗
    • RPC エラーを返す
    • サーバログに「open 失敗」のメッセージが出る
  3. /etc/ethers が存在しない/読めない

    • 起動時にエラー終了することを確認

10.3 unknown client 試験

  1. /etc/ethers に存在せず、かつ remotediskd.conf にも記述されていない MAC からのアクセス

    • 初回に「unknown client〜」のログが出る
    • その後短時間に繰り返しアクセスしても、ログがスパム状態にはならない (一定時間抑止ロジックが効いている)

10.4 SIGHUP 再読み込み試験

  1. 正常な新設定への切替

    • 起動中に remotediskd.conf を編集し、ユニット定義を変更
    • kill -HUP <pid> を送る
    • 新設定でのブート動作に切り替わること
    • 旧設定由来のイメージ fd がすべて close されていること
  2. 新設定が文法エラーの場合

    • SIGHUP 後にパースエラーが発生した場合、
    • 旧設定で動作が継続することをログで確認

10.5 idle timeout 試験

  1. タイムアウトで close される

    • bo rd を一度実行して正常にブートさせる

    • その後クライアントからのアクセスを止めて 300 秒以上放置

    • サーバログに

      client <host> unit 0: idle timeout (300 sec), closing image
      

      のようなメッセージが出る

    • fstat / lsof 等でイメージ fd が閉じられていることを確認

  2. タイムアウト前にアクセスが続く場合 close されない

    • テスト用クライアントまたは PROM を利用し、 たとえば 1 分おきに RDREAD を送り続ける
    • 10 分程度観測しても idle timeout ログが出ないこと
  3. タイムアウト後の再 open

    • 1 回目の bo rd → idle timeout で close
    • その後、再度 bo rd して正常にブートできることを確認

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment