**QtNat – Qt UPnPでポートを開く**

2026/01/10 5:17

**QtNat – Qt UPnPでポートを開く**

RSS: https://news.ycombinator.com/rss

要約

日本語訳:

要約

QtNatはQt 6向けに作られた軽量C++ライブラリで、UPnPを通じてNATポートマッピングを自動化します。シンプルなAPI(

UpnpNat
)を公開し、内部で探索・説明取得・ポートマッピング要求を実行時に管理します。

探索
ライブラリはM‑SEARCHリクエストをマルチキャストアドレス 239.255.255.250:1900 に送信し、ルーターからの応答を受け取り、デバイスの説明URLを抽出して

m_descriptionUrl
に保存します。

説明解析

QNetworkAccessManager
を使用してQtNatはXML説明ファイルをダウンロードし、
urn:schemas-upnp-org:device:InternetGatewayDevice
WANDevice
、または
WANConnectionDevice
などのデバイスタイプを検索します。これから関連サービス(
WANIPConnection
または
WANPPPConnection
)の制御URLを抽出し
m_controlUrl
に格納します。

ポートマッピング
SOAP

<AddPortMapping>
エンベロープが inja で生成され、外部/内部ポート、プロトコル、内部クライアントIP、説明、およびリース期間を含みます。リクエストは以下のヘッダーを設定します:

Content-Type: text/xml; charset="utf‑8"
SOAPAction: "<service type>#AddPortMapping"

成功したレスポンスが返るとステータスは

NAT_ADD
に変更され、探索・説明取得・マッピング中に失敗すると
NAT_ERROR
へ遷移し、説明メッセージが付与されます。

状態フロー
ライブラリの状態機械は次をカバーします:

  • NAT_IDLE
  • NAT_DISCOVERY
  • NAT_GETDESCRIPTION
  • NAT_DESCRIPTION_FOUND
  • NAT_FOUND
  • NAT_READY
  • NAT_ADD
  • NAT_ERROR

使用例

auto nat = UpnpNat::create();
QObject::connect(nat.get(), &UpnpNat::statusChanged, [nat](int status) {
    switch (static_cast<UpnpNat::Status>(status)) {
        case UpnpNat::Status::NAT_FOUND:
            nat->requestDescription(); break;
        case UpnpNat::Status::NAT_READY:
            nat->addPortMapping(6664, 6664, "TCP", "QtNat demo"); break;
        case UpnpNat::Status::NAT_ADD:
            QCoreApplication::quit(); break;
        case UpnpNat::Status::NAT_ERROR:
            qFatal("Error: %s", nat->errorMessage().toUtf8().constData());
    }
});
nat->discover();

対象読者とメリット

QtNatはピア‑ツー‑ピアアプリ、マルチプレイヤーゲーム、リモートアクセスツール、および手動でルーター設定を行わずに受信NAT接続が必要なソフトウェア向けです。探索とポートマッピングを自動化することでセットアップの摩擦を減らし、デプロイ速度を向上させます。

ソースコードはGitHubでホストされており、貢献者は様々なルーターでライブラリをテストし改善点を提案することが奨励されています。

本文

QtNat – UPnP による NAT ポートマッピングを実現する軽量 C++ ライブラリ(Qt 6)


概要

  • UPnP 対応ルーターを自動検出
  • ランタイムでポート転送ルールを作成
    (必要に応じて既存のマッピングも変更できます)
  • P2P アプリ、マルチプレイヤーゲーム、リモートアクセスツールなどに最適

クイックスタート

UpnpNat nat;

QObject::connect(&nat, &UpnpNat::statusChanged,
                 [&nat, &app](){
    switch (nat.status()){
        case UpnpNat::NAT_STAT::NAT_IDLE:
        case UpnpNat::NAT_STAT::NAT_DISCOVERY:
        case UpnpNat::NAT_STAT::NAT_GETDESCRIPTION:
        case UpnpNat::NAT_STAT::NAT_DESCRIPTION_FOUND:
            break;

        case UpnpNat::NAT_STAT::NAT_FOUND:
            nat.requestDescription();
            break;

        case UpnpNat::NAT_STAT::NAT_READY:
            nat.addPortMapping("UpnpTest",
                               nat.localIp(),
                               6664, 6664,
                               "TCP");
            break;

        case UpnpNat::NAT_STAT::NAT_ADD:
            qDebug() << "It worked!";
            app.quit();
            break;

        case UpnpNat::NAT_STAT::NAT_ERROR:
            qDebug() << "Error:" << nat.error();
            app.exit(1);
            break;
    }
});

