Eine aufbereitete Darstellung der Quelle

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

Benutzer

Quelle  test_happy_eyeballs_h1_0rtt.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/. */


// Verifies the Happy Eyeballs HTTP/1 0-RTT-accepted code path:
//  * The winning HT is promoted (conn.mTransaction becomes a pass-
//    through to the real nsHttpTransaction) so no re-Activate /
//    DispatchTransaction races with the MOZ_ASSERT in the conn mgr.
//  * The real transaction does NOT re-send its request on the wire
//    after the server has accepted the early-data bytes.
//
// We run against ZeroRttAcceptServer — a TLS 1.3 test server that
// accepts 0-RTT, reads HTTP requests in full, and fires a callback on
// every request it observes on the wire. The test counts callbacks:
// one per user-visible fetch. A duplicate request post-handshake
// (i.e. the bug this fix guards against) would produce a second
// callback on the same connection.

"use strict";

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

let callbackServer;
// Server splits /callback/request/{early,std} by whether the request
// arrived via accepted 0-RTT early data. Track the two counters
// separately so tests can assert which path delivered each fetch.
let earlyCount = 0;
let stdCount = 0;
// Set to true in add_setup once ZeroRttAcceptServer starts successfully.
// Tasks check this via skip_if so they are cleanly skipped (not timed out)
// when the server cert directory is absent (e.g. comm-central packaging).
let gServerStarted = false;

function callbackHandler(metadata) {
  if (metadata.path === "/callback/request/early") {
    earlyCount++;
  } else if (metadata.path === "/callback/request/std") {
    stdCount++;
  }
}

function resetCounts() {
  earlyCount = 0;
  stdCount = 0;
}

add_setup(
  {
    skip_if: () => AppConstants.MOZ_SYSTEM_NSS,
  },
  async () => {
    callbackServer = new HttpServer();
    callbackServer.registerPrefixHandler("/callback/", callbackHandler);
    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; // cert dir absent (e.g. comm-central); tasks will be skipped
    }
    gServerStarted = true;
    let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
    await nssComponent.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);
    // ZeroRttAcceptServer binds IPv4 loopback only. HE otherwise tries
    // [::1] first and the losing IPv6 attempt perturbs 0-RTT state for
    // the winning IPv4 attempt, collapsing fetch#2 into the std path.
    // Race tasks supply both families via a reverse proxy and flip
    // this pref themselves.
    Services.prefs.setBoolPref("network.dns.disableIPv6", true);

    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.dns.disableIPv6");
      if (callbackServer) {
        await callbackServer.stop();
      }
    });
  }
);

function fetchExpect200(url) {
  return new Promise(resolve => {
    let chan = NetUtil.newChannel({
      uri: url,
      loadUsingSystemPrincipal: true,
    }).QueryInterface(Ci.nsIHttpChannel);
    chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
    chan.asyncOpen(
      new ChannelListener(
        req => {
          let httpChan = req.QueryInterface(Ci.nsIHttpChannel);
          let httpChanInt = req.QueryInterface(Ci.nsIHttpChannelInternal);
          let status = 0;
          let remote = "";
          let resumed = false;
          try {
            status = httpChan.responseStatus;
          } catch (e) {}
          try {
            remote = httpChanInt.remoteAddress;
          } catch (e) {}
          try {
            resumed = req.securityInfo.resumed;
          } catch (e) {}
          resolve({ status, remote, resumed });
        },
        null,
        CL_ALLOW_UNKNOWN_CL
      )
    );
  });
}

async function runHandshakeThenResume(host) {
  Services.prefs.setCharPref("network.dns.localDomains", host);
  const url = `https://${host}:8443/`;

  resetCounts();

  let r1 = await fetchExpect200(url);
  Assert.equal(r1.status, 200, "First fetch should succeed");

  // Anti-replay window + NewSessionTicket propagation.
  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  await new Promise(resolve => setTimeout(resolve, 1500));

  Services.obs.notifyObservers(null, "net:cancel-all-connections");
  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  await new Promise(resolve => setTimeout(resolve, 200));

  let r2 = await fetchExpect200(url);
  Assert.equal(r2.status, 200, "Second fetch should succeed");

  // Drop the keep-alive conn from fetch #2 so the server's single-
  // threaded accept loop (parked in PR_Recv inside HandleH1Session)
  // gets EOF and returns, freeing it for the next task's ClientHello.
  Services.obs.notifyObservers(null, "net:cancel-all-connections");
  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  await new Promise(resolve => setTimeout(resolve, 200));
}

