Skip to content

tuntap driver が gcc-15 で build できない #100

@satokaz

Description

@satokaz

激詰してもらっている

tuntap ドライバ コードレビュー(Solaris 11.4 観点)

レビュー初版日: 2026-02-27
最終更新日: 2026-03-12
対象ブランチ: solaris
対象OS: Solaris 11.4


更新メモ(2026-03-12)

このドキュメントは初版(2026-02-27)の指摘を保持しつつ、現行ソースとの照合結果を反映した。
対応記録は本書末尾の「付録: レビュー対応記録(2026-03-12)」に統合。
なお、同日夜の追加修正として以下を反映済み:

  • ee2c813: tunerr() 越境書き込み修正
  • ceac8c9: M_PROTO/M_PCPROTO 最小長チェック追加
  • 9cac718: DL_UNITDATA_REQqwriter(PERIM_OUTER) 依存解除
  • a73fd07: tun_frame() fan-out の dupmsg 削減
  • 153172b: tun_bind_req() の再入安全化

0. Solaris 11.4 における TUN/TAP の位置づけ

Solaris 11.4 組み込みのトンネル機能

Solaris 11 には iptun ドライバが標準搭載されており、dladm create-iptun で作成できる。

dladm create-iptun -T ipv6 -a local=192.168.1.1 -a remote=192.168.2.1 ip6tun0

対応プロトコル: IPv4-in-IPv4(4in4)、IPv6-in-IPv4(6in4)、GRE
これはカーネル内での IP カプセル化用であり、ユーザー空間プロセスがパケットを直接読み書きする仕組みではない

なぜこのリポジトリが必要か

OpenVPN・WireGuard・vpnc などのユーザー空間 VPN は、
カーネルドライバが提供する /dev/tun/dev/tap ファイルディスクリプタに対して
read()/write() を行うことでパケットを送受信する。
この「ユーザー空間向け TUN/TAP デバイス」は Solaris 11.4 に標準搭載されていないため、
本リポジトリのドライバが必要となる。

特徴 Solaris iptun このリポジトリの tun/tap
用途 IP カプセル化(カーネル内) ユーザー空間プロセスがパケットを読み書き
代表的な利用 6in4 トンネル、GRE OpenVPN、WireGuard、vpnc
ユーザー空間への fd なし あり(read()/write() でパケット I/O)
/dev デバイス /dev/iptun(管理用) /dev/tun/dev/tap
GLDv3 対応 あり なし(DLPI/STREAMS スタイル)

Solaris 11.4 での確認コマンド

modinfo | grep tun          # ロード済みドライバを確認
ls /dev/tun /dev/tap        # デバイスノードの存在確認
pkg info driver/network/tun # IPS パッケージとして提供されているか確認

凡例

重要度 意味
🔴 CRITICAL カーネルパニック・セキュリティ脆弱性・データ破壊につながるバグ
🟠 HIGH 動作不正・メモリ破壊・競合状態などの深刻な問題
🟡 MEDIUM 機能制限・移植性問題・非推奨 API の使用
🔵 LOW コード品質・スタイル・軽微なバグ

1. tun.c — カーネルドライバ本体

🔴 [CRITICAL-1] TUNNEWPPA/TUNSETPPA でのオフバイワン(境界外配列アクセス)

ファイル: tun.c
箇所: tun_ioctl() 内 TUNNEWPPA/TUNSETPPA ケース

// 現状(バグあり)
if( p < -1 || p > TUNMAXPPA){   // TUNMAXPPA = 20 のとき p==20 が通過してしまう
    tuniocack(wq, mp, M_IOCNAK, 0, EINVAL);
    return;
}
// → tun_ppa[20] は tun_ppa[TUNMAXPPA] で配列外(有効は0〜19)

tun_ppastatic struct tunppa *tun_ppa[TUNMAXPPA] で宣言され、
有効インデックスは 0TUNMAXPPA-1(= 0〜19)。
p == TUNMAXPPA (20) のとき p > TUNMAXPPA は偽となり、
tun_ppa[20] への境界外アクセスが発生する。

TUNSETPPA / tun_attach_req() にも同様の問題がある。

修正案:

