Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  test_happy_eyeballs_0rtt_fallback.js   Sprache: JAVA

 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


// Regression test: TCP 0-RTT with alpnChanged=1 (H2 ticket, server responds H1)
// must not declare the dying connection the HE winner and insert it into
// mActiveConns.  With mUsingSpdy=true on the CM entry, RestrictConnections()
// would permanently block all new connection attempts for the host.
//
// Setup: warm-up over IPv4-only establishes mUsingSpdy=true and leaves an H2
// session ticket; the race (IPv4 0 ms, IPv6 100 ms) fires 0-RTT with that
// ticket while the server now negotiates H1, triggering alpnChanged=1.

"use strict";

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);
const { HttpServer } = ChromeUtils.importESModule(
  "resource://testing-common/httpd.sys.mjs"
);
const { NodeHTTPServer } = ChromeUtils.importESModule(
  "resource://testing-common/NodeServer.sys.mjs"
);
var { setTimeout } = ChromeUtils.importESModule(
  "resource://gre/modules/Timer.sys.mjs"
);

const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
  Ci.nsINativeDNSResolverOverride
);

let callbackServer;
let gServerStarted = false;

add_setup(
  {
    skip_if: () => AppConstants.MOZ_SYSTEM_NSS,
  },
  async () => {
    callbackServer = new HttpServer();
    callbackServer.registerPrefixHandler("/callback/", () => {});
    callbackServer.start(-1);

    Services.env.set(
      "MOZ_ZERORTT_ACCEPT_CALLBACK_PORT",
      callbackServer.identity.primaryPort
    );
    Services.env.set("MOZ_TLS_SERVER_0RTT", "1");
    const started = await asyncStartTLSTestServer(
      "ZeroRttAcceptServer",
      "../../../security/manager/ssl/tests/unit/test_faulty_server"
    );
    if (!started) {
      return;
    }
    gServerStarted = true;

    let nss = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
    await nss.asyncClearSSLExternalAndInternalSessionCache();

    Services.prefs.setBoolPref("network.http.happy_eyeballs_enabled", true);
    Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
    Services.prefs.setBoolPref("network.ssl_tokens_cache_enabled", true);
    Services.prefs.setBoolPref("network.http.http3.enable", false);
    // Make the H2 idle timeout very short so the warm-up H2 session is
    // reclaimed quickly, leaving the CM entry alive (mUsingSpdy=true) but
    // mActiveConns empty before the race starts.
    Services.prefs.setIntPref("network.http.http2.timeout", 2);

    registerCleanupFunction(async () => {
      Services.prefs.clearUserPref("network.http.happy_eyeballs_enabled");
      Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
      Services.prefs.clearUserPref("network.ssl_tokens_cache_enabled");
      Services.prefs.clearUserPref("network.http.http3.enable");
      Services.prefs.clearUserPref("network.http.http2.timeout");
      Services.prefs.clearUserPref("network.dns.disableIPv6");
      override.clearOverrides();
      if (callbackServer) {
        await callbackServer.stop();
        callbackServer = null;
      }
    });
  }
);

// Mutable proxy: constant port (same ConnectionInfo key throughout) with
// configurable per-family delay.
async function startMutableProxy(node) {
  return node.execute(`
    (function() {
      const net = require("net");
      global.__proxy = { ipv6Ms: 0, ipv4Ms: 0, p6: null, p4: null };
      function fwd(client, family) {
        const ms = global.__proxy[family + "Ms"];
        let buf = [], dead = false;
        client.on("data", c => buf.push(c));
        ["error","end","close"].forEach(e => client.on(e, () => { dead = true; }));
        setTimeout(() => {
          if (dead) { try { client.destroy(); } catch(_) {} return; }
          const b = net.connect(8443, "127.0.0.1", () => {
            buf.forEach(c => b.write(c)); buf = null;
            client.removeAllListeners("data");
            client.on("data", c => b.write(c));
            b.on("data", c => { try { client.write(c); } catch(_) {} });
            b.on("end", () => { try { client.end(); } catch(_) {} });
            client.on("end", () => b.end());
            b.on("error", () => client.destroy());
            client.on("error", () => b.destroy());
          });
          b.on("error", () => client.destroy());
        }, ms);
      }
      const p6 = net.createServer(s => fwd(s, "ipv6"));
      const p4 = net.createServer(s => fwd(s, "ipv4"));
      global.__proxy.p6 = p6; global.__proxy.p4 = p4;
      return new Promise((res, rej) => {
        p6.once("error", rej);
        p6.listen(0, "::1", () => {
          const port = p6.address().port;
          p4.once("error", rej);
          p4.listen(port, "127.0.0.1", () => res(port));
        });
      });
    })()
  `);
}