add_task(
  {
    skip_if: () => AppConstants.MOZ_SYSTEM_NSS || !gServerStarted,
  },
  async function test_he_h1_0rtt_accepted_no_duplicate_on_the_wire() {
    // Server accepts 0-RTT. HE promotes the winning HT to forward to
    // the real txn. Real txn's request stream is positioned at the
    // winner's offset so ReadSegments returns 0 bytes and
    // nsHttpConnection treats the request as fully sent. No duplicate
    // request burst is produced on the wire.
    await runHandshakeThenResume("0rtt-accept-h1.example.com");
    Assert.equal(stdCount, 1, "Fetch #1 arrived on the standard path");
    Assert.equal(earlyCount, 1, "Fetch #2 arrived as accepted 0-RTT");
  }
);

add_task(
  {
    skip_if: () => AppConstants.MOZ_SYSTEM_NSS || !gServerStarted,
  },
  async function test_he_h1_0rtt_rejected_restarts_cleanly() {
    // Server refuses 0-RTT on resumption (no anti-replay context), so
    // NSS reports "early data not accepted" and ZeroRttHandle drives
    // Finish0RTT(aRestart=true): the request stream seeks back to 0
    // and the real txn retransmits over the post-handshake wire. The
    // server sees both fetches on the standard path (the early-data
    // bytes are discarded by NSS before our handler ever sees them).
    await runHandshakeThenResume("0rtt-reject-h1.example.com");
    Assert.equal(earlyCount, 0, "No request should arrive as 0-RTT");
    Assert.equal(stdCount, 2, "Both fetches arrived on the standard path");
  }
);

// Reverse TCP proxy that listens on ::1 and 127.0.0.1 at the same
// port and forwards raw bytes to the TLS server on 127.0.0.1:8443.
// Each family can be artificially delayed so HE picks a specific
// winner in the 0-RTT resumption race. TLS is not terminated — the
// ClientHello's SNI and early data pass through intact, so the
// backend still decides ALPN / 0-RTT accept per our sHosts entries.
async function startFamilyDelayProxy(node, ipv6DelayMs, ipv4DelayMs) {
  return node.execute(`
    (function() {
      const net = require("net");
      function pipeToBackend(clientSocket, delay) {
        let buffered = [];
        let clientDead = false;
        clientSocket.on("data", (chunk) => buffered.push(chunk));
        clientSocket.on("error", () => { clientDead = true; });
        // Track client-side teardown up front: the HE race deliberately
        // cancels the losing attempt mid-delay, so without these early
        // listeners the deferred net.connect below would forward a
        // stale ClientHello to the single-threaded backend and park its
        // accept loop on a dead conn.
        clientSocket.on("end", () => { clientDead = true; });
        clientSocket.on("close", () => { clientDead = true; });

        setTimeout(() => {
          if (clientDead) {
            try { clientSocket.destroy(); } catch(e) {}
            return;
          }
          const backendSocket = net.connect(8443, "127.0.0.1", () => {
            for (const chunk of buffered) {
              backendSocket.write(chunk);
            }
            buffered = null;
            clientSocket.removeAllListeners("data");
            clientSocket.on("data", (chunk) => backendSocket.write(chunk));
            backendSocket.on("data", (chunk) => {
              try { clientSocket.write(chunk); } catch(e) {}
            });
            backendSocket.on("end", () => {
              try { clientSocket.end(); } catch(e) {}
            });
            clientSocket.on("end", () => backendSocket.end());
            backendSocket.on("error", () => clientSocket.destroy());
            clientSocket.on("error", () => backendSocket.destroy());
          });
          backendSocket.on("error", () => clientSocket.destroy());
        }, delay);
      }
      function makeProxy(delay) {
        return net.createServer((clientSocket) => {
          pipeToBackend(clientSocket, delay);
        });
      }
      const proxy6 = makeProxy(${ipv6DelayMs});
      const proxy4 = makeProxy(${ipv4DelayMs});
      return new Promise((resolve, reject) => {
        proxy6.once("error", reject);
        proxy6.listen(0, "::1", () => {
          const port = proxy6.address().port;
          proxy4.once("error", reject);
          proxy4.listen(port, "127.0.0.1", () => {
            global.delayProxy6 = proxy6;
            global.delayProxy4 = proxy4;
            resolve(port);
          });
        });
      });
    })()
  `);
}

async function stopFamilyDelayProxy(node) {
  await node.execute(`
    if (global.delayProxy6) { global.delayProxy6.close(); global.delayProxy6 = null; }
    if (global.delayProxy4) { global.delayProxy4.close(); global.delayProxy4 = null; }
  `);
}