nat.discovery();   // 処理全体を開始
ステータス発生すること
NAT_FOUND
ルーターが検出 → 説明ファイル取得リクエスト
NAT_READY
説明ファイル解析完了 → ポートマッピング追加
NAT_ADD
マッピング成功 → 終了
NAT_ERROR
エラー発生 → メッセージ表示と終了

技術的な概要

1. 検出

SSDP M-SEARCH をマルチキャストアドレスに送信します。

#define HTTPMU_HOST_ADDRESS "239.255.255.250"
#define HTTPMU_HOST_PORT    1900
#define SEARCH_REQUEST_STRING \
" M-SEARCH * HTTP/1.1\n"\
"ST:UPnP:rootdevice\n"\
"MX: 3\n"\
"Man:\"ssdp:discover\"\n"\
"HOST: 239.255.255.250:1900\n\n"

void UpnpNat::discovery()
{
    setStatus(NAT_STAT::NAT_DISCOVERY);
    m_udpSocketV4.reset(new QUdpSocket(this));

    QHostAddress broadcastIpV4(HTTPMU_HOST_ADDRESS);
    m_udpSocketV4->bind(QHostAddress::AnyIPv4, 0);

    QByteArray datagram(SEARCH_REQUEST_STRING);
    connect(m_udpSocketV4.get(), &QUdpSocket::readyRead,
            this, [this](){
        QByteArray data;
        while (m_udpSocketV4->hasPendingDatagrams()){
            data.resize(m_udpSocketV4->pendingDatagramSize());
            m_udpSocketV4->readDatagram(data.data(), data.size());
        }
        QString result(data);
        int start = result.indexOf("http://");
        if (start < 0){ setError(tr("Unable to read the beginning of server answer")); setStatus(NAT_STAT::NAT_ERROR); return; }

        int end = result.indexOf("\r", start);
        if (end < 0){ setError(tr("Unable to read the end of server answer")); setStatus(NAT_STAT::NAT_ERROR); return; }

        m_describeUrl = result.mid(start, end - start);
        setStatus(NAT_STAT::NAT_FOUND);
        m_udpSocketV4->close();
    });

    connect(m_udpSocketV4.get(), &QUdpSocket::errorOccurred,
            this, [this](QAbstractSocket::SocketError){
                setError(m_udpSocketV4->errorString());
                setStatus(NAT_STAT::NAT_ERROR);
            });

    m_udpSocketV4->writeDatagram(datagram, broadcastIpV4, HTTPMU_HOST_PORT);
}

m_describeUrl
にデバイス説明ファイルの URL が格納されます。


2. 説明ファイルの取得

void UpnpNat::requestDescription()
{
    setStatus(NAT_STAT::NAT_GETDESCRIPTION);
    QNetworkRequest request(QUrl(m_describeUrl));
    m_manager.get(request);          // 内部で QNetworkAccessManager を使用
}

3. XML 説明ファイルの解析

デバイスは複数の UPnP サービスを公開している可能性があります。
以下のいずれかのサービスを探します。

デバイスタイプ対応するサービス
InternetGatewayDevice
WANIPConnection
,
WANPPPConnection
WANDevice
WANConnectionDevice
void UpnpNat::processXML(QNetworkReply* reply)
{
    QByteArray data = reply->readAll();
    if (data.isEmpty()){
        setError(tr("Description file is empty"));
        setStatus(NAT_STAT::NAT_ERROR);
        return;
    }

    // XML を解析し <serviceList> → <serviceType> を取得
    m_controlUrl = /* 選択したサービスのコントロール URL */;
    m_serviceType = /* 例: "urn:schemas-upnp-org:service:WANIPConnection" */;

    setStatus(NAT_STAT::NAT_READY);
}

4. ポートマッピングリクエストの送信