async function setProxyDelay(node, ipv6Ms, ipv4Ms) {
  await node.execute(
    `global.__proxy.ipv6Ms=${ipv6Ms}; global.__proxy.ipv4Ms=${ipv4Ms};`
  );
}

async function stopProxy(node) {
  await node.execute(`
    if (global.__proxy) {
      global.__proxy.p6.close(); global.__proxy.p4.close();
      global.__proxy = null;
    }
  `);
}

function fetchNoThrow(url) {
  const chan = NetUtil.newChannel({
    uri: url,
    loadUsingSystemPrincipal: true,
  }).QueryInterface(Ci.nsIHttpChannel);
  chan.loadFlags =
    Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI | Ci.nsIRequest.LOAD_BYPASS_CACHE;
  const promise = new Promise(resolve => {
    chan.asyncOpen({
      onStartRequest() {},
      onDataAvailable(_req, stream, _offset, count) {
        read_stream(stream, count);
      },
      onStopRequest(req, status) {
        if (Components.isSuccessCode(status)) {
          resolve({
            ok: true,
            status: req.QueryInterface(Ci.nsIHttpChannel).responseStatus,
          });
        } else {
          resolve({ ok: false, status: 0, error: status });
        }
      },
      QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
    });
  });
  return { chan, promise };
}

add_task(
  {
    skip_if: () =>
      AppConstants.MOZ_SYSTEM_NSS ||
      !gServerStarted ||
      mozinfo.os == "android" ||
      mozinfo.socketprocess_networking,
  },
  async function test_he_0rtt_alpn_switch_dead_winner_blocks_host() {
    // Warm-up (IPv4-only) sets mUsingSpdy=true on the CM entry and issues an
    // H2 session ticket.  After the idle timeout, the CM entry stays alive
    // (mUsingSpdy=true, mActiveConns empty).  The race fires 0-RTT with that
    // ticket; the server responds H1 → alpnChanged=1 → Finish0RTT fires.

    const host = "0rtt-alpn-switch.example.com";
    const node = new NodeHTTPServer();
    await node.start();

    try {
      let nss = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
      await nss.asyncClearSSLExternalAndInternalSessionCache();

      override.clearOverrides();
      override.addIPOverride(host, "::1");
      override.addIPOverride(host, "127.0.0.1");

      // One proxy, constant port, so warm-up and race share the same
      // ConnectionInfo key (and therefore the same CM entry / mUsingSpdy flag).
      const port = await startMutableProxy(node);
      const url = `https://${host}:${port}/`;

      // ── Warm-up (IPv4 only, 0 ms delay) ──────────────────────────────────
      // Server handles connection #0 as H2, issues a session ticket.
      // ReportSpdyConnection sets mUsingSpdy=true on the CM entry.
      Services.prefs.setBoolPref("network.dns.disableIPv6", true);
      const wu = fetchNoThrow(url);
      const wuResult = await wu.promise;
      Assert.ok(wuResult.ok, "warm-up must succeed");
      Services.prefs.setBoolPref("network.dns.disableIPv6", false);

      // Let the H2 idle timeout (2 s) fire so the warm-up H2 session is
      // reclaimed and mActiveConns becomes empty. The CM entry stays alive
      // (mUsingSpdy=true) — DO NOT call net:cancel-all-connections, which
      // would remove the entry from mCT and lose mUsingSpdy=true.
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      await new Promise(r => setTimeout(r, 3000));

      // ── Race (IPv6 100 ms, IPv4 0 ms) ────────────────────────────────────
      // Server uses H1 for all subsequent connections (gAlpnSwitchCount >= 1).
      // Client's H2 ticket → 0-RTT; server responds H1 → alpnChanged=1 →
      // Finish0RTT(restart=1) fires.
      //
      // Without the fix: HE declares the dying connection the winner, inserting
      // it into mActiveConns.  npnPending=true (only Start0RTTSpdy ran) →
      // RestrictConnections() permanently blocks the re-queued transaction.
      //
      // Only one request: with multiple requests, other HCAs (no ticket, plain
      // H1) would win and remove the dead winner via MakeAllDontReuseExcept,
      // self-healing the deadlock before the timeout fires.
      await setProxyDelay(node, 100, 0);

      const TIMEOUT_MS = 10000;
      let timedOut = false;

      const f1 = fetchNoThrow(url);

      const result = await Promise.race([
        f1.promise,
        new Promise(r =>
          // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
          setTimeout(() => {
            timedOut = true;
            r(null);
          }, TIMEOUT_MS)
        ),
      ]);

      if (timedOut) {
        try {
          f1.chan.cancel(Cr.NS_ERROR_ABORT);
        } catch (_) {}
        await f1.promise;
      }

      Assert.ok(
        !timedOut,
        "Request must complete — without the fix, Finish0RTT(alpnChanged=1) " +
          "inserts the dead connection into mActiveConns and " +
          "RestrictConnections() permanently blocks new connections for this host."
      );
      if (!timedOut) {
        Assert.ok(result.ok, "request should return HTTP 200");
      }
    } finally {
      Services.obs.notifyObservers(null, "net:cancel-all-connections");
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      await new Promise(r => setTimeout(r, 200));
      await stopProxy(node);
      await node.stop();
      override.clearOverrides();
    }
  }
);