// Drives the accept-path H1 0-RTT test through a dual-family reverse
// proxy. A native DNS override maps the hostname to both ::1 and
// 127.0.0.1 at the proxy port; per-family connect delays pick which
// address wins the HE race. Asserts fetch#1 comes through the std
// path (full handshake) and fetch#2 comes through the early path
// (0-RTT accepted on the winning family), matching the invariants of
// the non-race accept task.
async function runHe0RttRace(host, ipv6DelayMs, ipv4DelayMs) {
  let node = new NodeHTTPServer();
  await node.start();

  const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
    Ci.nsINativeDNSResolverOverride
  );
  // Race needs both families live; non-race tasks set disableIPv6=true
  // globally. Restored in the finally block below.
  Services.prefs.clearUserPref("network.dns.disableIPv6");
  Services.prefs.clearUserPref("network.dns.localDomains");
  override.addIPOverride(host, "::1");
  override.addIPOverride(host, "127.0.0.1");

  // The earlier accept task cached a resumption ticket for this same
  // host. Without clearing it, this task's "first" fetch would attempt
  // 0-RTT instead of a full handshake, and the race between the two
  // HE attempts over the proxy trips the server's anti-replay guard.
  let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
  await nssComponent.asyncClearSSLExternalAndInternalSessionCache();

  let proxyPort = await startFamilyDelayProxy(node, ipv6DelayMs, ipv4DelayMs);
  const url = `https://${host}:${proxyPort}/`;

  try {
    resetCounts();

    let r1 = await fetchExpect200(url);
    Assert.equal(r1.status, 200, "First fetch should succeed");
    Assert.equal(r1.resumed, false, "First fetch is a full handshake");

    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(resolve => setTimeout(resolve, 200));
    Services.obs.notifyObservers(null, "net:cancel-all-connections");
    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(resolve => setTimeout(resolve, 100));

    let r2 = await fetchExpect200(url);
    Assert.equal(r2.status, 200, "Second fetch should succeed");
    Assert.equal(r2.resumed, true, "Second fetch should resume the session");

    info(`fetch#1 remote=${r1.remote} resumed=${r1.resumed}`);
    info(`fetch#2 remote=${r2.remote} resumed=${r2.resumed}`);
    return { r1, r2 };
  } finally {
    Services.obs.notifyObservers(null, "net:cancel-all-connections");
    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(resolve => setTimeout(resolve, 200));
    await stopFamilyDelayProxy(node);
    await node.stop();
    override.clearOverrides();
    // Restore the non-race tasks' baseline.
    Services.prefs.setBoolPref("network.dns.disableIPv6", true);
  }
}

add_task(
  {
    skip_if: () =>
      AppConstants.MOZ_SYSTEM_NSS ||
      !gServerStarted ||
      mozinfo.os == "android" ||
      mozinfo.socketprocess_networking,
  },
  async function test_he_h1_0rtt_ipv4_wins_race() {
    // Fetch#1 has no cached session, so no 0-RTT commit. IPv6 fires
    // first but its response is delayed 1s at the proxy; IPv4 backup
    // fires at 250ms and its full handshake completes first. No 0-RTT
    // sibling exists, so the non-0-RTT disqualify policy doesn't
    // engage — IPv4 wins the full handshake (r1.remote = 127.0.0.1).
    //
    // Fetch#2 has a cached ticket. HE again fires IPv6 first and
    // ZeroRttHandle commits the 0-RTT attempt to that (IPv6) HT. The
    // IPv4 backup fires without 0-RTT and is disqualified by
    // HappyEyeballsTransaction::Close once it finishes a plain
    // handshake. Firefox waits for IPv6 to complete on the wire, so
    // r2.resumed = true. (r2.remote ends up empty because the
    // channel's remoteAddress isn't populated when the connection is
    // promoted through HappyEyeballsTransaction.)
    let { r1 } = await runHe0RttRace("0rtt-accept-h1.example.com", 1000, 0);
    Assert.equal(r1.remote, "127.0.0.1", "fetch#1 should win on IPv4");
  }
);

add_task(
  {
    skip_if: () =>
      AppConstants.MOZ_SYSTEM_NSS ||
      !gServerStarted ||
      mozinfo.os == "android" ||
      mozinfo.socketprocess_networking,
  },
  async function test_he_h1_0rtt_ipv6_wins_race() {
    // IPv6 fires first and is unimpeded; IPv4 backup is delayed and
    // never matters. Fetch#1 goes over IPv6; fetch#2 resumes via
    // 0-RTT on the same family.
    let { r1 } = await runHe0RttRace("0rtt-accept-h1.example.com", 0, 1000);
    Assert.equal(r1.remote, "::1", "fetch#1 wins on IPv6");
  }
);

Messung V0.5 in Prozent
C=76 H=99 G=88

¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet am  2026-10-04) ¤

*© 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=1897691