SOAP エンベロープは inja テンプレートで生成します。

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
            s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <s:Body>
    <u:AddPortMapping xmlns:u="{{ service }}">
      <NewRemoteHost></NewRemoteHost>
      <NewExternalPort>{{ port }}</NewExternalPort>
      <NewProtocol>{{ protocol }}</NewProtocol>
      <NewInternalPort>{{ port }}</NewInternalPort>
      <NewInternalClient>{{ ip }}</NewInternalClient>
      <NewEnabled>1</NewEnabled>
      <NewPortMappingDescription>{{ description }}</NewPortMappingDescription>
      <NewLeaseDuration>0</NewLeaseDuration>
    </u:AddPortMapping>
  </s:Body>
</s:Envelope>
void UpnpNat::addPortMapping(const QString& description,
                             const QString& destination_ip,
                             unsigned short port_ex,
                             unsigned short port_in,
                             const QString& protocol)
{
    inja::json subdata;
    subdata["description"] = description.toStdString();
    subdata["protocol"]    = protocol.toStdString();
    subdata["service"]     = m_serviceType.toStdString();
    subdata["port"]        = port_in;            // 内部・外部ポートは同じ
    subdata["ip"]          = destination_ip.toStdString();

    QByteArray body = QByteArray::fromStdString(
        inja::render(loadFile(key::envelop).toStdString(), subdata));

    QNetworkRequest request(QUrl(m_controlUrl));
    QHttpHeaders headers;
    headers.append(QHttpHeaders::WellKnownHeader::ContentType,
                   "text/xml; charset=\"utf-8\"");
    headers.append("SOAPAction",
                   QString("\"%1#AddPortMapping\"").arg(m_serviceType));
    request.setHeaders(headers);

    m_manager.post(request, body);
}

5. レスポンスの処理

void UpnpNat::processAnswer(QNetworkReply* reply)
{
    if (reply->error() != QNetworkReply::NoError){
        setError(tr("Something went wrong: %1").arg(reply->errorString()));
        setStatus(NAT_STAT::NAT_ERROR);
        return;
    }
    setStatus(NAT_STAT::NAT_ADD);   // 成功
}

使い方 / テスト方法

  1. Qt 6(C++17)でビルド
  2. UPnP 対応ルーターの後ろにあるマシンで実行
  3. コンソールに「It worked!」と表示され、正常終了するとプログラムが終了します。
  4. エラー発生時はメッセージを表示し、
    exit(1)
    で終了します。

ぜひお試しいただき、改善点や機能追加のアイデアを GitHub へ Pull Request してください。

同じ日のほかのニュース

一覧に戻る →

2026/01/10 1:49

**Flockが米国の監視インフラのパスワードを53回ハードコードした**

## 日本語訳: **要約:** Flock SafetyのデフォルトArcGIS APIキーが誤ってクライアント側JavaScriptバンドルに埋め込まれ、同社のArcGISマッピング環境と50個のプライベートアイテムへの無制限アクセスが漏洩しました。キーは複数のサブドメインにわたる53件の公開フロントエンドバンドルに出現し、リファラーやIP制限が設定されていませんでした。この問題は約12,000件のデプロイメントに影響を与えました:~5,000件の警察署、~6,000件のコミュニティデプロイメント、および~1,000件の民間企業。 リスク対象となるデータには、監視インフラ(カメラ在庫、ドローンテレメトリー)、法執行位置情報(パトロールカーGPS、ボディーカム位置)、人・車両検出アラート、ホットリスト検知、CADイベントレイヤー、Flock911インシデント文字起こし/音声、およびカメラ登録者の個人情報が含まれます。キーのメタデータは「portal:app:access:item」権限を50件持っており、多数または千単位の機関からのデータを集約している可能性があります。 2025年11月13日に別途報告された重大脆弱性により、未認証でFlock Safetyの本番環境(「Flock Safety Prod」)にスコープされた有効なArcGISトークンをミントできる問題が存在しました。この欠陥は2026年1月7日現在でもパッチ適用されておらず、デフォルトキーとトークンミントの脆弱性は、ほぼ100万件の利用可能クレジットを持つアクティブサブスクリプション下で機能していました。 この漏洩により、Flock Safetyが主張するCJIS、SOC 2 Type II、ISO 27001、およびNIST標準へのコンプライアンスは損なわれます—これらの主張は監査報告で裏付けられたことがありません。実際に悪用例として、ジョージア州(Steffman)、カンザス州(Nygaard)、フロリダ州(Brown)の警察長官がFlockカメラを利用して個人をストーカーした事例が報告されており、システムの乱用への脆弱性が示されています。上院議員ロン・ワイデンは公にFTCによるFlock Safetyのサイバーセキュリティ実務調査を要請しています。 外国情報機関は通信を傍受せずとも大規模に移動データを収集でき、国内では強制、脅迫、ハイプロファイル標的への影響操作といったリスクが存在します。トークンミント欠陥は2026年初頭まで未修正であり、警察署、コミュニティデプロイメント、民間企業を脅かし、法執行技術に対する公衆の信頼を侵食しています。

2026/01/10 4:33

**タイトル:** RTX 5090 vs. Raspberry Pi:ゲームに使えるのか? **概要:** - **RTX 5090** – 4K解像度で高フレームレート、レイトレーシングやDLSSを駆使したハイエンドデスクトップGPU。 - **Raspberry Pi** – 超低消費電力のSBC。VideoCore IVまたはVI GPUは極めて軽量なゲームやエミュレーションしか処理できない。 **結論:** RTX 5090は本格的なゲーミングに最適で、Raspberry Piは単純なインディータイトルやレトロクラシックを動かす程度の性能しか持たず、RTX 5090とのパフォーマンス差は埋められない。

## Japanese Translation: Raspberry Pi 5に外部NVIDIA RTX 5090 GPUを接続してゲームをプレイすることは技術的には可能ですが、ほとんどのユーザーにとって実用的ではありません。Pi 5のPCIe Gen 2 ×1インターフェース(約500 MB/s)は帯域幅を制限し、FEXエミュレーション下でのCPUは2008年型Core 2 Quadに匹敵するため、Cyberpunk 2077などの最新ゲームは720pでも<16 FPSしか出せずプレイできません。RTX 5090を接続してもフレームレートが16 FPSを超えることは稀です。一方、Beelink MINI‑S13のようなIntelベースのSBCは多くのタイトルで50+ FPSを達成し、Windows/WINEゲームにおいてARMボードよりも優れた性能を示します。Radxa ROCK 5BはPi 5よりコア数とPCIe帯域幅が増えていますが、FEXオーバーヘッドのため古いゲームで22–23 FPSに留まります。 ARMボードではNVIDIAドライバパッチ(例:@mariobalanca の作業)が必要で、DXVK を無効化(PROTON_USE_WINED3D=1)して特定タイトルを動かす必要があります。負荷時の電力消費はPi 5が9 W未満、一方Beelinkでは約30 Wです。これらの数値にはGPU消費は含まれていません。Portal 2 のような非常に古いゲームは、Pi 5とRTX 5090で4K解像度でも60 FPS以上を実現できます。 Valve の次世代 VR ヘッドセットや統合 GPU を備えた NVIDIA SoC などの将来の ARM プラットフォームが Linux ゲーミング性能を向上させる可能性はありますが、現在の構成で本格的にプレイすることは推奨されません。低電力ゲーマーは非常に古いタイトルなら Pi 5 を利用できるかもしれませんが、高フレームレートを求めるユーザーは Intel ベースの SBC を選ぶでしょう。メーカーは PCIe 帯域幅とドライバサポートの拡充に注力することで、ARM ゲーミングの実用性を広げられる可能性があります。

2026/01/10 4:15

Show HN:ロケット発射と軌道シミュレーター

## Japanese Translation: **改訂概要** テレメトリログは、打ち上げが開始直後(T+00:00:00)であることを示しています。この瞬間にステージ 1 の状態が表示され、すべての主要な飛行パラメータが初期値になっています: * 高度 = 0 km、下方向距離 = 0 km * 速度 = 0 m/s(垂直 = 0 m/s、水平 = 0 m/s) * 加速度 = 0 G;Max Q = 0 kPa;動圧 = 0 kPa * 空気抵抗係数 = 0.00;マッハ数 = 0.00 * 質量 = 0 kg;残り燃料 = 0 % * 推力 = 0 kN * ピッチ角度 = 90°(目標値と現在値) * Apoapsis = 0 km、Periapsis = 0 km ログには「MISSION EVENTS」セクションも含まれ、ピッチプログラムでは目標ピッチと現在ピッチがともに 90° と記載されています。これらの基準値はミッションコントロール、シミュレーター、およびエンジニアリングチームにとって重要であり、逸脱すると安全性を損なったり性能予測に影響を与える可能性があります。

**QtNat – Qt UPnPでポートを開く** | そっか~ニュース