// TUNNEWPPA
if( p < -1 || p >= TUNMAXPPA){   // > → >=

// TUNSETPPA / tun_attach_req
if( p < 0 || p >= TUNMAXPPA){    // > → >=

🔴 [CRITICAL-2] TUNNEWPPA ioctl で mp->b_cont の NULL チェックなし

ファイル: tun.c
箇所: tun_ioctl()TUNNEWPPA ケース

case TUNNEWPPA:
    p = *(int *)mp->b_cont->b_rptr;  // b_cont が NULL の場合カーネルパニック

M_IOCTL メッセージに b_cont がない(データブロックなし)場合、
mp->b_cont は NULL となる。
ユーザープログラムが不正な ioctl を発行した場合(または I_STR でなく直接 ioctl() を使った場合)に
カーネルパニックが発生する。

修正案:

case TUNNEWPPA:
    if (mp->b_cont == NULL || MBLKL(mp->b_cont) < sizeof(int)) {
        tuniocack(wq, mp, M_IOCNAK, 0, EINVAL);
        return;
    }
    p = *(int *)mp->b_cont->b_rptr;

🟠 [HIGH-1] tun_frame() とリスト変更操作の間に競合状態の可能性(再評価)

ファイル: tun.c
箇所: tun_frame(), tun_attach_req(), tun_detach_req()

  • 現在の DL_UNITDATA_REQqwriter を通さない direct path で処理される(9cac718)。
  • tun_frame()ppa->p_str を走査しつつ配送するため、attach/detach/close と競合し得る点は設計課題として残る。
  • 直列化で抑えるより、共有状態を明示ロック(per-PPA ロック)で守る方針が妥当。

Solaris 11.4 のマルチスレッド STREAMS では、ppa->p_str リストを安全に走査・変更するために、アクセスパターンを統一する必要がある。

現行方針:
DL_UNITDATA_REQ の direct path は維持し、per-PPA ロック設計で整合性を担保する:

/* direct path (current) */
case DL_UNITDATA_REQ:
    tun_unitdata_req(wq, mp);
    break;

詳細は tun-driver-per-ppa-lock-design.md を参照。


🟠 [HIGH-2] TAP モードで全 PPA が同一 MAC アドレスを持つ

ファイル: tun.c
箇所: tun_generate_mac_addr(), tun_alloc_ppa()

// tun_generate_mac_addr(): ドライバロード時に一度だけ1つの MAC アドレスを生成
static struct ether_addr localmacaddr;  // グローバル1個

// tun_alloc_ppa(): 各 PPA に同じ MAC をコピー
bcopy(&localmacaddr, &ppa->etheraddr, ETHERADDRL);

複数の tap インターフェース(tap0, tap1, ...)が同時に使われると、
すべてが同一の MAC アドレスを持つことになり、Ethernet フレームの配送が正しく機能しない。

修正案: 各 PPA のユニーク MAC 生成(例: ベースアドレス + PPA ID):

static void tun_alloc_ppa_mac(struct tunppa *ppa)
{
    bcopy(&localmacaddr, &ppa->etheraddr, ETHERADDRL);
    /* PPA ID を最終バイトに加算してユニーク化 */
    ppa->etheraddr.ether_addr_octet[5] =
        (uchar_t)((localmacaddr.ether_addr_octet[5] + ppa->id) & 0xff);
}

🟡 [MEDIUM-1] u_long / u_short の使用(64ビット非対応型)

ファイル: if_tun.h

struct tunstr {
  u_long flags;     // 64ビット環境では8バイト(本来32ビットで十分)
  u_long state;     // DLPI state は t_uscalar_t を使うべき
  u_long sap;       // SAP は 16ビット値
  u_long minor;     // minor_t を使うべき
};

struct tundladdr {
  u_short sap;      // uint16_t が望ましい
};

Solaris 11.4 の 64ビット amd64/sparcv9 カーネルでは u_long は8バイト。
DLPI プロトコルの仕様では statet_uscalar_t(= uint32_t)であり、
サイズ不一致による構造体パディング/整合性の問題が発生する可能性がある。

修正案:

struct tunstr {
  struct tunstr  *s_next;
  struct tunstr  *p_next;
  queue_t        *rq;
  struct tunppa  *ppa;
  uint32_t        flags;
  t_uscalar_t     state;    /* DLPI state */
  t_uscalar_t     sap;
  minor_t         minor;
};

🟡 [MEDIUM-2] tunclose() でリストに str が見つからない場合 NULL 参照

ファイル: tun.c
箇所: tunclose() (行 ~345)

for(prev = &tun_str; (tmp = *prev); prev = &tmp->s_next)
   if( tmp==str ) break;
*prev = tmp->s_next;   // str が見つからなかった場合、tmp == NULL → パニック

str がリストに存在しない場合(状態異常時)、ループ終了時に tmp == NULL となり、
tmp->s_next で NULL ポインタ参照が発生する。

修正案:

for(prev = &tun_str; (tmp = *prev); prev = &tmp->s_next)
   if( tmp == str ) break;
if (tmp != NULL)       /* 安全チェック追加 */
    *prev = tmp->s_next;
else
    cmn_err(CE_WARN, "tun: tunclose: str %p not found in list\n", (void*)str);

🟡 [MEDIUM-3] tun_physaddr_req() で関数型が void なのに static でない

ファイル: tun.c

void tun_physaddr_req(queue_t *wq, mblk_t *mp)  // static が抜けている

他の static 関数と一貫性がなく、不要なカーネルシンボル露出になる。

修正案: static void tun_physaddr_req(...) に変更。


🟡 [MEDIUM-4] 権限チェックが未実装(Solaris 11.4 特有)

ファイル: tun.c
箇所: tunopen()

static int tunopen(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *credp)
{
    // credp を使用した権限チェックが一切ない

Solaris 11.4 では最小権限モデル(Least Privilege)が重視される。
/dev/tun/dev/tap はネットワークに直接アクセスできるため、
secpolicy_net_rawaccess(credp) または PRIV_NET_RAWACCESS のチェックを行うべき。

修正案:

#include <sys/policy.h>

static int tunopen(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *credp)
{
    int rc;
    if ((rc = secpolicy_net_rawaccess(credp)) != 0)
        return rc;
    // ...

🟡 [MEDIUM-5] Solaris 11.4 では IPS パッケージが標準(SVR4 パッケージ/手動インストール非推奨)

ファイル: Makefile.in, configure.in

現在は sudo make install で直接 /usr/kernel/drv/ に配置する方式。
Solaris 11.4 は IPS (Image Packaging System) が標準で、
手動ファイル配置は IPS の追跡外となり、パッケージ更新やゾーン管理と干渉する。

推奨対応: IPS パッケージマニフェスト (.p5m) の作成。
最低限の例:

set name=pkg.fmri value=pkg:/driver/network/tuntap@1.3.2
set name=pkg.description value="TUN/TAP pseudo device driver"
driver name=tun
driver name=tap
file path=usr/kernel/drv/amd64/tun mode=0755
file path=usr/kernel/drv/amd64/tap mode=0755
file path=usr/kernel/drv/tun.conf mode=0644
file path=usr/kernel/drv/tap.conf mode=0644
file path=usr/include/net/if_tun.h mode=0644

🔵 [LOW-1] DBG マクロが GCC 拡張構文(Sun Studio 非互換)

ファイル: if_tun.h

#define DBG( a... )     // GCC 拡張(GNU C)

a... は GCC/Clang 独自の可変引数マクロ省略記法。
Solaris Studio (Oracle Developer Studio) でビルドする場合はコンパイルエラーになる。

修正案:

#ifdef TUN_DEBUG
#define DBG  cmn_err
#else
#define DBG(...)  ((void)0)  // C99 標準の可変引数マクロ
#endif

🔵 [LOW-2] configure.in でGCCバージョンチェックに誤った変数名

ファイル: configure.in

GCC_MAJOR_VERSION=`echo $GCC_VERSION | cut -f 1 -d .`
GCC_MINOR_VERSION=`echo $GCC_VERSION | cut -f 2 -d .`
...
if test "$GCC_MAJOR_VERSION" -ge 4 \
   -a "$MINOR_VERSION" -ge 5 ; then   # ← $GCC_MINOR_VERSION であるべき
    KCFLAGS="$KCFLAGS -m64 -mcmodel=large -mno-red-zone"

$GCC_MINOR_VERSION ではなく $MINOR_VERSION(OS のマイナーバージョン = 11)が使われている。
Solaris 11 では $MINOR_VERSION は常に 11 なので 11 >= 5 は常に真となり、
GCC >= 4 の場合は常に -mcmodel=large が選ばれる。
現実には GCC 4.x+ が前提なので偶発的に正しく動いているが、潜在的なバグ。

修正案:

if test "$GCC_MAJOR_VERSION" -ge 4 \
   -a "$GCC_MINOR_VERSION" -ge 5 ; then

🔵 [LOW-3] TUNTAP_TUN 時の DL_ENABMULTI_REQ / DL_DISABMULTI_REQ の扱い

ファイル: tun.c
箇所: tun_dlpi()

#elif defined(TUNTAP_TUN)
     case DL_ENABMULTI_REQ:
     case DL_DISABMULTI_REQ:
#endif
     default:
        tundlerrack(wq, mp, prim, DL_UNSUPPORTED, 0);

TUN モードでは DL_ENABMULTI_REQ / DL_DISABMULTI_REQ を fall-through で DL_UNSUPPORTED にエラーとしている。
TAP モードでは DL_OK_ACK を返す。
Solaris 11.4 の IP スタックは場合によって multicast ioctl を発行することがあり、
エラー時の動作が想定外になりうるため、TUN でも DL_OK_ACK を返す方が堅牢。


🔵 [LOW-4] Makefile.in clean ターゲットに重複・誤記

ファイル: Makefile.in

clean:
        rm -f tun tap *.o *~
        rm -f tun tun *.o *~   # "tun tun" という重複、かつ tap が抜けている

修正案:

clean:
        rm -f tun tap *.o *~

2. tun-test/tunctl.c — テストユーティリティ

🟠 [HIGH-3] get_devname() でのメモリリーク

ファイル: tun-test/tunctl.c

char *get_devname (char *interface)
{
    char *devname = NULL;
    int   i;
    for ( i = 0 ; i < TUNMAXPPA ; i++){   // ループが TUNMAXPPA 回まわる
        if ((instance = strpbrk(interface, "0123456789")) == NULL)
            continue;
        devname = malloc(30);   // 毎回 malloc するが前の領域を free しない
        bzero(devname, 30);
        strncpy(devname, interface, instance - interface);
    }
    return(devname);   // 最後の malloc した1つだけ返し、残りは全てリーク
}

malloc(30) が最大 TUNMAXPPA (20) 回呼ばれるが、
最後の1つ以外は free() されないメモリリークが発生する。
また、strpbrk() は毎回同じポインタを返すため、
ループ自体に意味がなく、同じ処理を20回繰り返すだけになっている。

修正案:

char *get_devname (char *interface)
{
    char *instance;
    char *devname;

    if ((instance = strpbrk(interface, "0123456789")) == NULL)
        return NULL;

    devname = malloc(instance - interface + 1);
    if (devname == NULL)
        return NULL;
    strncpy(devname, interface, instance - interface);
    devname[instance - interface] = '\0';
    return devname;
}

🟠 [HIGH-4] main() でのメモリリーク(nodeget_devname() 返り値)

ファイル: tun-test/tunctl.c

if(node == NULL){
    node = malloc(NODE_LEN);
    bzero(node, NODE_LEN);
    snprintf(node, NODE_LEN, "/dev/%s", get_devname(tun));  // get_devname() 返り値をfreeしない
}
// ...
close(ip_fd);
close(tun_fd);
exit(0);  // node も get_devname() の返り値も free されない

プロセス終了時にOSが回収するため実害は少ないが、静的解析ツールでの誤検出を避けるためにも修正が望ましい。

修正案:

if(node == NULL){
    char *devname = get_devname(tun);
    if (devname == NULL) {
        fprintf(stderr, "Invalid device name: %s\n", tun);
        exit(1);
    }
    node = malloc(NODE_LEN);
    if (node == NULL) { perror("malloc"); exit(1); }
    snprintf(node, NODE_LEN, "/dev/%s", devname);
    free(devname);
}

🟡 [MEDIUM-6] delete_if() で ARP unlink が TAP 専用だが理由のコメントが不明確

ファイル: tun-test/tunctl.c

if( strncmp(tun, "tap", 3) == 0){
    /* just in case, unlink arp's stream */
    arp_muxid = ifr.lifr_arp_muxid;
    if (ioctl (ip_fd, I_PUNLINK, arp_muxid) < 0){
        /* ignore err */
    }
}

Solaris 11.4 では ARP の muxid は IPv4インターフェースのプランブ時に設定される。
arp_muxid が有効かどうか(ゼロでないか)のチェックなしに I_PUNLINK を呼ぶとエラーになりうる。
エラーを無視しているため動作上は問題ないが、意図を明示すべき。

修正案:

if( strncmp(tun, "tap", 3) == 0 && ifr.lifr_arp_muxid != 0 ){
    (void) ioctl(ip_fd, I_PUNLINK, ifr.lifr_arp_muxid);  /* best effort */
}

🔵 [LOW-5] add_if() で IPv6 プランブが未対応

ファイル: tun-test/tunctl.c

現在の add_if() は IPv4 (/dev/ip) への I_PLINK のみ実装。
Solaris 11.4 で IPv6 対応インターフェースとして使う場合、
/dev/ip6 へのリンクも必要。
OpenVPN + IPv6 トンネルなど用途によっては問題になる。


3. tun-test/test.sh — テストスクリプト

🟠 [HIGH-5] nobuild 変数のタイポ(テストが常に実行される可能性)

ファイル: tun-test/test.sh

main() { 
    if [ "nobuild" = "$1" ]; then
        nobuild=1
        cd ../
    else 
        nobuld=0      # ← タイポ: nobuild ではなく nobuld
    fi
    ...
    if [ "$nobuild" -eq 0 ]; then   # $nobuild は未設定 → 空文字列

nobuild 以外の引数(または引数なし)で呼ばれた場合、
else 節で nobuld=0(タイポ)と設定し、
$nobuild は未設定のままになる。
[ "" -eq 0 ] は sh 実装により動作が異なり、
Solaris /usr/xpg4/bin/sh では算術比較エラーとなる可能性がある。

修正案:

    else 
        nobuild=0     # nobuld → nobuild
    fi

🟠 [HIGH-6] TESTLOGFILE が初期化前に使用される

ファイル: tun-test/test.sh

main() { 
    echo "##" | tee -a $TESTLOGFILE         # ← init() 前に呼ばれる
    echo "## Initializing test env" | tee -a $TESTLOGFILE
    echo "##" | tee -a $TESTLOGFILE
    init                                     # ← ここで TESTLOGFILE が設定される

init() が呼ばれる前に $TESTLOGFILE を使用しているため、
最初の3行のログはファイルに書かれない(tee -a "" はエラー扱い)。

修正案: init() を最初に呼ぶ:

main() { 
    if [ "nobuild" = "$1" ]; then
        nobuild=1
        cd ../
    else 
        nobuild=0
    fi

    init      # ← 最初に呼ぶ
    
    echo "##" | tee -a $TESTLOGFILE
    echo "## Initializing test env" | tee -a $TESTLOGFILE

🔵 [LOW-6] uninstall()make uinstall のタイポ

ファイル: tun-test/test.sh

uninstall(){
    sudo make uinstall | tee -a $TESTLOGFILE  # ← uninstall の typo

make uinstall は存在しない Makefile ターゲット → エラーになるが、test.shuninstall() は呼ばれていないため現状は問題なし。

修正案:

sudo make uninstall | tee -a $TESTLOGFILE

🔵 [LOW-7] loadcheck()modinfo の出力が空の場合の [ -z $loaded ] は引用符が必要

ファイル: tun-test/test.sh

loadcheck(){
    loaded=`modinfo| grep "$1 (TUN/TAP"`
    if [ -z $loaded ]; then   # $loaded を引用符で囲む必要がある

$loaded が空の場合、[ -z $loaded ][ -z ] となり、
Solaris sh では構文エラーになりうる。

修正案:

if [ -z "$loaded" ]; then

4. configure.in / Makefile.in

🔴 [CRITICAL-3] GCC (x86-64) でカーネルモジュールに SSE/MMX 命令が生成される

ファイル: configure.in
箇所: amd64 ケースの KCFLAGS 設定
参照: Writing Device Drivers in Oracle Solaris 11.4 — Compiling and Linking the Driver

Solaris x86 カーネルは MMX / SSE 命令をサポートしない。
カーネルコンテキストでこれらの命令を実行すると カーネルパニック になる。
この制約は GCC でも Studio でも同様に適用される。

Oracle 公式ドキュメントの記述(E61061 — eqbvb)

Oracle Solaris 11.4 "Writing Device Drivers" ガイド(E61061)は以下を明記している:

x86 アーキテクチャー向けにコンパイルする場合は、コンパイルしても MMX 命令または
SSE 命令が生成されないことを確認する必要があります。x86 カーネルでは MMX 命令および
SSE 命令はサポートされません。MMX 命令または SSE 命令を使用するとカーネルパニックが
発生するため、使用しないようにしてください。

  • Oracle Developer Studio の場合: -xregs=no%float
  • GCC の場合: -mno-mmx -mno-sse -mno-sse2

また同ドキュメントが示す amd64 向け GCC の公式推奨ビルドコマンド:

gcc -D_KERNEL -m64 -mcmodel=kernel -mno-red-zone \
    -ffreestanding -nodefaultlibs \
    -mno-mmx -mno-sse -mno-sse2 \
    -c mydriver.c
ld -r -o mydriver mydriver.o

初版レビュー時点(2026-02-27)の configure.in との差分

フラグ 公式推奨 現状 configure.in 影響
-mno-red-zone ✅ 必須 ✅ あり
-mno-mmx -mno-sse -mno-sse2 ✅ 必須 ❌ なし カーネルパニック
-ffreestanding ✅ 必須 ❌ なし 標準ライブラリ依存コードが混入の恐れ
-nodefaultlibs ✅ 推奨 ❌ なし 標準ライブラリのシンボルリンクの恐れ
-mcmodel=kernel/large ✅ 必須 ✅ あり (large)
-m64 ✅ 必須 ✅ あり

GCC x86-64 で SSE が生成される理由

  • x86-64 ABI では SSE2 がベースライン命令セットに含まれており、GCC は SSE2 を自由に使用してよいとみなす
  • GCC 4.x 以前は -O0 相当ではほぼ SSE を使わないが、-O2 以上では整数演算・メモリコピーにも XMM レジスタを積極利用する
  • GCC 15 では自動ベクトル化 (auto-vectorization) がデフォルト有効で、ループやメモリ操作に AVX/AVX2 を含む SIMD 命令が生成されうる

Sun Studio 12.4 と GCC の対応関係

コンパイラ 問題フラグ 禁止フラグ
Studio 12.4+ デフォルト最適化で SSE -xregs=no%float
GCC (x86-64) デフォルト ABI + -O2 以上 -mno-sse -mno-sse2 -mno-mmx(または -mgeneral-regs-only

修正案

-mgeneral-regs-only は GCC 5 以降で利用可能(GCC 15 で確実にサポート済み)。
汎用レジスタのみ使用を許可し、FPU/MMX/SSE/AVX レジスタを一切使わないことをコンパイラに強制する
-mno-sse -mno-sse2 -mno-mmx の上位互換)。

# configure.in の amd64 / GCC ケース(修正後)
if test "$GCC" = yes; then
    if test "$GCC_MAJOR_VERSION" -ge 4 \
       -a "$GCC_MINOR_VERSION" -ge 5 ; then
        KCFLAGS="$KCFLAGS -m64 -mcmodel=large -mno-red-zone \
                 -ffreestanding -mgeneral-regs-only"
    else
        KCFLAGS="$KCFLAGS -m64 -mcmodel=kernel -mno-red-zone \
                 -ffreestanding -mgeneral-regs-only"
    fi

GCC 5 未満(-mgeneral-regs-only が使えない場合)の代替:

KCFLAGS="$KCFLAGS -ffreestanding -mno-mmx -mno-sse -mno-sse2 -mno-sse3 -mno-avx"

-ffreestanding の役割: GCC に「ホスト環境(標準 C ランタイム)が存在しない」と伝える。
これにより memcpy()memset() などを GCC 組み込みに置換する挙動を抑制し、
カーネル内の実装(bcopybzero)との整合性を保つ。

PERF-1 との関係

-O2 の追加(PERF-1)と本修正はセットで適用すること。
-O2 のみ追加して SSE 禁止フラグを入れないと、最適化によって SSE 命令が生成され
カーネルパニックが発生するリスクが高まる。


🟡 [MEDIUM-7] configure.inAC_PREFIX_DEFAULT に引数なし

ファイル: configure.in

AC_PREFIX_DEFAULT

AC_PREFIX_DEFAULT(PREFIX) はデフォルトプレフィックスを設定するマクロで、
引数必須。引数なしは Autoconf の警告/エラーの原因となる。

修正案: 削除または明示的に指定:

AC_PREFIX_DEFAULT([/usr/local])

🔵 [LOW-8] configure.in が古い AC_INIT 形式 / autoconf 2.13 前提

ファイル: configure.in

AC_INIT([tuntap], [1.3.2], [admin2@whiteboard.ne.jp])

拡張子が .inconfigure.in を使っているのは旧来の慣習。
Autoconf 2.50 以降は configure.ac が標準。
Solaris 11.4 上の最新 Autoconf ではこのままでも動作するが、
将来の互換性のために configure.ac へのリネームを推奨。


5. Solaris 11.4 アーキテクチャ上の制限事項

🟡 [MEDIUM-8] GLDv3 非対応 — dladm / ipadm との統合なし

Solaris 11.4 のネットワーク管理は GLDv3(Generic LAN Driver version 3)ベースの
mac(9E) ドライバフレームワークが標準。
本ドライバは旧来の DLPI/STREAMS スタイルで実装されており、以下の機能が未対応:

  • dladm show-link でのインターフェース表示
  • dladm create-vnic などの仮想 NIC 作成との統合
  • IPMP (IP Network Multipathing) サポート
  • Crossbow ネットワーク仮想化との統合
  • Vanity naming(インターフェース名の自由設定)

現状の I_PLINK を使った手動プランブは機能するが、
ipadm create-if などのモダンな管理ツールでは使えない。

Solaris 11.4 で完全な統合を得るには mac(9E) ベースの再実装が望ましいが、
これは大きな作業になる。既存の用途(OpenVPN 互換など)では
現状の DLPI スタイルでも動作する。


🟡 [MEDIUM-9] tun.conf / tap.confinstance=0 のみ宣言

ファイル: tun.conf, tap.conf

name="tun" parent="pseudo" instance=0;

instance=0 のみの宣言では、tunattach() が呼ばれるのは1回のみ。
これは複数の物理デバイスのような構造ではなく、CLONE デバイスとして機能するため
現状で正しい設計になっている。ただし、Solaris 11.4 の IPS パッケージ経由でインストールする場合、
ドライバのバインディングは .conf ファイルではなく add_drv(1M) の引数で制御する方式が主流。


6. パフォーマンス改善観点

🔴 [PERF-1] KCFLAGS に最適化フラグ (-O2) が設定されていない

ファイル: configure.in

KCFLAGS="$CFLAGS $KCFLAGS -D_KERNEL -I."
# -O や -O2 が明示されていない → GCC のデフォルト -O0(最適化なし)でビルドされる

GCC のデフォルトは最適化なし(-O0)。カーネルモジュールは通常 -O2 でビルドする。
最適化なしでは、インライン展開・デッドコード除去・ループ最適化が行われず、
実測でスループットが数十% 低下することがある。

⚠️ 重要: -O2 を追加する場合は CRITICAL-3 の SSE 禁止フラグ (-mgeneral-regs-only) と
必ずセットで適用すること-O2 のみ追加すると GCC が SSE/AVX 命令を積極生成し、
x86 Solaris カーネルパニックの原因となる。

修正案configure.in、amd64/GCC ケース):

# KCFLAGS への -O2 追加(架線の KCFLAGS 設定のさらに前に)
KCFLAGS="$CFLAGS $KCFLAGS -O2 -D_KERNEL -I."

# amd64 ケースに -mgeneral-regs-only を追加(SSE/MMX 禁止)
KCFLAGS="$KCFLAGS -m64 -mcmodel=large -mno-red-zone -mgeneral-regs-only"

🟠 [PERF-2] tun_eth_hdr() が毎パケット allocb() を呼ぶ

ファイル: tun.c
箇所: tun_eth_hdr(), tun_unitdata_ind()

// tun_eth_hdr(): 14バイトの Ethernet ヘッダのために毎回 allocb()
size = sizeof(struct ether_header);   // 14 バイト
if( !(nmp = allocb(size, BPRI_LO)) ){
    freemsg(mp);
    return NULL;
}
nmp->b_cont = mp;   // 元のメッセージを後ろに連結

// tun_unitdata_ind(): DLPI ヘッダのために毎回 allocb()
size = sizeof(dl_unitdata_ind_t) + TUN_ADDR_LEN + TUN_ADDR_LEN;
if( !(nmp = allocb(size, BPRI_LO)) ){
    freemsg(mp);
    return NULL;
}

allocb() は毎回 kmem_alloc() を呼ぶわけではないが(STREAMS の mblk プールから取得)、
高スループット時には mblk の確保・解放が頻繁に発生する。

改善案: 送信側(tun_eth_hdr)では、元の mblk の前方に余白があれば
b_rptr を後退させてヘッダを直接書き込む(ゼロコピー的な手法):

/* 元のブロックに前方余白があればそこに Ethernet ヘッダを書き込む */
if (mp->b_rptr - mp->b_datap->db_base >= sizeof(struct ether_header)
    && DB_REF(mp) == 1) {
    mp->b_rptr -= sizeof(struct ether_header);
    /* ヘッダをここに書き込む */
    return mp;
}
/* 余白がなければ従来通り allocb */

🟠 [PERF-3] tun_msg_len()msgdsize() を使わない独自実装

ファイル: tun.c
箇所: tun_msg_len()

static int tun_msg_len(mblk_t *mp)
{
    int len = 0;
    do {
        len += MBLKL(mp);
    } while (( mp = mp->b_cont) != NULL);
    return(len);
}

Solaris STREAMS には msgdsize(mp) という標準関数があり、
同等の処理をカーネル内で最適化済みの実装で行う。
独自実装は不要であり、標準関数に置き換えたほうが保守性・パフォーマンスともに優れる。

修正案:

// tun_msg_len() の呼び出し箇所を msgdsize() に置換
// type = tun_msg_len(nmp);
type = (int)msgdsize(nmp);

tun_msg_len() 関数自体を削除可能。


🟠 [PERF-4] mi_maxpsz = 2048 — Jumbo Frame 非対応によるスループット制限

ファイル: tun.c

static struct module_info tunminfo = {
  125,
  "tap" / "tun",
  21,      /* mi_minpsz */
  2048,    /* mi_maxpsz ← Ethernet 標準 MTU は 1500、Jumbo Frame は最大 9000 */
  (32 * 1024),  /* mi_hiwat */
  21       /* mi_lowat */
};

mi_maxpsz = 2048 は Jumbo Frame(最大 9000 バイト程度)を想定していない。
VPN トンネルで Jumbo Frame を使う環境(データセンター内部など)では、
フラグメント処理が強制され、スループットが大幅に低下する。

改善案:

#define TUN_MAX_PACKET  (9 * 1024)  /* Jumbo Frame 対応 */

static struct module_info tunminfo = {
  ...
  TUN_MAX_PACKET,  /* mi_maxpsz */
  (64 * 1024),     /* mi_hiwat  (スループット優先なら拡大) */
  21
};

🟠 [PERF-5] モジュールキュー mi_hiwat = 32KBTUN_SO_HIWAT = 1MB の不整合

ファイル: tun.c

/* モジュール情報 */
(32 * 1024),       /* mi_hiwat  = 32KB(モジュールキューの上限) */

/* TUNNEWPPA ioctl でストリームヘッドに設定 */
#define TUN_SO_HIWAT    1024*1024   /* ストリームヘッド = 1MB */
sop->so_hiwat = TUN_SO_HIWAT;

ストリームヘッド(ユーザー空間側)のバッファは 1MB に設定されているが、
ドライバモジュール内キューの上限は 32KB のまま。
ユーザー空間が大量データを書き込む際に、モジュールキューが早期に詰まり
フロー制御(canputnext() = false)が頻繁に発動する。

改善案: mi_hiwatTUN_SO_HIWAT に合わせるか、
または ndd(1M) 等でチューニング可能なパラメータとして公開する:

(256 * 1024),  /* mi_hiwat  (32KB → 256KB に拡大) */

🟡 [PERF-6] ppa->p_str リストの線形スキャン

ファイル: tun.c
箇所: tun_frame()

for( tmp=ppa->p_str; tmp; tmp = tmp->p_next ){
    /* ... SAP フィルタリング、MAC アドレス比較 ... */
}

各フレームのルーティング時に ppa->p_str リスト全体を線形スキャンする。
通常の OpenVPN 使用では1〜2ストリームのみで問題ないが、
多数のクライアントが同時接続するサーバーサイド用途では
SAP をキーとしたハッシュテーブルにするとスケールする。

改善案(多ストリーム用途の場合):

/* SAP → tunstr のハッシュテーブル(2^n サイズ) */
#define TUN_SAP_HASH_SIZE  16
struct tunstr *sap_hash[TUN_SAP_HASH_SIZE];
#define TUN_SAP_HASH(sap)  ((sap) & (TUN_SAP_HASH_SIZE - 1))

🟡 [PERF-7] qwriter(PERIM_OUTER) のエスカレーションコスト

ファイル: tun.c

/* tunwput() 内 */
case M_IOCTL:
    qwriter(wq, mp, tun_ioctl, PERIM_OUTER);   // 外側ペリメーター取得
    break;

qwriter(PERIM_OUTER) はキュー全体の外側ペリメーターを排他ロックで取得するため、
他スレッドが内側ペリメーターを保持している間は待機が発生する。
ioctl は低頻度なので通常は問題ないが、
tun_attach_req / tun_detach_reqtun_frame が頻繁に交錯する場合は
ロック競合のボトルネックになりうる。

データパス(M_DATA)は qwriter を経由しない直接処理になっているのは適切。
ioctl・attach・detach のコントロールパスは現状維持で問題ない。


🔵 [PERF-8] TUN_SO_HIWAT がコンパイル時定数で実行時チューニング不可

ファイル: if_tun.h, tun.c

#define TUN_SO_HIWAT    1024*1024   /* 1MB 固定 */

高スループット環境(10GbE 以上)では 1MB では不足することがある。
Solaris の ndd(1M) コマンドや ddi_prop_* API を使って
チューニングパラメータとして公開すると運用時に調整可能になる。

改善案:

/* ドライバ変数として公開 */
static uint_t tun_so_hiwat = 1024 * 1024;   /* デフォルト 1MB、ndd でチューニング可 */

パフォーマンス改善 影響度サマリー

No. 内容 期待効果 実装コスト
PERF-1 KCFLAGS に -O2 追加 (数十% のスループット向上)
PERF-2 tun_eth_hdr() の allocb 削減 中(高負荷時の CPU 削減)
PERF-3 tun_msg_len()msgdsize() 低(コード整理効果が主)
PERF-4 mi_maxpsz 拡大(Jumbo Frame) (Jumbo Frame 環境限定)
PERF-5 mi_hiwat 拡大(32KB → 256KB) 中(フロー制御発動頻度低減)
PERF-6 ppa->p_str ハッシュ化 低(多ストリーム限定)
PERF-7 qwriter エスカレーション最適化 低(コントロールパスは低頻度)
PERF-8 TUN_SO_HIWAT を ndd チューニング可に 低(運用柔軟性向上)

最優先: PERF-1(-O2 追加)は1行の変更で最大の効果が見込める。
次点: PERF-4(mi_maxpsz 拡大)と PERF-5(mi_hiwat 拡大)も変更量が小さく効果が大きい。


まとめ・優先度別アクション一覧(2026-03-12 時点)

現行ソース(tun.c / if_tun.h / configure.in / tun-test/*)と
本書末尾の対応記録を照合した結果を反映。

No. 優先度 状態 内容 ファイル
CRITICAL-1 CRITICAL 完了 オフバイワン境界外アクセス修正 tun.c
CRITICAL-2 CRITICAL 完了 mp->b_cont の NULL/長さ検証を追加 tun.c
CRITICAL-3 CRITICAL 完了 SSE/MMX 禁止フラグを導入(-mno-*, -xregs=no%float configure.in
HIGH-1 HIGH 要再評価 DL_UNITDATA_REQ は direct path。共有状態同期は per-PPA ロック設計で継続課題 tun.c
HIGH-2 HIGH 完了 TAP の PPA ごとに MAC を一意化 tun.c
HIGH-3 HIGH 完了 get_devname() のリーク修正 tun-test/tunctl.c
HIGH-4 HIGH 完了 main()devname/node 解放対応 tun-test/tunctl.c
HIGH-5 HIGH 完了 nobuild タイポ修正 tun-test/test.sh
HIGH-6 HIGH 完了 TESTLOGFILE 初期化順序修正 tun-test/test.sh
HIGH-7 HIGH 完了 tunerr() の越境書き込み修正 tun.c
HIGH-8 HIGH 完了 M_PROTO/M_PCPROTO の最小長チェックを追加 tun.c
MEDIUM-1 MEDIUM 完了 u_long/u_shortuint32_t/t_uscalar_t/minor_t/uint16_t へ更新 if_tun.h
MEDIUM-2 MEDIUM 完了 tunclose() の NULL 保護追加 tun.c
MEDIUM-3 MEDIUM 完了 tun_physaddr_req()static tun.c
MEDIUM-4 MEDIUM 保留 tunopen() の権限チェック(運用影響ありのため保留) tun.c
MEDIUM-5 MEDIUM 完了 IPS パッケージマニフェストを追加 pkg/tuntap.p5m
MEDIUM-6 MEDIUM 完了 ARP muxid のゼロチェック追加 tun-test/tunctl.c
MEDIUM-7 MEDIUM 完了 AC_PREFIX_DEFAULT([/]) を設定 configure.in
MEDIUM-8 MEDIUM 保留 GLDv3 / dladm 非対応(設計上の制限)
MEDIUM-9 MEDIUM 完了 IPS 向け driver アクションを manifest に定義 pkg/tuntap.p5m
LOW-1 LOW 完了 DBG を C99 可変引数マクロへ更新 if_tun.h
LOW-2 LOW 完了 GCC 分岐判定に GCC_MINOR_VERSION を使用 configure.in
LOW-3 LOW 完了 TUN でも DL_ENABMULTI_REQ/DL_DISABMULTI_REQDL_OK_ACK 応答(実体制御は未実装) tun.c
LOW-4 LOW 完了 clean ターゲット重複行を削除 Makefile.in
LOW-5 LOW 完了 tunctl/dev/ip6 への best-effort plumb/unplumb を追加 tun-test/tunctl.c
LOW-6 LOW 完了 make uninstall へ修正 tun-test/test.sh
LOW-7 LOW 完了 loadcheck() 実装見直しで引用符問題を解消 tun-test/test.sh
LOW-8 LOW 保留 configure.inconfigure.ac 移行 configure.in
PERF-9 MEDIUM 完了 tun_frame() fan-out を 1-pass 化し dupmsg を削減 tun.c
MEDIUM-10 MEDIUM 完了 tun_bind_req()static 作業領域をスタック化 tun.c

7. 今後の対応

2026-03-12 時点で、CRITICAL 項目(1-3)と追加 HIGH 項目(7-8)は完了済み。
次の優先順位で未対応項目を処理する。

  1. multicast 制御実体化(DL_ENABMULTI_REQ / DL_DISABMULTI_REQ
  2. per-PPA ロック導入(HIGH-1 再評価項目)
  3. kstat 可観測性の追加と性能回帰実測(PPS/多stream)
  4. MEDIUM-4: tunopen() 権限チェック(運用影響確認後)
  5. 設計課題: MEDIUM-8(GLDv3 非対応), LOW-8configure.ac 移行)

更新時は「問題提起」と「対応済みステータス」を分離し、
「まとめ・優先度別アクション一覧」を先に更新してから個別セクションを追記する。

コードについて

このツリーに収められているのは Solaris 用の STREAMS ベースの TUN/TAP 擬似デバイスドライバです。
if_tun.htun.c が本体で、tun-test 以下にはユーザランドのテストプログラムが入っています。


全体構造

  • tun.c はモジュール/ドライバとしてロードされる STREAMS ドライバで、TUN か TAP のどちらか一方でコンパイルされる (TUNTAP_TAP もしくは TUNTAP_TUN が必須)。
  • tun_str リストと tun_ppa[] 配列で開かれたストリームと仮想インタフェース (PPA: per‑path adapter) を管理する。
  • tunppa は各仮想インタフェースの制御ストリームと、TAP では MAC アドレスを保持。
  • tunstr はストリーム単位の状態を保持。フラグや DLPI 状態、SAP、割り当てられた PPA へのポインタなどを持つ。

if_tun.h には上記構造体と I/OCTL 定義、フラグのマクロが書かれています。
PROMISC/ALL などのスニッファ条件を判定する SNIFFER() マクロもここにあります。


ドライバのエントリポイント

  • _init, _fini, _infomod_install/mod_remove/mod_info を呼び、ロード/アンロードを担当。
  • tunprobe/tunattach/tundetach/tuninfodev_ops に登録され、デバイスノードの作成や情報取得を行う。
    • tunattach では ddi_create_minor_node/dev/tun あるいは /dev/tap のクローンデバイスを作成し、TAP ならローカル MAC を生成。

STREAMS キューとコールバック

  • tunrinit/tunwinit によって、リードパスは tunopen/tunclose、ライトパスは tunwput/tunwsrv が呼ばれる。
  • tunopen は CLONEOPEN に対応し、新しい minor 番号の割り当てと tunstr 構造体の初期化、リストへの挿入を行う。
  • tunclose はストリームを PPA から切り離し、制御ストリームであれば PPA 自体を解放。最後に tunstr をリストから削除してメモリを返す。

IOCTL/DLPI ハンドラ

  • tun_ioctl がライトキューに入る I/OCTL へ対応。PPA の作成 (TUNNEWPPA)、ストリームの PPA への紐付け (TUNSETPPA)、取得などを実装する。
  • IOCTL 以外にも DLPI プリミティブを処理する多数の関数があり、リクエストの検査と ACK/NAK の返信をする。
    • tun_info_reqtun_attach_reqtun_detach_reqtun_bind_req など BOOTP シリーズに対応。
    • プロミスコン(on/off) ではフラグを変更し、物理アドレス要求や単一データ要求なども実装。
    • TAP モードでは tun_set_physaddr_reqtun_enabmulti_req など付随処理もある。

これらの DLPI 関連関数は共通のヘルパとして
tunchmsg(メッセージ確保/初期化)、tundlokack/tundlerrack(ACK/エラー返信)、
tunerr(M_ERROR 送出)を利用しています。


データフレームの経路

  • tun_unitdata_req はプロトコルストリームから制御ストリームへ送るフレームを形成。TAP では引数からイーサネットヘッダを付加し、TUN ではヘッダをゼロクリアする。
  • tun_unitdata_ind は制御ストリームからプロトコルストリームへ送るインジケーションを組み立てる。TAP/TUN で宛先・送信元アドレスの扱いが異なる。
  • tun_eth_hdr がイーサネットヘッダの生成を行う(TAP では実アドレス、TUN ではタイプだけ)。

フレームの振り分けは tun_frame に集約される。ストリームの種類とフラグに応じて:

  1. PPA に未接続なら捨てる。
  2. PPA に接続済みストリーム群を走査し、スニッファ条件 (TUN_ALL_PHY/SAP/MULTI) に合致すれば複製して配信。生データ (TUN_RAW) 指定なら DLPI ヘッダを付けずに送る。
  3. 制御ストリームから来たデータは、各プロトコルストリームへフィルタリングして転送。TAP モードでは MAC レベルで eligible 判定 (tun_frame_is_eligible)、TUN では SAP 値チェック。
  4. プロトコル側から来たデータは制御ストリームへ送り、キューがいっぱいならキューィング/ドロップ。

TAP固有ロジック

  • tun_generate_mac_addr でランダムにローカル MAC を生成し、各 PPA にコピー。
  • tun_frame_is_eligible で受信ストリームが受け取るべきフレームかどうかを確認(ブロードキャスト、マルチキャスト、宛先アドレス比較、SAP 比較など)。
  • tun_msg_len が 802.3 長さフィールドを計算。

そのほかユーティリティ

  • tun_alloc_ppa は PPA を新規確保または指定番号で割り当て、配列に登録。
  • tunpromiscon_reqtunpromiscoff_req がプロミスコン状態ビットを管理。
  • tun_physaddr_req は PPA の MAC を返す。

動作の流れ

  1. /dev/tun あるいは /dev/tap に open すると tunopen が呼ばれ、tunstr が作られてストリームが動作開始。
  2. TUNNEWPPA IOCTL で仮想デバイスを作成し、返されたインスタンス番号を TUNSETPPA でプロトコルストリームにセット。
  3. DLPI の ATTACHBINDUNITDATA_REQ が順次送受信され、ソケット風にパケットをやり取りできる。
  4. 制御ストリーム側 (TUN_CONTROL フラグ) には TAP/TUN が生成したパケットが流れ、プロトコルストリームへフェッチされる。

テストコード

tun-test/ 以下には簡単な CLI 付きテスト (test.shtunctl.c) があり、ユーザーランドから PPA の生成やパケット送受信を試せるようになっています。


このドライバは UNIX STREAMS と DLPI プロトコルに密接に結びついており、TUN と TAP の振る舞いをビルド時に切り替えることで、パケットをユーザー空間へ中継する仕組みをシンプルに実装しています。各関数は状態遷移とエラーチェックを細かく行い、メッセージの再利用や複製によって効率的にフレームを振り分けます。必要であれば各ブロックをさらに掘り下げて説明できますので、気になる箇所があればお知らせください。


付録: レビュー対応記録(2026-03-12)

概要

リポジトリレビューで抽出した不具合・リスクに対して、tun.c / tun-test/tunctl.c / tun-test/test.sh を修正し、回帰確認用の軽量テストを追加した。

対応内容

  1. TUNMAXPPA の境界チェック修正(off-by-one)
  • p > TUNMAXPPAp >= TUNMAXPPA に変更し、配列外アクセスを防止。
  • 対象: tun_ioctl(TUNNEWPPA/TUNSETPPA), tun_attach_req
  1. M_IOCTL 入力検証の追加
  • mp->b_contNULL チェック、sizeof(int) 未満の長さチェックを追加。
  • 不正な ioctl ペイロードでの不正参照を防止。
  1. 二重アタッチ防止
  • 既に str->ppa != NULL の場合、TUNNEWPPA / TUNSETPPA を拒否。
  • DL_ATTACH_REQ は「既存 PPA と同一の場合は許可・異なる場合は拒否」に調整し、IF_UNITSEL 経路との互換性を維持。
  • tun_detach_req / tunclose では unlink 時の NULL 保護を追加。
  1. メッセージ長・オフセット検証強化
  • DL_UNITDATA_REQ の最小長チェックを追加。
  • dl_dest_addr_offset / dl_dest_addr_length の妥当性を検証し、DL_BADADDR を返すよう修正。
  • フレーム先頭参照前に msgdsizepullupmsg で長さを保証。
    • TUN: 1byte 以上
    • TAP: sizeof(struct ether_header) 以上
  1. DL_SET_PHYS_ADDR_REQ の検証追加
  • DL_SET_PHYS_ADDR_REQ_SIZE の最小長チェック追加。
  • ppa == NULL の状態チェック追加。
  • dl_addr_offset / dl_addr_length の妥当性検証追加。
  • コピー元を固定オフセットではなく dl_addr_offset 由来に修正。
  1. tunctl のメモリリーク・安全性修正
  • get_devname() の不要ループを廃止し、1回だけ安全に確保する実装へ変更。
  • main()devname / node のエラー処理と解放処理を追加。
  • lifr_name への代入を strncpy から snprintf に変更。
  1. tun-test/test.sh の修正
  • nobuld typo を nobuild に修正。
  • unplumball() の引数を tun0/tap0 相当に正しく扱えるよう修正(tun / tap を渡す)。
  • -z 判定の未クォートを修正。
  1. Issue Broadcom BCM57762/BCM57766 をサポートした Solaris bge driver #27satokaz/hitorigoto)の内容確認と反映
  • Issue の主旨: カーネルドライバで浮動小数点/SSEレジスタを使うと問題化するため、コンパイルフラグで禁止する必要がある。
  • configure.inamd64 向け KCFLAGS を強化。
    • GCC: -mno-mmx -mno-sse -mno-sse2 -mno-80387 -msoft-float を追加
    • Studio: -xregs=no%float を追加
  • 上記により、Issue Broadcom BCM57762/BCM57766 をサポートした Solaris bge driver #27 で示された方針をビルド設定へ反映した。
  1. 追加対応(再レビュー結果の反映)
  • tun.c:
    • TUNNEWPPAallocb() 失敗時に、確保済み PPA をロールバックする処理を追加。
    • 一時的な履歴コメントを削除して可読性を改善。
    • fast path の重複 msgdsize() 事前走査を削減し、pullupmsg() ベースの最小検証へ整理。
  • tun-test/test.sh:
    • 失敗時に終了コードへ反映するよう整理。
    • sudo -n を利用した非対話実行対応。
    • trap による常時クリーンアップとロックディレクトリで同時実行競合を抑制。
    • 固定 tun0/tap0 は維持しつつ、TEST_PPA_INDEX 環境変数で変更可能化。
    • ログファイル名を PID 付きにして衝突確率を低減。
  • tun-test/regression-test.sh:
    • bash -nsh -n に変更。
    • 二重アタッチガードの件数確認と、PPAロールバック確認を追加。
  • tun-test/tunctl.c:
    • /dev/<name> のバッファを動的長で確保し、snprintf 戻り値を検証。
  • Makefile.in / tun-test/Makefile.in:
    • .PHONY 追加、test ターゲットを $(MAKE) -C ... へ変更。
    • KCFLAGS-Wno-incompatible-pointer-types を追加(GCC 15 環境での型互換エラー回避)。
  • configure.in:
    • no-float/no-SIMD フラグを変数化し、重複定義を削減。
  1. 追加対応(HIGH-1/HIGH-2 の反映と再調整)
  • tun.c:
    • TAP の PPA 割当時に tun_alloc_ppa_mac() を通して PPA ID ベースで MAC 末尾バイトを変化させ、複数 tapN の MAC 重複を回避。
    • その後の性能改善で DL_UNITDATA_REQqwriter 経由を廃止し、direct path(tun_unitdata_req(wq, mp))へ再調整(9cac718)。
  • tun-test/regression-test.sh:
    • DL_UNITDATA_REQqwriter 非依存(require_no_match)を検証。
    • direct path 呼び出しの存在(require_match)を検証。
    • tun_alloc_ppa_mac() と PPA ID ベース MAC 一意化ロジックの存在チェックを追加。
  1. 追加対応(MEDIUM/LOW 残件の反映)
  • if_tun.h:
    • DBG( a... ) を C99 互換の DBG(...) へ変更。
    • tunstr の型を uint32_t / t_uscalar_t / minor_t へ更新。
    • tundladdr.sapuint16_t へ更新。
  • tun.c:
    • tun_physaddr_req()static 化。
    • TUN/TAP 共通で DL_ENABMULTI_REQ / DL_DISABMULTI_REQDL_OK_ACK を返すよう統一。
  • configure.in:
    • AC_PREFIX_DEFAULT([/]) を設定。
    • amd64/GCC 分岐で MINOR_VERSION ではなく GCC_MINOR_VERSION を参照するよう修正。
  • Makefile.in:
    • clean ターゲットの重複削除行(rm -f tun tun ...)を削除。
  • tun-test/test.sh:
    • make uinstallmake uninstall へ修正。
  • tun-test/tunctl.c:
    • ARP unlink 時に lifr_arp_muxid != 0 のガードを追加。
    • /dev/ip6 を使った IPv6 への best-effort plumb/unplumb を追加。
  • pkg/tuntap.p5m(新規):
    • IPS 用マニフェストを追加し、driver name=tun/tap と関連ファイルの配備ルールを定義。
  • tun-test/regression-test.sh:
    • 上記修正(型、multicast ack、configure 判定、clean、uninstall、IPv6/ARP、IPS manifest)の静的回帰チェックを追加。
  1. 実機検証で見つかった不整合の修正(Solaris 11.4)
  • tun.c:
    • tun_attach_req()str->ppa ガードを見直し、同一PPAへの DL_ATTACH_REQ を許可。
    • これにより IF_UNITSELENXIO を解消し、tunctl -t tun0/tap0 が成功することを確認。
  • tun-test/test.sh:
    • cleanup 中の再 unplumb 失敗で failed=1 が残り、成功時でも exit 1 になる問題を修正。
    • main() の成功パスで return 0 を明示。
  • tun-test/regression-test.sh:
    • DL_ATTACH_REQ 互換ガード(str->ppa != NULL && str->ppa != ppa)の存在チェックを追加。
  1. E2E テストスクリプトの追加
  • tun-test/e2e-openvpn.sh:
    • 2ホスト構成で OpenVPN static-key を使った E2E 疎通テストを実行するスクリプトを追加。
    • --peer, --local-transport-ip, --remote-transport-ip を必須引数として、ローカル/リモートの tunN を作成・起動・疎通・クリーンアップまで自動化。
  • tun-test/Makefile.in / Makefile.in:
    • test-e2e-openvpn ターゲットを追加し、E2E_ARGS 経由で引数を渡せるようにした。
  • tun-test/regression-test.sh:
    • e2e-openvpn.sh の存在・構文の静的チェックを追加。
  • README.md:
    • E2E 実行方法(必須依存とコマンド例)を追記。
  1. 依存なしミニ relay E2E 経路の追加
  • tun-test/tunrelay.c(新規):
    • TUNSETPPADLIOCRAW で既存 tunN/tapN へ接続し、poll() で TUN/TAP と UDP を双方向転送する最小 relay を追加。
    • OpenVPN 等に依存せず、単体バイナリで E2E 経路検証が可能。
  • tun-test/e2e-relay.sh(新規):
    • 2ホストの tunN 作成、ifconfig 設定、relay 起動、ping 疎通、後始末までを自動化。
    • --peer, --local-transport-ip, --remote-transport-ip を必須引数として実行する。
  • tun-test/Makefile.in / Makefile.in:
    • tunrelay ビルドターゲットを追加。
    • test-e2e-relay ターゲットを追加し、E2E_RELAY_ARGS(未指定時は E2E_ARGS)を relay スクリプトへ引き渡す。
    • test-e2e のデフォルトを dependency-free relay 経路へ変更。
  • tun-test/regression-test.sh:
    • tunrelay.cTUNSETPPA / DLIOCRAW / poll() の存在チェックを追加。
    • test-e2e の relay 既定化と引数フォールバック導線の静的チェックを追加。
  1. 実機 panic(2026-03-12 13:35:49 JST)の原因解析と対処
  • 事象:
    • Host A でドライバ動作中に panic。panicstrBAD TRAP: type=7 (#nm Device not available)
    • fmdump -Vp -u 6f01e6d3-ebee-46fe-8bf0-ac6cf2d74e8b の stack は tun:tunchmsg.constprop.0 -> tun:tunwsrv
  • 原因:
    • configure.in には no-FPU/no-SSE フラグ修正を入れていたが、生成済み configure が古いままで未同期。
    • その結果、実機で ./configure 後の Makefile KCFLAGS-mno-mmx -mno-sse -mno-sse2 -msoft-float が入らず、カーネルモジュールに不適切な命令列が混入した可能性が高い。
  • 対処:
    • configure 本体へ no-FPU/no-SSE フラグ反映(NOFP_KCFLAGS_GCC / NOFP_KCFLAGS_STUDIO)。
    • amd64/GCC 分岐の条件を MINOR_VERSION ではなく GCC_MINOR_VERSION へ修正。
    • tun-test/regression-test.shconfigure 本体の静的検証(no-FPU flags と条件式)を追加し、再発防止。
  • 修正後確認:
    • 実機で再 ./configure 後、KCFLAGS-mno-mmx -mno-sse -mno-sse2 -mno-80387 -msoft-float が含まれることを確認。
    • 実機で gmake clean && gmake && sudo gmake install 後、tunctl -t tun0 -b / tunctl -d tun0 -b の smoke 実行時に新規 panic は発生せず。
  1. 実機 panic(2026-03-12 14:04:40 JST, 168.138.214.190)の追加解析
  • 事象:
    • fmdump -Vp の UUID 6ce6b4dd-98e6-4cbd-8656-db6a95f7bc2eBAD TRAP: type=7 (#nm Device not available) を確認。
    • stack は前回と同じく tun:tunchmsg.constprop.0 -> tun:tunwsrv
  • 直接原因:
    • panic時に使われた tun バイナリを dis すると、tunchmsg.constprop.0 ほかに xmm 命令(movdqa, movups, punpcklqdq)が存在。
    • カーネル内で SSE 命令実行により #nm が発生したと判断。
  • なぜ混入したか:
    • 実機ビルドログが Nothing to be done for 'all' で、古い生成物(tun/tap)を再コンパイルせずに install していた。
    • その結果、no-FPU/no-SSE フラグ未適用の stale バイナリが導入された。
  • 併発要因:
    • e2e-relay.sh / e2e-openvpn.sh の既定 PPA_INDEX92/90 で、TUNMAXPPA=20 の範囲外。
    • tunctl -t tun92 失敗経路で tunchmsg が通り、panic を引き起こしやすい条件になっていた。
  • 対処:
    • e2e-relay.sh / e2e-openvpn.sh の既定 PPA_INDEX9 に変更。
    • MAX_PPA_INDEX=19 の上限ガードを追加し、範囲外値は即エラー終了。
    • tun-test/regression-test.sh に既定値・上限チェックの静的検証を追加。

追加テスト

tun-test/regression-test.sh を追加し、今回の修正ポイントに対する静的回帰チェックを実装。

チェック内容:

  • 境界チェック修正の残存確認
  • ioctl ペイロード検証コードの存在確認
  • 二重アタッチ防止コードの存在確認
  • DL_BADADDR / pullupmsg ガードの存在確認
  • test.sh typo 修正確認
  • tunctl.c のリーク原因ループ除去確認
  • sh -n tun-test/test.sh 実行
  • configure.in のカーネル向けレジスタ制約フラグ確認(GCC/Studio)
  • test.sh の失敗伝播・非対話 sudo・構文検証
  • TUNNEWPPA のロールバック有無(静的確認)
  • ./configure 実行(Makefile 再生成)
  • make 実行(再試行後に成功)
  • make -C /export/home/kazus/Gits/tuntap tun tap 実行(成功)
  • make -C /export/home/kazus/Gits/tuntap/tun-test tunrelay 実行(成功)
  • 実機(Solaris 11.4)で gmake -C tun-test tunrelay 実行(成功)
  • 実機(Solaris 11.4)で gmake -C tun-test test-regression 実行(成功)
  • 実機(Solaris 11.4)で fmdump -Vp -u <panic UUID> により panic stack を確認
  • 実機で修正済み configure による KCFLAGS 再検証(no-FPU/no-SSE フラグ入り)を実施

変更ファイル

  • /export/home/kazus/Gits/tuntap/tun.c
  • /export/home/kazus/Gits/tuntap/tun-test/tunctl.c
  • /export/home/kazus/Gits/tuntap/tun-test/test.sh
  • /export/home/kazus/Gits/tuntap/tun-test/regression-test.sh(新規)
  • /export/home/kazus/Gits/tuntap/tun-test/e2e-openvpn.sh(新規)
  • /export/home/kazus/Gits/tuntap/tun-test/e2e-relay.sh(新規)
  • /export/home/kazus/Gits/tuntap/tun-test/tunrelay.c(新規)
  • /export/home/kazus/Gits/tuntap/tun-test/Makefile.in
  • /export/home/kazus/Gits/tuntap/Makefile.in
  • /export/home/kazus/Gits/tuntap/configure.in
  • /export/home/kazus/Gits/tuntap/if_tun.h
  • /export/home/kazus/Gits/tuntap/pkg/tuntap.p5m(新規)
  • /export/home/kazus/Gits/tuntap/README.md

実行結果

  • tun-test/regression-test.sh : regression checks passed
  • sh -n tun-test/test.sh : test.sh syntax OK
  • sh -n tun-test/regression-test.sh : regression-test syntax OK
  • ./configure : Makefile, tun-test/Makefile を再生成
  • make(1回目): incompatible-pointer-types で失敗
  • Makefile.in 調整後 ./configure 再実行
  • make(2回目): tun / tap のビルド成功
  • 実機(Host A, Solaris 11.4)で gmake -C tun-test test-regression 成功
  • 実機で sudo gmake installsudo sh tun-test/test.sh nobuildsudo gmake uninstall 成功
  • 実機で /dev/ip6 への I_PLINKEINVAL のため warning を出すが、IPv4 経路テストは成功
  • ローカルで tun-test/e2e-openvpn.shsh -ntest-regression を通過(2ホスト E2E は別途実行)
  • ローカルで make -C tun-test tunrelay 成功
  • ローカルで tun-test/regression-test.sh 成功(relay 静的チェック含む)
  • 実機(Host A, Solaris 11.4)で ./configuregmake -C tun-test tunrelaygmake -C tun-test test-regression 成功
  • 実機で panic 発生(2026-03-12 13:35:49 JST, UUID: 6f01e6d3-ebee-46fe-8bf0-ac6cf2d74e8b)を確認し、configure 未同期起因の no-FPU フラグ欠落を修正
  • 修正後の実機で ./configuregmakesudo gmake installtunctl -t tun0 -b / tunctl -d tun0 -b を実施し、新規 panic なし
  • 実機 168.138.214.190 で panic 発生(2026-03-12 14:04:40 JST, UUID: 6ce6b4dd-98e6-4cbd-8656-db6a95f7bc2e)を確認
  • panic時バイナリ tun に SSE 命令(xmm)が含まれることを dis で確認
  • stale 生成物の再利用(Nothing to be done for 'all')と E2E 既定 PPA 範囲外(90/92)を修正
  • --ppa-index 9 で 2ホスト relay E2E を再実行(168.138.214.190132.145.125.154)し、両ホストで tun9 作成・relay 起動までは成功
  • ただし underlay 到達性不足により ping 10.252.0.2 は不達(relay E2E ping failed
  • 追加確認として A↔B の UDP 単発テスト(public/private 各IP)を実施し、いずれも受信タイムアウト

未実施

  • multicast 制御実体化の実装と E2E 検証
  • kstat 導入後の性能回帰測定(PPS/多stream)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions