Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .cargo/config.toml.in
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ git = "https://github.com/mozilla/cubeb-pulse-rs"
rev = "3cf4a1af09a1748d0e184297a17132bdcc608874"
replace-with = "vendored-sources"

[source."git+https://github.com/mozilla/happy-eyeballs?tag=v0.6.0"]
[source."git+https://github.com/mozilla/happy-eyeballs?tag=v0.7.0"]
git = "https://github.com/mozilla/happy-eyeballs"
tag = "v0.6.0"
tag = "v0.7.0"
replace-with = "vendored-sources"

[source."git+https://github.com/mozilla/memtest?rev=ad681ba425beb0aeba95f03e671432b4be932174"]
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions netwerk/dns/HTTPSSVC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ NS_IMETHODIMP SVCBRecord::GetHasIPHintAddress(bool* aHasIPHintAddress) {
return NS_OK;
}

// Legacy HTTPS-RR / CNAME consistency check for the non-Happy-Eyeballs-v3
// code path. When network.http.happy_eyeballs_enabled is true this check is
// handled unconditionally inside the happy-eyeballs crate; this function (and
// the network.dns.https_rr.check_record_with_cname pref) only take effect on
// the legacy path.
static bool CheckRecordIsUsableWithCname(const SVCB& aRecord,
const nsACString& aCname) {
if (StaticPrefs::network_dns_https_rr_check_record_with_cname() &&
Expand Down
22 changes: 17 additions & 5 deletions netwerk/protocol/http/HappyEyeballsConnectionAttempt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,10 @@ HappyEyeballsConnectionAttempt::SetupDnsFlags(
break;
}

// The HTTPS-RR / CNAME consistency check always runs in Happy Eyeballs v3
// and needs the canonical name from the origin's address resolution.
dnsFlags |= nsIDNSService::RESOLVE_CANONICAL_NAME;

// Deal with IP hints later
/*if (ent->mConnInfo->HasIPHintAddress()) {
nsresult rv;
Expand Down Expand Up @@ -1513,8 +1517,9 @@ nsresult HappyEyeballsConnectionAttempt::OnARecord(nsIDNSRecord* aRecord,
nsresult rv;
if (NS_FAILED(status) || !addrRecord) {
nsTArray<NetAddr> emptyArray;
rv =
happy_eyeballs_process_dns_response_a(mHappyEyeballs, aId, &emptyArray);
nsAutoCString cname;
rv = happy_eyeballs_process_dns_response_a(mHappyEyeballs, aId, &emptyArray,
&cname);
if (NS_FAILED(rv)) {
return rv;
}
Expand All @@ -1524,6 +1529,9 @@ nsresult HappyEyeballsConnectionAttempt::OnARecord(nsIDNSRecord* aRecord,
nsTArray<NetAddr> addresses;
addrRecord->GetAddresses(addresses);

nsAutoCString cname;
(void)addrRecord->GetCanonicalName(cname);

// Filter to only IPv4 addresses
nsTArray<NetAddr> ipv4Addresses;
for (const auto& addr : addresses) {
Expand All @@ -1534,7 +1542,7 @@ nsresult HappyEyeballsConnectionAttempt::OnARecord(nsIDNSRecord* aRecord,
}

rv = happy_eyeballs_process_dns_response_a(mHappyEyeballs, aId,
&ipv4Addresses);
&ipv4Addresses, &cname);
if (NS_FAILED(rv)) {
return rv;
}
Expand All @@ -1561,8 +1569,9 @@ nsresult HappyEyeballsConnectionAttempt::OnAAAARecord(nsIDNSRecord* aRecord,
nsresult rv;
if (NS_FAILED(status) || !addrRecord) {
nsTArray<NetAddr> emptyArray;
nsAutoCString cname;
rv = happy_eyeballs_process_dns_response_aaaa(mHappyEyeballs, aId,
&emptyArray);
&emptyArray, &cname);
if (NS_FAILED(rv)) {
return rv;
}
Expand All @@ -1572,6 +1581,9 @@ nsresult HappyEyeballsConnectionAttempt::OnAAAARecord(nsIDNSRecord* aRecord,
nsTArray<NetAddr> addresses;
addrRecord->GetAddresses(addresses);

nsAutoCString cname;
(void)addrRecord->GetCanonicalName(cname);

// Filter to only IPv6 addresses
nsTArray<NetAddr> ipv6Addresses;
for (const auto& addr : addresses) {
Expand All @@ -1582,7 +1594,7 @@ nsresult HappyEyeballsConnectionAttempt::OnAAAARecord(nsIDNSRecord* aRecord,
}

rv = happy_eyeballs_process_dns_response_aaaa(mHappyEyeballs, aId,
&ipv6Addresses);
&ipv6Addresses, &cname);
if (NS_FAILED(rv)) {
return rv;
}
Expand Down
2 changes: 1 addition & 1 deletion netwerk/protocol/http/happy_eyeballs_glue/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = "FFI glue layer between happy-eyeballs Rust crate and Firefox's C+
[dependencies]
firefox-on-glean = { path = "../../../../toolkit/components/glean/api" }
gecko-profiler = { path = "../../../../tools/profiler/rust-api" }
happy-eyeballs = { tag = "v0.6.0", git = "https://github.com/mozilla/happy-eyeballs" }
happy-eyeballs = { tag = "v0.7.0", git = "https://github.com/mozilla/happy-eyeballs" }
log = "0.4"
nserror = { path = "../../../../xpcom/rust/nserror" }
serde = { version = "1.0", features = ["derive"] }
Expand Down
36 changes: 30 additions & 6 deletions netwerk/protocol/http/happy_eyeballs_glue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub unsafe extern "C" fn happy_eyeballs_process_dns_response_a(
he: *mut HappyEyeballs,
id: u64,
addrs: *const ThinVec<NetAddr>,
cname: *const nsACString,
) -> nsresult {
let Some(he) = (unsafe { he.as_mut() }) else {
debug_assert!(false, "unexpected null he pointer");
Expand All @@ -125,14 +126,15 @@ pub unsafe extern "C" fn happy_eyeballs_process_dns_response_a(
return NS_ERROR_INVALID_ARG;
};

he.process_dns_response_a(id, addrs)
he.process_dns_response_a(id, addrs, canonical_name(cname))
}

#[no_mangle]
pub unsafe extern "C" fn happy_eyeballs_process_dns_response_aaaa(
he: *mut HappyEyeballs,
id: u64,
addrs: *const ThinVec<NetAddr>,
cname: *const nsACString,
) -> nsresult {
let Some(he) = (unsafe { he.as_mut() }) else {
debug_assert!(false, "unexpected null he pointer");
Expand All @@ -144,7 +146,19 @@ pub unsafe extern "C" fn happy_eyeballs_process_dns_response_aaaa(
return NS_ERROR_INVALID_ARG;
};

he.process_dns_response_aaaa(id, addrs)
he.process_dns_response_aaaa(id, addrs, canonical_name(cname))
}

/// Converts an optional FFI canonical-name string into a [`TargetName`].
///
/// A null or empty string yields `None`, matching the state machine's "empty
/// cname => no filtering" behavior.
fn canonical_name(cname: *const nsACString) -> Option<happy_eyeballs::TargetName> {
let cname = unsafe { cname.as_ref() }?;
if cname.is_empty() {
return None;
}
Some(happy_eyeballs::TargetName::from(cname.to_utf8().as_ref()))
}

#[no_mangle]
Expand Down Expand Up @@ -219,7 +233,12 @@ pub struct HappyEyeballs {
}

impl HappyEyeballs {
fn process_dns_response_a(&mut self, id: u64, net_addrs: &ThinVec<NetAddr>) -> nsresult {
fn process_dns_response_a(
&mut self,
id: u64,
net_addrs: &ThinVec<NetAddr>,
cname: Option<happy_eyeballs::TargetName>,
) -> nsresult {
let id: happy_eyeballs::Id = id.into();
let mut addrs = Vec::with_capacity(net_addrs.len());
for na in net_addrs.iter() {
Expand All @@ -237,14 +256,19 @@ impl HappyEyeballs {
self.profiler.dns_response(id, &addrs);
self.metrics.dns_response(id);

let result = happy_eyeballs::DnsResult::A(Ok(addrs));
let result = happy_eyeballs::DnsResult::A(Ok(addrs), cname);
let input = happy_eyeballs::Input::DnsResult { id, result };
self.inner.process_input(input, Instant::now());

NS_OK
}

fn process_dns_response_aaaa(&mut self, id: u64, net_addrs: &ThinVec<NetAddr>) -> nsresult {
fn process_dns_response_aaaa(
&mut self,
id: u64,
net_addrs: &ThinVec<NetAddr>,
cname: Option<happy_eyeballs::TargetName>,
) -> nsresult {
let id: happy_eyeballs::Id = id.into();
let mut addrs = Vec::with_capacity(net_addrs.len());
for na in net_addrs.iter() {
Expand All @@ -263,7 +287,7 @@ impl HappyEyeballs {
self.profiler.dns_response(id, &addrs);
self.metrics.dns_response(id);

let result = happy_eyeballs::DnsResult::Aaaa(Ok(addrs));
let result = happy_eyeballs::DnsResult::Aaaa(Ok(addrs), cname);
let input = happy_eyeballs::Input::DnsResult { id, result };
self.inner.process_input(input, Instant::now());

Expand Down
25 changes: 19 additions & 6 deletions netwerk/test/unit/test_trr_https_rr_with_cname.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,10 @@ add_setup(async function setup() {

Services.prefs.setBoolPref("network.dns.port_prefixed_qname_https_rr", false);

// Happy Eyeballs does not support check CNAME for now.
Services.prefs.setBoolPref("network.http.happy_eyeballs_enabled", false);

trr_test_setup();
registerCleanupFunction(async () => {
trr_clear_prefs();
Services.prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
Services.prefs.clearUserPref("network.http.happy_eyeballs_enabled");
});

h2Port = Services.env.get("MOZHTTP2_PORT");
Expand Down Expand Up @@ -180,7 +176,14 @@ async function do_test_https_rr_records(
// Test the case that the pref is off and the cname is not the same as the
// targetName. The expected protocol version being "h3" means that the last
// svcb record is used.
//
// check_record_with_cname only takes effect on the legacy (non-Happy-Eyeballs-v3)
// path; HE-v3 performs the CNAME check unconditionally, so the pref=off case
// only exists on the legacy path. Pin this test there. It can be removed once
// the non-HE-v3 path is gone. The pref is restored at the end of the task so
// the following tests still exercise HE-v3.
add_task(async function test_https_rr_with_unmatched_cname() {
Services.prefs.setBoolPref("network.http.happy_eyeballs_enabled", false);
Services.prefs.setBoolPref(
"network.dns.https_rr.check_record_with_cname",
false
Expand All @@ -194,6 +197,7 @@ add_task(async function test_https_rr_with_unmatched_cname() {
"test.cname1.com",
"h3"
);
Services.prefs.clearUserPref("network.http.happy_eyeballs_enabled");
});

// Test the case that the pref is on and the cname is not the same as the
Expand Down Expand Up @@ -232,9 +236,18 @@ add_task(async function test_https_rr_with_matched_cname() {
);
});

// Test the case that the pref is on and both records are failed to connect.
// We can only fallback to "h2" when another pref is on.
// Test the case that the pref is on and both HTTPS records fail to connect.
// We can only fall back to plain "h2" when network.dns.echconfig.fallback_to_origin_when_all_failed is on.
//
// This test covers ECH failure → origin fallback (not a CNAME mismatch).
// With Happy Eyeballs v3 the origin fallback after ECH failure is not implemented,
// so the test is pinned to the legacy (non-HE-v3) code path where
// echconfig.fallback_to_origin_when_all_failed still applies.
add_task(async function test_https_rr_with_matched_cname_1() {
Services.prefs.setBoolPref("network.http.happy_eyeballs_enabled", false);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("network.http.happy_eyeballs_enabled");
});
Comment on lines +241 to +250

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I thought once a cname mismatch is detected, it does fallback. Please explain.

Services.prefs.setBoolPref(
"network.dns.echconfig.fallback_to_origin_when_all_failed",
true
Expand Down
2 changes: 1 addition & 1 deletion third_party/rust/happy-eyeballs/.cargo-checksum.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"files":{".codecov.yml":"81d2e8284c4c496f2d251510fe631509a89cfdf63a851a1b650e00de34eb7131",".github/CODEOWNERS":"fe246d8cd7003a28b9b250319c196db2051adaa15f0390a4a5e8d8a5315b0a51",".github/dependabot.yml":"91c1465d580d7ce968d40ae09ddc789a83bc6b5a8a622679071c63ad748bf207",".github/workflows/claude.yml":"47f6e2f725861de913a147e8d80bed28f51a2376798dd5aa97d9331a23089085",".github/workflows/mutants.yml":"c8f3dcafa9b560f9d87ff22f1923d6fc2fd340eb0863244113d2cd4c2891ad13",".github/workflows/rust.yml":"40fffdc3b5177dd3019cf6a94afb9700691b90ee2f712e5ae69337ee005bddb1",".github/workflows/sbom.yml":"9de74b8f79d0a88747d5ab08385b0a40b53134a574f019c7615414a166e2ae9e","CODE_OF_CONDUCT.md":"f7b4cba1deaa0a77bd611c04c84ef5b6859e44c8370f7513fe688fb9531b913b","Cargo.lock":"7677a765d090efdecc800b9fd672e47dba8496505bfb31a8d512ca592119123a","Cargo.toml":"7dae0463e54971f7b6f4c213a7dcc7a6d75a51bce08e38028f7fce566d27625c","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"4ad721b5b6a3d39ca3e2202f403d897c4a1d42896486dd58963a81f8e64ef61d","README.md":"ea07ce695cee320e0e053fa4dede6a6300aade43eece29a6a4b40d1ad114b420","deny.toml":"eb7287cd071f6f5ff285ad2bf96ebac0d328a4ddb55bb8c3967f6d737f2d43ed","src/id.rs":"3f025f8d61891dbebcabb19659640165e3eb37390325a9a50e1e13a415bd70c5","src/lib.rs":"1d1330f52cb47dfaf9ed83de4cab27320d3835700db7ca47cebc6cc776fc50de","tests/common/mod.rs":"f192ccded009dc2b421648671013f0dbe37e9b8ed1dbc90e2cb19dd3bbf60b34","tests/connection_attempts.rs":"b8337ef2044b60a634345f6df6e5203eadc85da2b93a9960a0c971ee3ea14c3c","tests/failure.rs":"31303e43825e492ddb87215c2480200d1768b350f2f9de7e12d17cfe4eafb19e","tests/hostname_resolution.rs":"ad2e103dd1f1ab301b2d8c0bfed1a6827987adf292fe1ee27e8b0479ca773459","tests/https_records.rs":"d84bff4dee30609db2110715283d164286b524303d55ad1e58f8348d88b55c69","tests/network_config.rs":"5a75ebb8db4bbc68f6bef3621dfcd932bdf6769cd410d8882293758ee4ce14bc","tests/type_impls.rs":"96a5b22d8c007ef679dc0f440fd982644b27bdb86e9362f06c0f4dd09596ca6b"},"package":null}
{"files":{".codecov.yml":"81d2e8284c4c496f2d251510fe631509a89cfdf63a851a1b650e00de34eb7131",".github/CODEOWNERS":"fe246d8cd7003a28b9b250319c196db2051adaa15f0390a4a5e8d8a5315b0a51",".github/dependabot.yml":"91c1465d580d7ce968d40ae09ddc789a83bc6b5a8a622679071c63ad748bf207",".github/workflows/claude.yml":"47f6e2f725861de913a147e8d80bed28f51a2376798dd5aa97d9331a23089085",".github/workflows/mutants.yml":"c8f3dcafa9b560f9d87ff22f1923d6fc2fd340eb0863244113d2cd4c2891ad13",".github/workflows/rust.yml":"40fffdc3b5177dd3019cf6a94afb9700691b90ee2f712e5ae69337ee005bddb1",".github/workflows/sbom.yml":"9de74b8f79d0a88747d5ab08385b0a40b53134a574f019c7615414a166e2ae9e","CODE_OF_CONDUCT.md":"f7b4cba1deaa0a77bd611c04c84ef5b6859e44c8370f7513fe688fb9531b913b","Cargo.lock":"07d0fe03d8d031278d3d39730581c2e19264d3027b8c15247b7e578d1eed4b5c","Cargo.toml":"218166445cf58519cb72e32dbb777c8975d25f7089f8d9485a590c99f3a0069c","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"4ad721b5b6a3d39ca3e2202f403d897c4a1d42896486dd58963a81f8e64ef61d","README.md":"ea07ce695cee320e0e053fa4dede6a6300aade43eece29a6a4b40d1ad114b420","deny.toml":"eb7287cd071f6f5ff285ad2bf96ebac0d328a4ddb55bb8c3967f6d737f2d43ed","src/id.rs":"3f025f8d61891dbebcabb19659640165e3eb37390325a9a50e1e13a415bd70c5","src/lib.rs":"2b3874b6743d9816627935098f5f987ff0396782c2855f248979e797639725c0","tests/common/mod.rs":"81db5b231e02e404892cec66a50ba5880740e328b17222d76cabd6df3cbbdf59","tests/connection_attempts.rs":"976b26fa3c695b664fbecbf746a77cce447649f22fb1a29ec06f44374f96da65","tests/failure.rs":"b72853297ff50d88e111a49941e7ff31f350031f4933f9b747088ed5a29384a1","tests/hostname_resolution.rs":"57b7b9d48f5fb8265d1ee18fc2569a7caa835e39a88b19d66327d9a843814faa","tests/https_records.rs":"acce31c1e01b94ef48913bcf4e18c9d2b574947afdc2bfb346dfbcf9529d0263","tests/https_rr_cname.rs":"88fb0cda127f3ca6c6b7a4f93aaa94d705339aac6af9cff6ae6c05666190d3f9","tests/network_config.rs":"c8a94f482f2e41b1c9aef77d42b2a544340aafe67383cd3cf27159aeeb2fa79b","tests/type_impls.rs":"bccc835a4d15a7335307f8b24c8812d551c0b1cc46f5cd5f21cb7b72daebf07f"},"package":null}
2 changes: 1 addition & 1 deletion third_party/rust/happy-eyeballs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 8 additions & 62 deletions third_party/rust/happy-eyeballs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,68 +1,14 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.

[package]
name = "happy-eyeballs"
version = "0.7.0"
edition = "2024"
rust-version = "1.85.0"
name = "happy-eyeballs"
version = "0.6.0"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
readme = "README.md"
license = "MIT OR Apache-2.0"

[lib]
name = "happy_eyeballs"
path = "src/lib.rs"

[[test]]
name = "connection_attempts"
path = "tests/connection_attempts.rs"

[[test]]
name = "failure"
path = "tests/failure.rs"

[[test]]
name = "hostname_resolution"
path = "tests/hostname_resolution.rs"

[[test]]
name = "https_records"
path = "tests/https_records.rs"

[[test]]
name = "network_config"
path = "tests/network_config.rs"

[[test]]
name = "type_impls"
path = "tests/type_impls.rs"

[dependencies.log]
version = "0.4"
default-features = false

[dependencies.thiserror]
version = "2.0.12"
default-features = false

[dependencies.url]
version = "2.5.7"
default-features = false
[dependencies]
log = { version = "0.4", default-features = false }
thiserror = { version = "2.0.12", default-features = false }
url = { version = "2.5.7", default-features = false }

[dev-dependencies.env_logger]
version = "0.10"
default-features = false
[dev-dependencies]
env_logger = { version = "0.10", default-features = false }
Loading