add_task(
  {
    skip_if: () =>
      AppConstants.MOZ_SYSTEM_NSS ||
      !gServerStarted ||
      mozinfo.os == "android" ||
      mozinfo.socketprocess_networking,
  },
  async function test_he_0rtt_h2_txn_gone_closes_connection() {
    const host = "0rtt-reject-h2.example.com";
    const TLS_PORT = 8443;
    const url = `https://${host}:${TLS_PORT}/`;

    let nss = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
    await nss.asyncClearSSLExternalAndInternalSessionCache();

    override.clearOverrides();
    override.addIPOverride(host, "127.0.0.1");
    Services.prefs.setBoolPref("network.dns.disableIPv6", true);

    try {
      // ── Warm-up: full H2 handshake, session ticket issued ──────────────
      const wu = fetchNoThrow(url);
      const wuResult = await wu.promise;
      Assert.ok(wuResult.ok, "H2 warm-up must succeed");

      // Wait for the session ticket to arrive and be cached.
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      await new Promise(r => setTimeout(r, 1000));

      // Wait for the H2 idle timeout (2 s, set in add_setup) to reclaim the
      // warm-up session so the next connection is forced to race fresh.
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      await new Promise(r => setTimeout(r, 3000));

      // ── Test: H2 0-RTT rejected, pref forces the null-realTxn path ────
      Services.prefs.setBoolPref(
        "network.http.0rtt_force_txn_gone_for_testing",
        true
      );

      const f = fetchNoThrow(url);
      const result = await f.promise;

      Assert.ok(!result.ok, "request should fail when null-txn path fires");
      Assert.equal(
        result.error,
        Cr.NS_ERROR_ABORT,
        "TCP connection should be closed cleanly with NS_ERROR_ABORT"
      );

      Services.prefs.clearUserPref(
        "network.http.0rtt_force_txn_gone_for_testing"
      );

      // ── Recovery: networking stack must still be functional ────────────
      Services.obs.notifyObservers(null, "net:cancel-all-connections");
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      await new Promise(r => setTimeout(r, 200));

      const r3 = fetchNoThrow(url);
      const r3Result = await r3.promise;
      Assert.ok(r3Result.ok, "recovery request should succeed");
      Assert.equal(r3Result.status, 200, "recovery request should return 200");
    } finally {
      Services.prefs.clearUserPref(
        "network.http.0rtt_force_txn_gone_for_testing"
      );
      Services.prefs.clearUserPref("network.dns.disableIPv6");
      Services.obs.notifyObservers(null, "net:cancel-all-connections");
      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
      await new Promise(r => setTimeout(r, 200));
      override.clearOverrides();
    }
  }
);

Messung V0.5 in Prozent
C=84 H=100 G=92

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet am  2026-10-02) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1126438
#Domains=1867298