From ca8ed8a574f67b3be245448e0349720ec968a7fb Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 08:18:21 +0000 Subject: [PATCH 1/5] Update hickory-dns monorepo to 0.25.2 --- Cargo.lock | 343 ++++++++++++++++++++++++++++++++++---- Cargo.toml | 8 +- workspace-hack/Cargo.toml | 16 +- 3 files changed, 323 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 949e71deb6..fb33c97cb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -977,7 +977,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata", + "regex-automata 0.4.8", "serde", ] @@ -2484,7 +2484,7 @@ source = "git+https://github.com/bluecatengineering/dhcproto.git?rev=120da6fcd8a dependencies = [ "dhcproto-macros", "hex", - "hickory-proto", + "hickory-proto 0.24.4", "ipnet", "rand 0.8.5", "thiserror 1.0.69", @@ -2659,8 +2659,8 @@ dependencies = [ "dropshot", "expectorate", "hickory-client", - "hickory-proto", - "hickory-resolver", + "hickory-proto 0.25.2", + "hickory-resolver 0.25.2", "hickory-server", "http", "internal-dns-types", @@ -2992,7 +2992,7 @@ dependencies = [ "colored", "dhcproto", "futures", - "hickory-resolver", + "hickory-resolver 0.25.2", "http", "humantime", "internal-dns-resolver", @@ -3172,7 +3172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ "bit-set 0.8.0", - "regex-automata", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -3670,6 +3670,19 @@ dependencies = [ "uuid", ] +[[package]] +name = "generator" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.58.0", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3776,7 +3789,7 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -4044,19 +4057,19 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hickory-client" -version = "0.24.4" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "156579a5cd8d1fc6f0df87cc21b6ee870db978a163a1ba484acd98a4eff5a6de" +checksum = "c466cd63a4217d5b2b8e32f23f58312741ce96e3c84bf7438677d2baff0fc555" dependencies = [ "cfg-if", "data-encoding", "futures-channel", "futures-util", - "hickory-proto", + "hickory-proto 0.25.2", "once_cell", "radix_trie", - "rand 0.8.5", - "thiserror 1.0.69", + "rand 0.9.1", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -4085,6 +4098,32 @@ dependencies = [ "url", ] +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.1", + "ring", + "serde", + "thiserror 2.0.12", + "tinyvec", + "tokio", + "tracing", + "url", +] + [[package]] name = "hickory-resolver" version = "0.24.4" @@ -4093,7 +4132,7 @@ checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" dependencies = [ "cfg-if", "futures-util", - "hickory-proto", + "hickory-proto 0.24.4", "ipconfig", "lru-cache", "once_cell", @@ -4106,20 +4145,44 @@ dependencies = [ "tracing", ] +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto 0.25.2", + "ipconfig", + "moka", + "once_cell", + "parking_lot 0.12.3", + "rand 0.9.1", + "resolv-conf", + "smallvec 1.15.0", + "thiserror 2.0.12", + "tokio", + "tracing", +] + [[package]] name = "hickory-server" -version = "0.24.4" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090078aff4e305853f8ccfbc89e6a1eec8a189bcb842be46255a2b660dae9416" +checksum = "d53e5fe811b941c74ee46b8818228bfd2bc2688ba276a0eaeb0f2c95ea3b2585" dependencies = [ "async-trait", "bytes", "cfg-if", + "data-encoding", "enum-as-inner", "futures-util", - "hickory-proto", + "hickory-proto 0.25.2", + "ipnet", + "prefix-trie", "serde", - "thiserror 1.0.69", + "thiserror 2.0.12", "time", "tokio", "tokio-util", @@ -4178,7 +4241,7 @@ checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ "cfg-if", "libc", - "windows", + "windows 0.52.0", ] [[package]] @@ -4671,7 +4734,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata", + "regex-automata 0.4.8", "same-file", "walkdir", "winapi-util", @@ -4988,7 +5051,7 @@ dependencies = [ "anyhow", "clap", "dropshot", - "hickory-resolver", + "hickory-resolver 0.25.2", "internal-dns-resolver", "internal-dns-types", "omicron-common", @@ -5008,7 +5071,7 @@ dependencies = [ "dropshot", "expectorate", "futures", - "hickory-resolver", + "hickory-resolver 0.25.2", "internal-dns-types", "omicron-common", "omicron-test-utils", @@ -5081,6 +5144,9 @@ name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +dependencies = [ + "serde", +] [[package]] name = "ipnetwork" @@ -5637,6 +5703,19 @@ dependencies = [ "value-bag", ] +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lpc55_areas" version = "0.2.5" @@ -5738,6 +5817,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -5870,6 +5958,25 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "moka" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "loom", + "parking_lot 0.12.3", + "portable-atomic", + "rustc_version 0.4.1", + "smallvec 1.15.0", + "tagptr", + "thiserror 1.0.69", + "uuid", +] + [[package]] name = "multer" version = "3.1.0" @@ -6711,7 +6818,7 @@ dependencies = [ "gateway-messages", "gateway-test-utils", "headers", - "hickory-resolver", + "hickory-resolver 0.25.2", "http", "http-body-util", "hyper", @@ -6880,6 +6987,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.50.1" @@ -7443,7 +7560,7 @@ dependencies = [ "gateway-test-utils", "headers", "hex", - "hickory-resolver", + "hickory-resolver 0.25.2", "hmac", "http", "http-body-util", @@ -8019,6 +8136,7 @@ dependencies = [ "crypto-common", "curve25519-dalek", "daft", + "data-encoding", "digest", "dof", "ecdsa", @@ -8042,7 +8160,7 @@ dependencies = [ "getrandom 0.3.1", "group", "hashbrown 0.15.3", - "hickory-proto", + "hickory-proto 0.25.2", "hmac", "hyper", "hyper-rustls 0.27.3", @@ -8051,6 +8169,7 @@ dependencies = [ "indexmap 2.9.0", "indicatif", "inout", + "ipnet", "ipnetwork", "itertools 0.10.5", "itertools 0.12.1", @@ -8078,6 +8197,7 @@ dependencies = [ "phf", "phf_shared", "pkcs8", + "portable-atomic", "postgres-types", "ppv-lite86", "predicates", @@ -8086,7 +8206,7 @@ dependencies = [ "rand 0.9.1", "rand_chacha 0.9.0", "regex", - "regex-automata", + "regex-automata 0.4.8", "regex-syntax 0.8.5", "reqwest", "rsa", @@ -8110,6 +8230,7 @@ dependencies = [ "subtle", "syn 1.0.109", "syn 2.0.103", + "thiserror 2.0.12", "time", "time-macros", "tokio", @@ -8173,6 +8294,10 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "oorandom" @@ -8385,6 +8510,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owo-colors" version = "4.2.1" @@ -8399,7 +8530,7 @@ dependencies = [ "base64 0.22.1", "chrono", "futures", - "hickory-resolver", + "hickory-resolver 0.25.2", "http", "hyper", "omicron-workspace-hack", @@ -9585,6 +9716,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "prefix-trie" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cf4c7c25f1dd66c76b451e9041a8cfce26e4ca754934fa7aed8d5a59a01d20" +dependencies = [ + "ipnet", + "num-traits", +] + [[package]] name = "pretty-hex" version = "0.2.1" @@ -10067,7 +10208,7 @@ dependencies = [ "debug-ignore", "derive-where", "futures", - "hickory-resolver", + "hickory-resolver 0.24.4", "rand 0.9.1", "serde", "thiserror 2.0.12", @@ -10449,7 +10590,7 @@ dependencies = [ "crossterm", "fd-lock", "itertools 0.12.1", - "nu-ansi-term", + "nu-ansi-term 0.50.1", "serde", "strip-ansi-escapes", "strum", @@ -10487,10 +10628,19 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + [[package]] name = "regex-automata" version = "0.4.8" @@ -11177,6 +11327,12 @@ dependencies = [ "syn 2.0.103", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -11554,6 +11710,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.1.0" @@ -12644,6 +12809,12 @@ dependencies = [ "unicode-width 0.1.14", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "take_mut" version = "0.2.2" @@ -13320,6 +13491,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term 0.46.0", + "once_cell", + "regex", + "sharded-slab", + "smallvec 1.15.0", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -14112,6 +14313,12 @@ dependencies = [ "log", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.9.0" @@ -14521,7 +14728,7 @@ dependencies = [ "gateway-messages", "gateway-test-utils", "hex", - "hickory-resolver", + "hickory-resolver 0.25.2", "http", "http-body-util", "hubtools", @@ -14670,6 +14877,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -14679,17 +14896,41 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.59.0", + "windows-interface 0.59.1", "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.3.1", + "windows-strings 0.3.1", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", ] [[package]] @@ -14703,6 +14944,17 @@ dependencies = [ "syn 2.0.103", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "windows-interface" version = "0.59.1" @@ -14727,8 +14979,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c44a98275e31bfd112bb06ba96c8ab13c03383a3753fdddd715406a1824c7e0" dependencies = [ "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.3.1", + "windows-strings 0.3.1", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -14740,6 +15001,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 41a3dd4705..be215a6f86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -454,10 +454,10 @@ headers = "0.4.1" heck = "0.5" hex = "0.4.3" hex-literal = "0.4.1" -hickory-client = "0.24.4" -hickory-proto = "0.24.4" -hickory-resolver = "0.24.4" -hickory-server = "0.24.4" +hickory-client = "0.25.2" +hickory-proto = "0.25.2" +hickory-resolver = "0.25.2" +hickory-server = "0.25.2" highway = "1.3.0" hkdf = "0.12.4" hmac = "0.12.1" diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 171aa8ae84..bd10df199e 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -42,6 +42,7 @@ crossterm = { version = "0.28.1", features = ["event-stream", "serde"] } crypto-common = { version = "0.1.6", default-features = false, features = ["getrandom", "std"] } curve25519-dalek = { version = "4.1.3", features = ["digest", "legacy_compatibility", "rand_core"] } daft = { version = "0.1.3", features = ["derive", "newtype-uuid1", "oxnet01", "uuid1"] } +data-encoding = { version = "2.9.0" } digest = { version = "0.10.7", features = ["mac", "oid", "std"] } ecdsa = { version = "0.16.9", features = ["pem", "signing", "std", "verifying"] } ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core"] } @@ -63,12 +64,13 @@ generic-array = { version = "0.14.7", default-features = false, features = ["mor getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2.15", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } hashbrown = { version = "0.15.3" } -hickory-proto = { version = "0.24.4", features = ["text-parsing"] } +hickory-proto = { version = "0.25.2", features = ["serde", "text-parsing"] } hmac = { version = "0.12.1", default-features = false, features = ["reset"] } hyper = { version = "1.6.0", features = ["full"] } idna = { version = "1.0.3" } indexmap = { version = "2.9.0", features = ["serde"] } inout = { version = "0.1.3", default-features = false, features = ["std"] } +ipnet = { version = "2.11.0", features = ["serde"] } ipnetwork = { version = "0.21.1", features = ["schemars", "serde"] } itertools-93f6ce9d446188ac = { package = "itertools", version = "0.10.5" } lalrpop-util = { version = "0.19.12" } @@ -84,7 +86,7 @@ num-bigint-dig = { version = "0.8.4", default-features = false, features = ["i12 num-integer = { version = "0.1.46", features = ["i128"] } num-iter = { version = "0.1.45", default-features = false, features = ["i128"] } num-traits = { version = "0.2.19", features = ["i128", "libm"] } -once_cell = { version = "1.21.3" } +once_cell = { version = "1.21.3", features = ["critical-section"] } openapiv3 = { version = "2.0.0", default-features = false, features = ["skip_serializing_defaults"] } peg-runtime = { version = "0.8.5", default-features = false, features = ["std"] } pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["std"] } @@ -93,6 +95,7 @@ petgraph = { version = "0.6.5", features = ["serde-1"] } phf = { version = "0.11.2" } phf_shared = { version = "0.11.2" } pkcs8 = { version = "0.10.2", default-features = false, features = ["encryption", "pem", "std"] } +portable-atomic = { version = "1.11.0" } postgres-types = { version = "0.2.9", default-features = false, features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } ppv-lite86 = { version = "0.2.20", default-features = false, features = ["simd", "std"] } predicates = { version = "3.1.3" } @@ -123,6 +126,7 @@ string_cache = { version = "0.8.9" } strum = { version = "0.26.3", features = ["derive"] } subtle = { version = "2.6.1" } syn-f595c2ba2a3f28df = { package = "syn", version = "2.0.103", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } +thiserror = { version = "2.0.12" } time = { version = "0.3.36", features = ["formatting", "local-offset", "macros", "parsing"] } tokio = { version = "1.45.1", features = ["full", "test-util"] } tokio-postgres = { version = "0.7.13", features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } @@ -170,6 +174,7 @@ crossterm = { version = "0.28.1", features = ["event-stream", "serde"] } crypto-common = { version = "0.1.6", default-features = false, features = ["getrandom", "std"] } curve25519-dalek = { version = "4.1.3", features = ["digest", "legacy_compatibility", "rand_core"] } daft = { version = "0.1.3", features = ["derive", "newtype-uuid1", "oxnet01", "uuid1"] } +data-encoding = { version = "2.9.0" } digest = { version = "0.10.7", features = ["mac", "oid", "std"] } ecdsa = { version = "0.16.9", features = ["pem", "signing", "std", "verifying"] } ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core"] } @@ -191,12 +196,13 @@ generic-array = { version = "0.14.7", default-features = false, features = ["mor getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2.15", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } hashbrown = { version = "0.15.3" } -hickory-proto = { version = "0.24.4", features = ["text-parsing"] } +hickory-proto = { version = "0.25.2", features = ["serde", "text-parsing"] } hmac = { version = "0.12.1", default-features = false, features = ["reset"] } hyper = { version = "1.6.0", features = ["full"] } idna = { version = "1.0.3" } indexmap = { version = "2.9.0", features = ["serde"] } inout = { version = "0.1.3", default-features = false, features = ["std"] } +ipnet = { version = "2.11.0", features = ["serde"] } ipnetwork = { version = "0.21.1", features = ["schemars", "serde"] } itertools-93f6ce9d446188ac = { package = "itertools", version = "0.10.5" } lalrpop-util = { version = "0.19.12" } @@ -212,7 +218,7 @@ num-bigint-dig = { version = "0.8.4", default-features = false, features = ["i12 num-integer = { version = "0.1.46", features = ["i128"] } num-iter = { version = "0.1.45", default-features = false, features = ["i128"] } num-traits = { version = "0.2.19", features = ["i128", "libm"] } -once_cell = { version = "1.21.3" } +once_cell = { version = "1.21.3", features = ["critical-section"] } openapiv3 = { version = "2.0.0", default-features = false, features = ["skip_serializing_defaults"] } peg-runtime = { version = "0.8.5", default-features = false, features = ["std"] } pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["std"] } @@ -221,6 +227,7 @@ petgraph = { version = "0.6.5", features = ["serde-1"] } phf = { version = "0.11.2" } phf_shared = { version = "0.11.2" } pkcs8 = { version = "0.10.2", default-features = false, features = ["encryption", "pem", "std"] } +portable-atomic = { version = "1.11.0" } postgres-types = { version = "0.2.9", default-features = false, features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } ppv-lite86 = { version = "0.2.20", default-features = false, features = ["simd", "std"] } predicates = { version = "3.1.3" } @@ -252,6 +259,7 @@ strum = { version = "0.26.3", features = ["derive"] } subtle = { version = "2.6.1" } syn-dff4ba8e3ae991db = { package = "syn", version = "1.0.109", features = ["extra-traits", "fold", "full", "visit"] } syn-f595c2ba2a3f28df = { package = "syn", version = "2.0.103", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } +thiserror = { version = "2.0.12" } time = { version = "0.3.36", features = ["formatting", "local-offset", "macros", "parsing"] } time-macros = { version = "0.2.18", default-features = false, features = ["formatting", "parsing"] } tokio = { version = "1.45.1", features = ["full", "test-util"] } From ebd5d7e4084baa88a7bdd8cc3c8645e7fa86db66 Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Fri, 20 Jun 2025 12:29:02 -0700 Subject: [PATCH 2/5] modernize --- Cargo.lock | 1 + clients/oxide-client/src/lib.rs | 34 +-- dns-server/src/dns_server.rs | 232 +++++++++--------- dns-server/src/lib.rs | 27 +- dns-server/src/storage.rs | 38 +-- dns-server/tests/basic_test.rs | 82 ++++--- end-to-end-tests/src/helpers/ctx.rs | 16 +- internal-dns/resolver/Cargo.toml | 1 + internal-dns/resolver/src/resolver.rs | 73 +++--- .../background/tasks/inventory_collection.rs | 19 +- nexus/src/app/external_dns.rs | 30 ++- nexus/test-utils/src/lib.rs | 25 +- nexus/tests/integration_tests/silos.rs | 14 +- wicketd/src/preflight_check/uplink.rs | 143 +++++------ 14 files changed, 389 insertions(+), 346 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb33c97cb5..199345b5bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5071,6 +5071,7 @@ dependencies = [ "dropshot", "expectorate", "futures", + "hickory-proto 0.25.2", "hickory-resolver 0.25.2", "internal-dns-types", "omicron-common", diff --git a/clients/oxide-client/src/lib.rs b/clients/oxide-client/src/lib.rs index f02581faaf..b93483eeb4 100644 --- a/clients/oxide-client/src/lib.rs +++ b/clients/oxide-client/src/lib.rs @@ -7,10 +7,11 @@ use anyhow::Context; use anyhow::anyhow; use futures::FutureExt; -use hickory_resolver::TokioAsyncResolver; +use hickory_resolver::TokioResolver; use hickory_resolver::config::{ - NameServerConfig, Protocol, ResolverConfig, ResolverOpts, + NameServerConfig, ResolverConfig, ResolverOpts, }; +use hickory_resolver::name_server::TokioConnectionProvider; use std::net::SocketAddr; use std::sync::Arc; use thiserror::Error; @@ -29,12 +30,12 @@ progenitor::generate_api!( /// for Nexus. This is often useful when trying to connect with Nexus using /// TLS, since you need to come in via the DNS name to do that. /// -/// This is a thin wrapper around `TokioAsyncResolver` +/// This is a thin wrapper around `TokioResolver` pub struct CustomDnsResolver { dns_addr: SocketAddr, // The lifetime constraints on the `Resolve` trait make it hard to avoid an // Arc here. - resolver: Arc, + resolver: Arc, } impl CustomDnsResolver { @@ -42,19 +43,22 @@ impl CustomDnsResolver { /// address pub fn new(dns_addr: SocketAddr) -> anyhow::Result { let mut resolver_config = ResolverConfig::new(); - resolver_config.add_name_server(NameServerConfig { - socket_addr: dns_addr, - protocol: Protocol::Udp, - tls_dns_name: None, - trust_negative_responses: false, - bind_addr: None, - }); + resolver_config.add_name_server(NameServerConfig::new( + dns_addr, + hickory_resolver::proto::xfer::Protocol::Udp, + )); let mut resolver_opts = ResolverOpts::default(); // Enable edns for potentially larger records resolver_opts.edns0 = true; - let resolver = - Arc::new(TokioAsyncResolver::tokio(resolver_config, resolver_opts)); + let resolver = Arc::new( + TokioResolver::builder_with_config( + resolver_config, + TokioConnectionProvider::default(), + ) + .with_options(resolver_opts) + .build(), + ); Ok(CustomDnsResolver { dns_addr, resolver }) } @@ -63,8 +67,8 @@ impl CustomDnsResolver { self.dns_addr } - /// Returns the underlying `TokioAsyncResolver - pub fn resolver(&self) -> &TokioAsyncResolver { + /// Returns the underlying `TokioResolver + pub fn resolver(&self) -> &TokioResolver { &self.resolver } } diff --git a/dns-server/src/dns_server.rs b/dns-server/src/dns_server.rs index 22579c02a9..1841667eaf 100644 --- a/dns-server/src/dns_server.rs +++ b/dns-server/src/dns_server.rs @@ -17,6 +17,7 @@ use hickory_proto::op::ResponseCode; use hickory_proto::rr::RData; use hickory_proto::rr::Record; use hickory_proto::rr::RecordType; +use hickory_proto::rr::rdata::NS; use hickory_proto::rr::rdata::SRV; use hickory_proto::serialize::binary::BinDecodable; use hickory_proto::serialize::binary::BinDecoder; @@ -224,19 +225,11 @@ fn dns_record_to_record( ) -> Result { match record { DnsRecord::A(addr) => { - let mut a = Record::new(); - a.set_name(name.clone()) - .set_rr_type(RecordType::A) - .set_data(Some(RData::A((*addr).into()))); - Ok(a) + Ok(Record::from_rdata(name.clone(), 0, RData::A((*addr).into()))) } DnsRecord::Aaaa(addr) => { - let mut aaaa = Record::new(); - aaaa.set_name(name.clone()) - .set_rr_type(RecordType::AAAA) - .set_data(Some(RData::AAAA((*addr).into()))); - Ok(aaaa) + Ok(Record::from_rdata(name.clone(), 0, RData::AAAA((*addr).into()))) } DnsRecord::Srv(Srv { prio, weight, port, target }) => { @@ -247,11 +240,11 @@ fn dns_record_to_record( error )) })?; - let mut srv = Record::new(); - srv.set_name(name.clone()).set_rr_type(RecordType::SRV).set_data( - Some(RData::SRV(SRV::new(*prio, *weight, *port, tgt))), - ); - Ok(srv) + Ok(Record::from_rdata( + name.clone(), + 0, + RData::SRV(SRV::new(*prio, *weight, *port, tgt)), + )) } DnsRecord::Ns(nsdname) => { @@ -262,12 +255,7 @@ fn dns_record_to_record( error )) })?; - let mut ns = Record::new(); - use hickory_proto::rr::rdata::NS; - ns.set_name(name.clone()) - .set_rr_type(RecordType::NS) - .set_data(Some(RData::NS(NS(nsdname)))); - Ok(ns) + Ok(Record::from_rdata(name.clone(), 0, RData::NS(NS(nsdname)))) } } } @@ -287,118 +275,126 @@ async fn handle_dns_message( // have to decide if the error is authoritative. header.set_authoritative(true); - let query = mr.query(); - let name = query.original().name().clone(); - let answer = store.query(mr)?; - let rb = MessageResponseBuilder::from_message_request(mr); let mut additional_records = vec![]; - let mut name_records = answer - .records - .as_ref() - .unwrap_or(&Vec::new()) - .iter() - .map(|record| dns_record_to_record(&name, record)) - .collect::, _>>()?; - - if answer.name.is_none() && query.query_type() == RecordType::SOA { - // The query was for an SOA record at the apex. There isn't an SOA - // record in the database, but we can build one from the answer. - name_records.push(store.soa_for(&answer)?); - } - - // If there were no records for the name at all, the name simply is not - // known to us. Bail now to return NXDomain. - // - // If there are no records after filtering, the name is known, just not with - // any records. Returning NXDomain later on would be incorrect. - if name_records.is_empty() { - return Err(RequestError::NxDomain(answer.queried_fqdn())); - } - - let response_records = name_records + let response_records = mr + .queries() .into_iter() - .filter(|record| { - let ty = query.query_type(); - if ty == RecordType::ANY { - return true; + .map(|query| { + let name = query.original().name().clone(); + let answer = store.query(query)?; + + let mut name_records = answer + .records + .as_ref() + .unwrap_or(&Vec::new()) + .iter() + .map(|record| dns_record_to_record(&name, record)) + .collect::, _>>()?; + + if answer.name.is_none() && query.query_type() == RecordType::SOA { + // The query was for an SOA record at the apex. There isn't an SOA + // record in the database, but we can build one from the answer. + name_records.push(store.soa_for(&answer)?); } - match (ty, record.data()) { - (RecordType::A, Some(RData::A(_))) => true, - (RecordType::AAAA, Some(RData::AAAA(_))) => true, - (RecordType::SRV, Some(RData::SRV(_))) => true, - (RecordType::NS, Some(RData::NS(_))) => true, - (RecordType::SOA, Some(RData::SOA(_))) => true, - _ => false, - } - }) - .map(|record| { - // DNS allows for the server to return additional records that - // weren't explicitly asked for by the client but that the server - // expects the client will want. SRV and NS records both use names - // for their referents (rather than IP addresses dierctly). If - // someone has queried for one of those kinds of records, they'll - // almost certainly be needing the IP addresses that go with them as - // well. We opportunistically attempt to resolve the target here and - // if successful return those additional records in the response. + // If there were no records for the name at all, the name simply is not + // known to us. Bail now to return NXDomain. // - // NOTE: we only do this one-layer deep. If the target of a SRV or - // NS is a CNAME instead of A/AAAA directly, it will be lost here. - let additionals_target = match record.data() { - Some(RData::SRV(srv)) => Some(srv.target()), - Some(RData::NS(ns)) => Some(&ns.0), - _ => None, - }; - - if let Some(target) = additionals_target { - let target_records = store.query_name(target).map(|answer| { - answer - .records - .unwrap_or(Vec::new()) - .into_iter() - .map(|record| dns_record_to_record(target, &record)) - .collect::, _>>() - }); - match target_records { - Ok(Ok(target_records)) => { - additional_records.extend(target_records); - } - // Don't bail out if we failed to lookup or - // handle the response as the original request - // did succeed and we only care to do this on - // a best-effort basis. - Err(error) => { - slog::warn!( - &log, - "additional records lookup failed"; - "original_mr" => #?mr, - "target" => ?target, - "error" => ?error, - ); - } - Ok(Err(error)) => { - slog::warn!( - &log, - "additional records unexpected response"; - "original_mr" => #?mr, - "target" => ?target, - "error" => ?error, - ); - } - } + // If there are no records after filtering, the name is known, just not with + // any records. Returning NXDomain later on would be incorrect. + if name_records.is_empty() { + return Err(RequestError::NxDomain(answer.queried_fqdn())); } - Ok(record) + let records = name_records + .into_iter() + .filter(|record| match (query.query_type(), record.data()) { + (RecordType::ANY, _) => true, + (RecordType::A, RData::A(_)) => true, + (RecordType::AAAA, RData::AAAA(_)) => true, + (RecordType::SRV, RData::SRV(_)) => true, + (RecordType::NS, RData::NS(_)) => true, + (RecordType::SOA, RData::SOA(_)) => true, + _ => false, + }) + .map(|record| { + // DNS allows for the server to return additional records that + // weren't explicitly asked for by the client but that the server + // expects the client will want. SRV and NS records both use names + // for their referents (rather than IP addresses directly). If + // someone has queried for one of those kinds of records, they'll + // almost certainly be needing the IP addresses that go with them as + // well. We opportunistically attempt to resolve the target here and + // if successful return those additional records in the response. + // + // NOTE: we only do this one-layer deep. If the target of a SRV or + // NS is a CNAME instead of A/AAAA directly, it will be lost here. + let additionals_target = match record.data() { + RData::SRV(srv) => Some(srv.target()), + RData::NS(ns) => Some(&ns.0), + _ => None, + }; + + if let Some(target) = additionals_target { + let target_records = + store.query_name(target).map(|answer| { + answer + .records + .unwrap_or(Vec::new()) + .into_iter() + .map(|record| { + dns_record_to_record(target, &record) + }) + .collect::, _>>() + }); + match target_records { + Ok(Ok(target_records)) => { + additional_records.extend(target_records); + } + // Don't bail out if we failed to lookup or + // handle the response as the original request + // did succeed and we only care to do this on + // a best-effort basis. + Err(error) => { + slog::warn!( + &log, + "additional records lookup failed"; + "original_mr" => #?mr, + "target" => ?target, + "error" => ?error, + ); + } + Ok(Err(error)) => { + slog::warn!( + &log, + "additional records unexpected response"; + "original_mr" => #?mr, + "target" => ?target, + "error" => ?error, + ); + } + } + } + Ok(record) + }) + .collect::, RequestError>>()?; + + Ok(records) }) - .collect::, RequestError>>()?; + .collect::, RequestError>>()? + .into_iter() + .flatten() + .collect::>(); debug!( &log, "dns response"; - "query" => ?query, + "queries" => ?mr.queries(), "records" => ?&response_records, "additional_records" => ?&additional_records, ); + + let rb = MessageResponseBuilder::from_message_request(mr); respond_records(request, rb, header, &response_records, &additional_records) .await } diff --git a/dns-server/src/lib.rs b/dns-server/src/lib.rs index 0a99c88793..f804207a17 100644 --- a/dns-server/src/lib.rs +++ b/dns-server/src/lib.rs @@ -56,11 +56,11 @@ pub mod http_server; pub mod storage; use anyhow::{Context, anyhow}; -use hickory_resolver::TokioAsyncResolver; +use hickory_resolver::TokioResolver; use hickory_resolver::config::NameServerConfig; -use hickory_resolver::config::Protocol; use hickory_resolver::config::ResolverConfig; use hickory_resolver::config::ResolverOpts; +use hickory_resolver::name_server::TokioConnectionProvider; use internal_dns_types::config::DnsConfigParams; use slog::o; use std::net::SocketAddr; @@ -179,20 +179,23 @@ impl TransientServer { Ok(()) } - pub async fn resolver(&self) -> Result { + pub async fn resolver(&self) -> Result { let mut resolver_config = ResolverConfig::new(); - resolver_config.add_name_server(NameServerConfig { - socket_addr: self.dns_server.local_address(), - protocol: Protocol::Udp, - tls_dns_name: None, - trust_negative_responses: false, - bind_addr: None, - }); + resolver_config.add_name_server(NameServerConfig::new( + self.dns_server.local_address(), + hickory_proto::xfer::Protocol::Udp, + )); let mut resolver_opts = ResolverOpts::default(); // Enable edns for potentially larger records resolver_opts.edns0 = true; - let resolver = - TokioAsyncResolver::tokio(resolver_config, resolver_opts); + + let resolver = TokioResolver::builder_with_config( + resolver_config, + TokioConnectionProvider::default(), + ) + .with_options(resolver_opts) + .build(); + Ok(resolver) } } diff --git a/dns-server/src/storage.rs b/dns-server/src/storage.rs index 41ff1c8094..3b0d8c6829 100644 --- a/dns-server/src/storage.rs +++ b/dns-server/src/storage.rs @@ -94,7 +94,7 @@ use anyhow::{Context, anyhow}; use camino::Utf8PathBuf; -use hickory_proto::rr::LowerName; +use hickory_proto::{op::LowerQuery, rr::LowerName}; use hickory_resolver::Name; use internal_dns_types::{ config::{DnsConfig, DnsConfigParams, DnsConfigZone, DnsRecord}, @@ -439,23 +439,23 @@ impl Store { }) .map(name_from_str)??; - let mut record = hickory_proto::rr::Record::new(); let soa_name = name_from_str(&answer.queried_fqdn())?; let rname = name_from_str(format!("admin.{}", answer.zone.as_str()))?; - record - .set_name(soa_name) - .set_rr_type(hickory_proto::rr::RecordType::SOA) - .set_data(Some(hickory_proto::rr::RData::SOA( - hickory_proto::rr::rdata::SOA::new( - preferred_nameserver, - rname, - answer.serial, - 3600, - 600, - 1800, - 600, - ), - ))); + + let record = hickory_proto::rr::Record::from_rdata( + soa_name, + 0, + hickory_proto::rr::RData::SOA(hickory_proto::rr::rdata::SOA::new( + preferred_nameserver, + rname, + answer.serial, + 3600, + 600, + 1800, + 600, + )), + ); + Ok(record) } @@ -759,10 +759,10 @@ impl Store { /// If the name does not match any zone, returns `QueryError::NoZone`. pub(crate) fn query( &self, - mr: &hickory_server::authority::MessageRequest, + query: &LowerQuery, ) -> Result { - let name = mr.query().name(); - let orig_name = mr.query().original().name(); + let name = query.name(); + let orig_name = query.original().name(); self.query_raw(name, orig_name) } diff --git a/dns-server/tests/basic_test.rs b/dns-server/tests/basic_test.rs index 9667c54b4e..62aa73b1b1 100644 --- a/dns-server/tests/basic_test.rs +++ b/dns-server/tests/basic_test.rs @@ -6,17 +6,18 @@ use anyhow::{Context, Result}; use camino_tempfile::Utf8TempDir; use dns_service_client::Client; use dropshot::{HandlerTaskMode, test_util::LogContext}; -use hickory_client::{ - client::{AsyncClient, ClientHandle}, - error::ClientError, - rr::RData, - udp::UdpClientStream, -}; -use hickory_resolver::TokioAsyncResolver; -use hickory_resolver::error::ResolveErrorKind; +use hickory_client::client::Client as HickoryClient; +use hickory_client::{ClientError, client::ClientHandle}; +use hickory_proto::rr::RData; +use hickory_proto::runtime::TokioRuntimeProvider; +use hickory_proto::udp::UdpClientStream; +use hickory_proto::xfer::Protocol; +use hickory_resolver::ResolveErrorKind; +use hickory_resolver::TokioResolver; +use hickory_resolver::name_server::TokioConnectionProvider; use hickory_resolver::{ - config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts}, - error::ResolveError, + ResolveError, + config::{NameServerConfig, ResolverConfig, ResolverOpts}, proto::{ op::ResponseCode, rr::{DNSClass, Name, RecordType, rdata::AAAA}, @@ -240,7 +241,7 @@ pub async fn srv_crud() -> Result<(), anyhow::Error> { // that the additional records really do come back in the "Additionals" // section of the response. - let name = hickory_client::rr::domain::Name::from_ascii(&test_fqdn) + let name = hickory_proto::rr::domain::Name::from_ascii(&test_fqdn) .expect("can construct name for query"); let response = raw_dns_client_query( @@ -299,7 +300,7 @@ pub async fn multi_record_crud() -> Result<(), anyhow::Error> { async fn lookup_ip_expect_error_code( server_addr: std::net::SocketAddr, - resolver: &TokioAsyncResolver, + resolver: &TokioResolver, name: &str, expected_code: ResponseCode, ) { @@ -363,31 +364,30 @@ async fn raw_query_expect_err( raw_response } +#[track_caller] fn expect_no_records_error_code( err: &ResolveError, expected_code: ResponseCode, ) { - match err.kind() { - ResolveErrorKind::NoRecordsFound { + if let ResolveErrorKind::Proto(proto_err) = err.kind() { + if let hickory_proto::ProtoErrorKind::NoRecordsFound { response_code, - query: _, - soa: _, - negative_ttl: _, - trusted: _, - } => { + .. + } = proto_err.kind() + { if response_code == &expected_code { // Error matches on all the conditions we're checking. No // issues. - } else { - panic!( - "Expected {expected_code}, got response code {response_code:?}" - ); + return; } - } - unexpected => { - panic!("Expected {expected_code}, got error {unexpected:?}"); + + panic!( + "Expected {expected_code}, got response code {response_code:?}" + ); } } + let unexpected = err.kind(); + panic!("Expected {expected_code}, got error {unexpected:?}"); } // Verify that the part of a name that's under the zone name can contain the @@ -519,7 +519,7 @@ pub async fn soa() -> Result<(), anyhow::Error> { let zone_ns_answer = resolver.ns_lookup(TEST_ZONE).await?; let has_ns_record = zone_ns_answer.as_lookup().records().iter().any(|record| { - if let Some(RData::NS(nsdname)) = record.data() { + if let RData::NS(nsdname) = record.data() { nsdname.0.to_utf8().as_str() == &ns1_name } else { false @@ -530,7 +530,7 @@ pub async fn soa() -> Result<(), anyhow::Error> { // The nameserver's AAAA record should be in additionals. let has_aaaa_additional = zone_ns_answer.as_lookup().records().iter().any(|record| { - if let Some(RData::AAAA(AAAA(addr))) = record.data() { + if let RData::AAAA(AAAA(addr)) = record.data() { addr == &ns1_addr } else { false @@ -639,7 +639,7 @@ pub async fn servfail() -> Result<(), anyhow::Error> { struct TestContext { client: Client, - resolver: TokioAsyncResolver, + resolver: TokioResolver, dns_server: dns_server::dns_server::ServerHandle, dropshot_server: dropshot::HttpServer, tmp: Utf8TempDir, @@ -684,18 +684,20 @@ async fn init_client_server( .await?; let mut resolver_config = ResolverConfig::new(); - resolver_config.add_name_server(NameServerConfig { - socket_addr: dns_server.local_address(), - protocol: Protocol::Udp, - tls_dns_name: None, - trust_negative_responses: false, - bind_addr: None, - }); + resolver_config.add_name_server(NameServerConfig::new( + dns_server.local_address(), + Protocol::Udp, + )); let mut resolver_opts = ResolverOpts::default(); // Enable edns for potentially larger records resolver_opts.edns0 = true; - let resolver = TokioAsyncResolver::tokio(resolver_config, resolver_opts); + let resolver = TokioResolver::builder_with_config( + resolver_config, + TokioConnectionProvider::default(), + ) + .with_options(resolver_opts) + .build(); let client = Client::new(&format!("http://{}", dropshot_server.local_addr()), log); @@ -805,8 +807,10 @@ async fn raw_dns_client_query( name: Name, record_ty: RecordType, ) -> Result { - let stream = UdpClientStream::::new(resolver_addr); - let (mut trust_client, bg) = AsyncClient::connect(stream).await.unwrap(); + let stream = + UdpClientStream::builder(resolver_addr, TokioRuntimeProvider::new()) + .build(); + let (mut trust_client, bg) = HickoryClient::connect(stream).await.unwrap(); tokio::spawn(bg); diff --git a/end-to-end-tests/src/helpers/ctx.rs b/end-to-end-tests/src/helpers/ctx.rs index 09cd83c149..680b9e886c 100644 --- a/end-to-end-tests/src/helpers/ctx.rs +++ b/end-to-end-tests/src/helpers/ctx.rs @@ -1,7 +1,8 @@ use crate::helpers::generate_name; use anyhow::{Context as _, Result, anyhow}; use chrono::Utc; -use hickory_resolver::error::ResolveErrorKind; +use hickory_resolver::ResolveErrorKind; +use hickory_resolver::proto::ProtoErrorKind; use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition}; use oxide_client::CustomDnsResolver; use oxide_client::types::{Name, ProjectCreate}; @@ -305,9 +306,9 @@ async fn wait_for_records( .resolver() .lookup_ip(dns_name) .await - .map_err(|e| match e.kind() { - ResolveErrorKind::NoRecordsFound { .. } - | ResolveErrorKind::Timeout => CondCheckError::NotYet, + .map_err(|e| match resolve_error_proto_kind(&e) { + Some(ProtoErrorKind::NoRecordsFound { .. }) + | Some(ProtoErrorKind::Timeout) => CondCheckError::NotYet, _ => CondCheckError::Failed(anyhow::Error::new(e).context( format!( "resolving {:?} from {}", @@ -333,3 +334,10 @@ async fn wait_for_records( ) }) } + +fn resolve_error_proto_kind( + e: &hickory_resolver::ResolveError, +) -> Option<&ProtoErrorKind> { + let ResolveErrorKind::Proto(proto_error) = e.kind() else { return None }; + Some(proto_error.kind()) +} diff --git a/internal-dns/resolver/Cargo.toml b/internal-dns/resolver/Cargo.toml index e4de05d5c9..f9d9d5c0ab 100644 --- a/internal-dns/resolver/Cargo.toml +++ b/internal-dns/resolver/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] futures.workspace = true hickory-resolver.workspace = true +hickory-proto.workspace = true internal-dns-types.workspace = true omicron-common.workspace = true omicron-workspace-hack.workspace = true diff --git a/internal-dns/resolver/src/resolver.rs b/internal-dns/resolver/src/resolver.rs index 8f6b46503a..6766705578 100644 --- a/internal-dns/resolver/src/resolver.rs +++ b/internal-dns/resolver/src/resolver.rs @@ -2,11 +2,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use hickory_resolver::TokioAsyncResolver; +use hickory_resolver::TokioResolver; use hickory_resolver::config::{ - LookupIpStrategy, NameServerConfig, Protocol, ResolverConfig, ResolverOpts, + LookupIpStrategy, NameServerConfig, ResolveHosts, ResolverConfig, + ResolverOpts, }; use hickory_resolver::lookup::SrvLookup; +use hickory_resolver::name_server::TokioConnectionProvider; use internal_dns_types::names::ServiceName; use omicron_common::address::{ AZ_PREFIX, DNS_PORT, Ipv6Subnet, get_internal_dns_server_addresses, @@ -17,7 +19,7 @@ use std::net::{Ipv6Addr, SocketAddr, SocketAddrV6}; #[derive(Debug, Clone, thiserror::Error)] pub enum ResolveError { #[error(transparent)] - Resolve(#[from] hickory_resolver::error::ResolveError), + Resolve(#[from] hickory_resolver::ResolveError), #[error("Record not found for SRV key: {}", .0.dns_name())] NotFound(ServiceName), @@ -71,7 +73,7 @@ impl QorbResolver { #[derive(Clone)] pub struct Resolver { log: slog::Logger, - resolver: TokioAsyncResolver, + resolver: TokioResolver, } type BoxError = Box; @@ -94,11 +96,11 @@ impl Resolver { pub fn new_from_system_conf( log: slog::Logger, ) -> Result { - let (rc, mut opts) = hickory_resolver::system_conf::read_system_conf()?; + let mut builder = TokioResolver::builder_tokio()?; // Enable edns for potentially larger records - opts.edns0 = true; + builder.options_mut().edns0 = true; - let resolver = TokioAsyncResolver::tokio(rc, opts); + let resolver = builder.build(); Ok(Self { log, resolver }) } @@ -113,24 +115,26 @@ impl Resolver { let mut rc = ResolverConfig::new(); let dns_server_count = dns_addrs.len(); for &socket_addr in dns_addrs.into_iter() { - rc.add_name_server(NameServerConfig { + rc.add_name_server(NameServerConfig::new( socket_addr, - protocol: Protocol::Udp, - tls_dns_name: None, - trust_negative_responses: false, - bind_addr: None, - }); + hickory_resolver::proto::xfer::Protocol::Udp, + )); } let mut opts = ResolverOpts::default(); // Enable edns for potentially larger records opts.edns0 = true; - opts.use_hosts_file = false; + opts.use_hosts_file = ResolveHosts::Never; opts.num_concurrent_reqs = dns_server_count; // The underlay is IPv6 only, so this helps avoid needless lookups of // the IPv4 variant. opts.ip_strategy = LookupIpStrategy::Ipv6Only; opts.negative_max_ttl = Some(std::time::Duration::from_secs(15)); - let resolver = TokioAsyncResolver::tokio(rc, opts); + let resolver = TokioResolver::builder_with_config( + rc, + TokioConnectionProvider::default(), + ) + .with_options(opts) + .build(); Ok(Self { log, resolver }) } @@ -150,7 +154,7 @@ impl Resolver { /// /etc/resolv.conf) for the underlying nameservers. pub fn new_with_resolver( log: slog::Logger, - resolver: TokioAsyncResolver, + resolver: TokioResolver, ) -> Self { Self { log, resolver } } @@ -413,11 +417,11 @@ mod test { use super::ResolveError; use super::Resolver; use anyhow::Context; - use assert_matches::assert_matches; use dropshot::{ ApiDescription, HandlerTaskMode, HttpError, HttpResponseOk, RequestContext, endpoint, }; + use hickory_resolver::ResolveErrorKind; use internal_dns_types::config::DnsConfigBuilder; use internal_dns_types::config::DnsConfigParams; use internal_dns_types::names::DNS_ZONE; @@ -545,19 +549,27 @@ mod test { .await .expect_err("Looking up non-existent service should fail"); + expect_no_records(&err); + + dns_server.cleanup_successful(); + logctx.cleanup_successful(); + } + + #[track_caller] + fn expect_no_records(err: &ResolveError) { let dns_error = match err { ResolveError::Resolve(err) => err, _ => panic!("Unexpected error: {err}"), }; - assert!( - matches!( - dns_error.kind(), - hickory_resolver::error::ResolveErrorKind::NoRecordsFound { .. }, - ), - "Saw error: {dns_error}", - ); - dns_server.cleanup_successful(); - logctx.cleanup_successful(); + + if let ResolveErrorKind::Proto(proto_err) = dns_error.kind() { + if let hickory_proto::ProtoErrorKind::NoRecordsFound { .. } = + proto_err.kind() + { + return; + } + } + panic!("Saw error: {dns_error}"); } // Insert and retreive a single DNS record. @@ -703,13 +715,8 @@ mod test { .lookup_socket_v6(ServiceName::Cockroach) .await .expect_err("unexpectedly found records"); - assert_matches!( - error, - ResolveError::Resolve(error) - if matches!(error.kind(), - hickory_resolver::error::ResolveErrorKind::NoRecordsFound { .. } - ) - ); + + expect_no_records(&error); // If we remove the zone altogether, we'll get a different resolution // error because the DNS server is no longer authoritative for this diff --git a/nexus/src/app/background/tasks/inventory_collection.rs b/nexus/src/app/background/tasks/inventory_collection.rs index d191614804..f3033513f1 100644 --- a/nexus/src/app/background/tasks/inventory_collection.rs +++ b/nexus/src/app/background/tasks/inventory_collection.rs @@ -169,10 +169,7 @@ async fn inventory_activate( vec![] } ResolveError::Resolve(hickory_err) - if matches!( - hickory_err.kind(), - hickory_resolver::error::ResolveErrorKind::NoRecordsFound { .. } - ) => + if is_no_records_found(&hickory_err) => { vec![] } @@ -206,6 +203,20 @@ async fn inventory_activate( Ok(collection) } +fn is_no_records_found(err: &hickory_resolver::ResolveError) -> bool { + match err.kind() { + hickory_resolver::ResolveErrorKind::Proto(proto_error) => { + match proto_error.kind() { + hickory_resolver::proto::ProtoErrorKind::NoRecordsFound { + .. + } => true, + _ => false, + } + } + _ => false, + } +} + /// Determine which sleds to inventory based on what's in the database /// /// We only want to inventory what's actually part of the control plane (i.e., diff --git a/nexus/src/app/external_dns.rs b/nexus/src/app/external_dns.rs index 22e78664db..f2a26728b5 100644 --- a/nexus/src/app/external_dns.rs +++ b/nexus/src/app/external_dns.rs @@ -5,38 +5,44 @@ use std::net::IpAddr; use std::net::SocketAddr; -use hickory_resolver::TokioAsyncResolver; +use hickory_resolver::TokioResolver; use hickory_resolver::config::NameServerConfig; -use hickory_resolver::config::Protocol; +use hickory_resolver::config::ResolveHosts; use hickory_resolver::config::ResolverConfig; use hickory_resolver::config::ResolverOpts; +use hickory_resolver::name_server::TokioConnectionProvider; +use hickory_resolver::proto::xfer::Protocol; use omicron_common::address::DNS_PORT; use reqwest::dns::Name; /// Wrapper around hickory-resolver to provide name resolution /// using a given set of DNS servers for use with reqwest. -pub struct Resolver(TokioAsyncResolver); +pub struct Resolver(TokioResolver); impl Resolver { pub fn new(dns_servers: &[IpAddr]) -> Resolver { assert!(!dns_servers.is_empty()); let mut rc = ResolverConfig::new(); for addr in dns_servers { - rc.add_name_server(NameServerConfig { - socket_addr: SocketAddr::new(*addr, DNS_PORT), - protocol: Protocol::Udp, - tls_dns_name: None, - trust_negative_responses: false, - bind_addr: None, - }); + rc.add_name_server(NameServerConfig::new( + SocketAddr::new(*addr, DNS_PORT), + Protocol::Udp, + )); } let mut opts = ResolverOpts::default(); // Enable edns for potentially larger records opts.edns0 = true; - opts.use_hosts_file = false; + opts.use_hosts_file = ResolveHosts::Never; // Do as many requests in parallel as we have configured servers opts.num_concurrent_reqs = dns_servers.len(); - Resolver(TokioAsyncResolver::tokio(rc, opts)) + Resolver( + TokioResolver::builder_with_config( + rc, + TokioConnectionProvider::default(), + ) + .with_options(opts) + .build(), + ) } } diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index 1ddbd89830..f7cdac28b4 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -17,11 +17,12 @@ use dropshot::test_util::LogContext; use futures::FutureExt; use futures::future::BoxFuture; use gateway_test_utils::setup::GatewayTestContext; -use hickory_resolver::TokioAsyncResolver; +use hickory_resolver::TokioResolver; use hickory_resolver::config::NameServerConfig; -use hickory_resolver::config::Protocol; use hickory_resolver::config::ResolverConfig; use hickory_resolver::config::ResolverOpts; +use hickory_resolver::name_server::TokioConnectionProvider; +use hickory_resolver::proto::xfer::Protocol; use id_map::IdMap; use internal_dns_types::config::DnsConfigBuilder; use internal_dns_types::names::DNS_ZONE_EXTERNAL_TESTING; @@ -1957,7 +1958,7 @@ pub async fn start_dns_server( ( dns_server::dns_server::ServerHandle, dropshot::HttpServer, - TokioAsyncResolver, + TokioResolver, ), anyhow::Error, > { @@ -1988,16 +1989,18 @@ pub async fn start_dns_server( .unwrap(); let mut resolver_config = ResolverConfig::new(); - resolver_config.add_name_server(NameServerConfig { - socket_addr: dns_server.local_address(), - protocol: Protocol::Udp, - tls_dns_name: None, - trust_negative_responses: false, - bind_addr: None, - }); + resolver_config.add_name_server(NameServerConfig::new( + dns_server.local_address(), + Protocol::Udp, + )); let mut resolver_opts = ResolverOpts::default(); resolver_opts.edns0 = true; - let resolver = TokioAsyncResolver::tokio(resolver_config, resolver_opts); + let resolver = TokioResolver::builder_with_config( + resolver_config, + TokioConnectionProvider::default(), + ) + .with_options(resolver_opts) + .build(); Ok((dns_server, http_server, resolver)) } diff --git a/nexus/tests/integration_tests/silos.rs b/nexus/tests/integration_tests/silos.rs index 4a33de0ae7..0cdd73f9d0 100644 --- a/nexus/tests/integration_tests/silos.rs +++ b/nexus/tests/integration_tests/silos.rs @@ -38,7 +38,8 @@ use std::fmt::Write; use std::str::FromStr; use base64::Engine; -use hickory_resolver::error::ResolveErrorKind; +use hickory_resolver::ResolveErrorKind; +use hickory_resolver::proto::ProtoErrorKind; use http::StatusCode; use http::method::Method; use httptest::{Expectation, Server, matchers::*, responders::*}; @@ -2165,8 +2166,8 @@ pub async fn verify_silo_dns_name( true } } - Err(error) => match error.kind() { - ResolveErrorKind::NoRecordsFound { .. } => false, + Err(error) => match resolve_error_proto_kind(&error) { + Some(ProtoErrorKind::NoRecordsFound { .. }) => false, _ => panic!( "unexpected error querying external \ DNS server for Silo DNS name {:?}: {:#}", @@ -2188,6 +2189,13 @@ pub async fn verify_silo_dns_name( .expect("failed to verify external DNS configuration"); } +fn resolve_error_proto_kind( + e: &hickory_resolver::ResolveError, +) -> Option<&ProtoErrorKind> { + let ResolveErrorKind::Proto(proto_error) = e.kind() else { return None }; + Some(proto_error.kind()) +} + // Test the basic behavior of the Silo-level IAM policy that supports // configuring Silo roles to confer Fleet-level roles. Because we don't support // modifying Silos at all, we have to use separate Silos to test this behavior. diff --git a/wicketd/src/preflight_check/uplink.rs b/wicketd/src/preflight_check/uplink.rs index 1997e8ed1f..7ff4aca297 100644 --- a/wicketd/src/preflight_check/uplink.rs +++ b/wicketd/src/preflight_check/uplink.rs @@ -14,11 +14,12 @@ use dpd_client::types::PortId; use dpd_client::types::PortSettings; use dpd_client::types::PortSpeed as DpdPortSpeed; use either::Either; -use hickory_resolver::TokioAsyncResolver; +use hickory_resolver::TokioResolver; use hickory_resolver::config::NameServerConfigGroup; +use hickory_resolver::config::ResolveHosts; use hickory_resolver::config::ResolverConfig; use hickory_resolver::config::ResolverOpts; -use hickory_resolver::error::ResolveErrorKind; +use hickory_resolver::name_server::TokioConnectionProvider; use illumos_utils::PFEXEC; use illumos_utils::zone::SVCCFG; use omicron_common::OMICRON_DPD_TAG; @@ -1018,7 +1019,7 @@ impl DnsLookupStep { async fn lookup_ip( &mut self, dns_ip: IpAddr, - resolver: &TokioAsyncResolver, + resolver: &TokioResolver, name: &str, options: DnsLookupOptions, cx: &StepContext, @@ -1088,91 +1089,79 @@ impl DnsLookupStep { Err(err) => err, }; - match err.kind() { - // If `NoRecordsFound` is an acceptable end to this lookup, - // we're done. - ResolveErrorKind::NoRecordsFound { .. } - if options.is_no_records_found_okay => - { - let message = format!( - "DNS server {dns_ip} \ - {query_type} query attempt {attempt}: \ - no record found for {name}; \ - connectivity to DNS server appears good" - ); - query_results.push(message.clone()); - cx.send_progress(StepProgress::Progress { - progress: None, - metadata: message, - }) - .await; - - self.messages.append(&mut query_results); - return Ok(()); - } - + if err.is_no_records_found() && options.is_no_records_found_okay { + let message = format!( + "DNS server {dns_ip} \ + {query_type} query attempt {attempt}: \ + no record found for {name}; \ + connectivity to DNS server appears good" + ); + query_results.push(message.clone()); + cx.send_progress(StepProgress::Progress { + progress: None, + metadata: message, + }) + .await; + + self.messages.append(&mut query_results); + return Ok(()); + } else if err.is_no_records_found() { // Otherwise, `NoRecordsFound` means we should either switch to // AAAA queries (if this was A) or we're done (and failed). - ResolveErrorKind::NoRecordsFound { .. } => { - let message = format!( - "DNS server {dns_ip} \ + let message = format!( + "DNS server {dns_ip} \ {query_type} query attempt {attempt}: \ failed to look up {name}: {}", - DisplayErrorChain::new(&err) - ); - query_results.push(message.clone()); - cx.send_progress(StepProgress::Progress { - progress: None, - metadata: message, - }) - .await; - - // If this was an A query, switch to AAAA and reset the - // attempt counter; otherwise, we're done (and we failed - // to resolve the name). - if query_ipv4 { - query_ipv4 = false; - attempt = 0; - continue; - } else { - self.warnings.append(&mut query_results); - return Ok(()); - } + DisplayErrorChain::new(&err) + ); + query_results.push(message.clone()); + cx.send_progress(StepProgress::Progress { + progress: None, + metadata: message, + }) + .await; + + // If this was an A query, switch to AAAA and reset the + // attempt counter; otherwise, we're done (and we failed + // to resolve the name). + if query_ipv4 { + query_ipv4 = false; + attempt = 0; + continue; + } else { + self.warnings.append(&mut query_results); + return Ok(()); } - - // For any other error, we're done (and failed) if we've passed - // `RETRY_TIMEOUT`; otherwise, we sleep briefly and then retry. - _ => { - let message = format!( - "DNS server {dns_ip} \ + } else { + let message = format!( + "DNS server {dns_ip} \ {query_type} query attempt {attempt}: \ failed to look up {name}: {}", - DisplayErrorChain::new(&err) - ); - query_results.push(message.clone()); - cx.send_progress(StepProgress::Progress { - progress: None, - metadata: message, - }) - .await; - - if start.elapsed() < RETRY_TIMEOUT { - tokio::time::sleep(Duration::from_secs(1)).await; - continue; - } else { - self.warnings.append(&mut query_results); - return Err(StopTryingServer); - } + DisplayErrorChain::new(&err) + ); + query_results.push(message.clone()); + cx.send_progress(StepProgress::Progress { + progress: None, + metadata: message, + }) + .await; + + if start.elapsed() < RETRY_TIMEOUT { + tokio::time::sleep(Duration::from_secs(1)).await; + continue; + } else { + self.warnings.append(&mut query_results); + return Err(StopTryingServer); } } } } - /// Returns a `TokioAsyncResolver` if we're able to build one. + /// Returns a `TokioResolver` if we're able to build one. /// /// If building it fails, we'll append to our internal `warnings` and return /// `None`. - fn build_resolver(&mut self, dns_ip: IpAddr) -> TokioAsyncResolver { + fn build_resolver(&mut self, dns_ip: IpAddr) -> TokioResolver { let mut options = ResolverOpts::default(); // Enable edns for potentially larger records @@ -1187,7 +1176,7 @@ impl DnsLookupStep { options.cache_size = 0; // We only want to query the DNS server, not /etc/hosts. - options.use_hosts_file = false; + options.use_hosts_file = ResolveHosts::Never; // This is currently the default for `ResolverOpts`, but it // doesn't hurt to specify it in case that changes. @@ -1197,7 +1186,7 @@ impl DnsLookupStep { // _probably_ doesn't matter. options.num_concurrent_reqs = 1; - TokioAsyncResolver::tokio( + TokioResolver::builder_with_config( ResolverConfig::from_parts( None, vec![], @@ -1207,8 +1196,10 @@ impl DnsLookupStep { true, ), ), - options, + TokioConnectionProvider::default(), ) + .with_options(options) + .build() } } From 7560b14baf60fc0f9e65b95f855bf736ee5dd83d Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Fri, 20 Jun 2025 20:54:28 -0700 Subject: [PATCH 3/5] output --- dev-tools/omdb/tests/successes.out | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index f063e9a4db..e5f69db2a3 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -484,7 +484,7 @@ task: "nat_v4_garbage_collector" currently executing: no last completed activation: , triggered by a periodic timer firing started at (s ago) and ran for ms - last completion reported error: failed to resolve addresses for Dendrite services: no record found for Query { name: Name("_dendrite._tcp.control-plane.oxide.internal."), query_type: SRV, query_class: IN } + last completion reported error: failed to resolve addresses for Dendrite services: proto error: no records found for Query { name: Name("_dendrite._tcp.control-plane.oxide.internal."), query_type: SRV, query_class: IN } task: "blueprint_loader" configured period: every m s @@ -523,7 +523,7 @@ task: "bfd_manager" currently executing: no last completed activation: , triggered by a periodic timer firing started at (s ago) and ran for ms - last completion reported error: failed to resolve addresses for Dendrite services: no record found for Query { name: Name("_dendrite._tcp.control-plane.oxide.internal."), query_type: SRV, query_class: IN } + last completion reported error: failed to resolve addresses for Dendrite services: proto error: no records found for Query { name: Name("_dendrite._tcp.control-plane.oxide.internal."), query_type: SRV, query_class: IN } task: "blueprint_planner" configured period: every m @@ -1010,7 +1010,7 @@ task: "nat_v4_garbage_collector" currently executing: no last completed activation: , triggered by a periodic timer firing started at (s ago) and ran for ms - last completion reported error: failed to resolve addresses for Dendrite services: no record found for Query { name: Name("_dendrite._tcp.control-plane.oxide.internal."), query_type: SRV, query_class: IN } + last completion reported error: failed to resolve addresses for Dendrite services: proto error: no records found for Query { name: Name("_dendrite._tcp.control-plane.oxide.internal."), query_type: SRV, query_class: IN } task: "blueprint_loader" configured period: every m s @@ -1049,7 +1049,7 @@ task: "bfd_manager" currently executing: no last completed activation: , triggered by a periodic timer firing started at (s ago) and ran for ms - last completion reported error: failed to resolve addresses for Dendrite services: no record found for Query { name: Name("_dendrite._tcp.control-plane.oxide.internal."), query_type: SRV, query_class: IN } + last completion reported error: failed to resolve addresses for Dendrite services: proto error: no records found for Query { name: Name("_dendrite._tcp.control-plane.oxide.internal."), query_type: SRV, query_class: IN } task: "blueprint_planner" configured period: every m From 481a28eeedb4c282baaeff30f2b9206f7bee430a Mon Sep 17 00:00:00 2001 From: iximeow Date: Mon, 23 Jun 2025 19:44:53 +0000 Subject: [PATCH 4/5] further hickory tweaks --- clients/oxide-client/src/lib.rs | 2 +- dns-server/src/dns_server.rs | 217 +++++++++++++------------- internal-dns/resolver/src/resolver.rs | 13 +- nexus/src/app/external_dns.rs | 27 +++- wicketd/src/preflight_check/uplink.rs | 22 ++- 5 files changed, 157 insertions(+), 124 deletions(-) diff --git a/clients/oxide-client/src/lib.rs b/clients/oxide-client/src/lib.rs index b93483eeb4..367c2b4adf 100644 --- a/clients/oxide-client/src/lib.rs +++ b/clients/oxide-client/src/lib.rs @@ -67,7 +67,7 @@ impl CustomDnsResolver { self.dns_addr } - /// Returns the underlying `TokioResolver + /// Returns the underlying `TokioResolver` pub fn resolver(&self) -> &TokioResolver { &self.resolver } diff --git a/dns-server/src/dns_server.rs b/dns-server/src/dns_server.rs index 1841667eaf..4411b96032 100644 --- a/dns-server/src/dns_server.rs +++ b/dns-server/src/dns_server.rs @@ -275,126 +275,129 @@ async fn handle_dns_message( // have to decide if the error is authoritative. header.set_authoritative(true); + let query = match mr.queries() { + [] => { + // A request with no queries could be legitimate (such as RFC 7873's Server Cookie + // query), but we won't look enough to find out because we don't support that. + return Err(RequestError::ServFail(anyhow!( + "request with no query?" + ))); + } + [query] => query, + _ => { + // A request that is a query (opcode 0) with more than one question + // is invalid according to RFC 9619. We don't currently check that + // the opcode is actually QUERY, but even for other opcodes we don't + // support more than one question. + return Err(RequestError::ServFail(anyhow!( + "request with too many queries" + ))); + } + }; + let name = query.original().name().clone(); + let answer = store.query(query)?; + let rb = MessageResponseBuilder::from_message_request(mr); let mut additional_records = vec![]; - let response_records = mr - .queries() - .into_iter() - .map(|query| { - let name = query.original().name().clone(); - let answer = store.query(query)?; - - let mut name_records = answer - .records - .as_ref() - .unwrap_or(&Vec::new()) - .iter() - .map(|record| dns_record_to_record(&name, record)) - .collect::, _>>()?; - - if answer.name.is_none() && query.query_type() == RecordType::SOA { - // The query was for an SOA record at the apex. There isn't an SOA - // record in the database, but we can build one from the answer. - name_records.push(store.soa_for(&answer)?); - } + let mut name_records = answer + .records + .as_ref() + .unwrap_or(&Vec::new()) + .iter() + .map(|record| dns_record_to_record(&name, record)) + .collect::, _>>()?; + + if answer.name.is_none() && query.query_type() == RecordType::SOA { + // The query was for an SOA record at the apex. There isn't an SOA + // record in the database, but we can build one from the answer. + name_records.push(store.soa_for(&answer)?); + } - // If there were no records for the name at all, the name simply is not - // known to us. Bail now to return NXDomain. + // If there were no records for the name at all, the name simply is not + // known to us. Bail now to return NXDomain. + // + // If there are no records after filtering, the name is known, just not with + // any records. Returning NXDomain later on would be incorrect. + if name_records.is_empty() { + return Err(RequestError::NxDomain(answer.queried_fqdn())); + } + let response_records = name_records + .into_iter() + .filter(|record| match (query.query_type(), record.data()) { + (RecordType::ANY, _) => true, + (RecordType::A, RData::A(_)) => true, + (RecordType::AAAA, RData::AAAA(_)) => true, + (RecordType::SRV, RData::SRV(_)) => true, + (RecordType::NS, RData::NS(_)) => true, + (RecordType::SOA, RData::SOA(_)) => true, + _ => false, + }) + .map(|record| { + // DNS allows for the server to return additional records that + // weren't explicitly asked for by the client but that the server + // expects the client will want. SRV and NS records both use names + // for their referents (rather than IP addresses directly). If + // someone has queried for one of those kinds of records, they'll + // almost certainly be needing the IP addresses that go with them as + // well. We opportunistically attempt to resolve the target here and + // if successful return those additional records in the response. // - // If there are no records after filtering, the name is known, just not with - // any records. Returning NXDomain later on would be incorrect. - if name_records.is_empty() { - return Err(RequestError::NxDomain(answer.queried_fqdn())); - } - let records = name_records - .into_iter() - .filter(|record| match (query.query_type(), record.data()) { - (RecordType::ANY, _) => true, - (RecordType::A, RData::A(_)) => true, - (RecordType::AAAA, RData::AAAA(_)) => true, - (RecordType::SRV, RData::SRV(_)) => true, - (RecordType::NS, RData::NS(_)) => true, - (RecordType::SOA, RData::SOA(_)) => true, - _ => false, - }) - .map(|record| { - // DNS allows for the server to return additional records that - // weren't explicitly asked for by the client but that the server - // expects the client will want. SRV and NS records both use names - // for their referents (rather than IP addresses directly). If - // someone has queried for one of those kinds of records, they'll - // almost certainly be needing the IP addresses that go with them as - // well. We opportunistically attempt to resolve the target here and - // if successful return those additional records in the response. - // - // NOTE: we only do this one-layer deep. If the target of a SRV or - // NS is a CNAME instead of A/AAAA directly, it will be lost here. - let additionals_target = match record.data() { - RData::SRV(srv) => Some(srv.target()), - RData::NS(ns) => Some(&ns.0), - _ => None, - }; - - if let Some(target) = additionals_target { - let target_records = - store.query_name(target).map(|answer| { - answer - .records - .unwrap_or(Vec::new()) - .into_iter() - .map(|record| { - dns_record_to_record(target, &record) - }) - .collect::, _>>() - }); - match target_records { - Ok(Ok(target_records)) => { - additional_records.extend(target_records); - } - // Don't bail out if we failed to lookup or - // handle the response as the original request - // did succeed and we only care to do this on - // a best-effort basis. - Err(error) => { - slog::warn!( - &log, - "additional records lookup failed"; - "original_mr" => #?mr, - "target" => ?target, - "error" => ?error, - ); - } - Ok(Err(error)) => { - slog::warn!( - &log, - "additional records unexpected response"; - "original_mr" => #?mr, - "target" => ?target, - "error" => ?error, - ); - } - } - } - Ok(record) - }) - .collect::, RequestError>>()?; + // NOTE: we only do this one-layer deep. If the target of a SRV or + // NS is a CNAME instead of A/AAAA directly, it will be lost here. + let additionals_target = match record.data() { + RData::SRV(srv) => Some(srv.target()), + RData::NS(ns) => Some(&ns.0), + _ => None, + }; - Ok(records) + if let Some(target) = additionals_target { + let target_records = store.query_name(target).map(|answer| { + answer + .records + .unwrap_or(Vec::new()) + .into_iter() + .map(|record| dns_record_to_record(target, &record)) + .collect::, _>>() + }); + match target_records { + Ok(Ok(target_records)) => { + additional_records.extend(target_records); + } + // Don't bail out if we failed to lookup or + // handle the response as the original request + // did succeed and we only care to do this on + // a best-effort basis. + Err(error) => { + slog::warn!( + &log, + "additional records lookup failed"; + "original_mr" => #?mr, + "target" => ?target, + "error" => ?error, + ); + } + Ok(Err(error)) => { + slog::warn!( + &log, + "additional records unexpected response"; + "original_mr" => #?mr, + "target" => ?target, + "error" => ?error, + ); + } + } + } + Ok(record) }) - .collect::, RequestError>>()? - .into_iter() - .flatten() - .collect::>(); + .collect::, RequestError>>()?; debug!( &log, "dns response"; - "queries" => ?mr.queries(), + "query" => ?query, "records" => ?&response_records, "additional_records" => ?&additional_records, ); - - let rb = MessageResponseBuilder::from_message_request(mr); respond_records(request, rb, header, &response_records, &additional_records) .await } diff --git a/internal-dns/resolver/src/resolver.rs b/internal-dns/resolver/src/resolver.rs index 6766705578..e5d6179b76 100644 --- a/internal-dns/resolver/src/resolver.rs +++ b/internal-dns/resolver/src/resolver.rs @@ -115,10 +115,19 @@ impl Resolver { let mut rc = ResolverConfig::new(); let dns_server_count = dns_addrs.len(); for &socket_addr in dns_addrs.into_iter() { - rc.add_name_server(NameServerConfig::new( + let mut ns_config = NameServerConfig::new( socket_addr, hickory_resolver::proto::xfer::Protocol::Udp, - )); + ); + // Intentionally continue trying other DNS servers if we get an + // NXDOMAIN or NOERROR with no answer. This should be a rare + // circumstance. If it occurs and the name is genuinely not + // present, we'll be slower to error. If the name is unevenly + // distributed it is in the process of going away, or a DNS server + // is serving stale records and may not be getting updated anymore. + // In this last case, we may be avoiding service disruption. + ns_config.trust_negative_responses = false; + rc.add_name_server(ns_config); } let mut opts = ResolverOpts::default(); // Enable edns for potentially larger records diff --git a/nexus/src/app/external_dns.rs b/nexus/src/app/external_dns.rs index f2a26728b5..2617e99a13 100644 --- a/nexus/src/app/external_dns.rs +++ b/nexus/src/app/external_dns.rs @@ -24,10 +24,33 @@ impl Resolver { assert!(!dns_servers.is_empty()); let mut rc = ResolverConfig::new(); for addr in dns_servers { - rc.add_name_server(NameServerConfig::new( + let mut ns_config = NameServerConfig::new( SocketAddr::new(*addr, DNS_PORT), Protocol::Udp, - )); + ); + // Explicltly set `trust_negative_responses` to false here to avoid + // some churn. It reasonably could be `true` here. + // + // Long ago, for internal DNS, we set this to false, disagreeing with + // `NameServerConfig::new`'s defaults in that context. We still set it false there, + // with some reasoning why. Other nameserver configurations are + // either less picky, or only have one server, so the setting + // doesn't matter, and we switched to `NameServerConfig::new` for + // simplicity in many test configs. Here, we could reasonably trust + // that DNS servers queried by the rack will be reasonably behaved and more + // human-operated than DNS servers in the rack, and set this to + // true. But then, + // https://github.com/hickory-dns/hickory-dns/pull/3052 imminently + // changes the default back to `false` for `NameServerConfig` + // constructors. So, sidestep the churn by setting this to false, + // keeping it the same as it was until we decide it should be + // otherwise. + // + // (The churn may also not materialize in hickory-dns 0.26.0, even + // so. I've reached out and they may switch the `NameServerConfig` + // builders to continue defaulting to trusting negative responses) + ns_config.trust_negative_responses = false; + rc.add_name_server(ns_config); } let mut opts = ResolverOpts::default(); // Enable edns for potentially larger records diff --git a/wicketd/src/preflight_check/uplink.rs b/wicketd/src/preflight_check/uplink.rs index 7ff4aca297..c77c94c655 100644 --- a/wicketd/src/preflight_check/uplink.rs +++ b/wicketd/src/preflight_check/uplink.rs @@ -1092,9 +1092,9 @@ impl DnsLookupStep { if err.is_no_records_found() && options.is_no_records_found_okay { let message = format!( "DNS server {dns_ip} \ - {query_type} query attempt {attempt}: \ - no record found for {name}; \ - connectivity to DNS server appears good" + {query_type} query attempt {attempt}: \ + no record found for {name}; \ + connectivity to DNS server appears good" ); query_results.push(message.clone()); cx.send_progress(StepProgress::Progress { @@ -1106,12 +1106,13 @@ impl DnsLookupStep { self.messages.append(&mut query_results); return Ok(()); } else if err.is_no_records_found() { - // Otherwise, `NoRecordsFound` means we should either switch to - // AAAA queries (if this was A) or we're done (and failed). + // If we did not find records but that is not an acceptable + // result for the lookup. Switch to AAAA queries (if this was A) + // or we're done (and failed). let message = format!( "DNS server {dns_ip} \ - {query_type} query attempt {attempt}: \ - failed to look up {name}: {}", + {query_type} query attempt {attempt}: \ + failed to look up {name}: {}", DisplayErrorChain::new(&err) ); query_results.push(message.clone()); @@ -1121,9 +1122,6 @@ impl DnsLookupStep { }) .await; - // If this was an A query, switch to AAAA and reset the - // attempt counter; otherwise, we're done (and we failed - // to resolve the name). if query_ipv4 { query_ipv4 = false; attempt = 0; @@ -1135,8 +1133,8 @@ impl DnsLookupStep { } else { let message = format!( "DNS server {dns_ip} \ - {query_type} query attempt {attempt}: \ - failed to look up {name}: {}", + {query_type} query attempt {attempt}: \ + failed to look up {name}: {}", DisplayErrorChain::new(&err) ); query_results.push(message.clone()); From 83dc4ca4bb9aa263963dbab6a5d751ff5e578774 Mon Sep 17 00:00:00 2001 From: iximeow Date: Mon, 23 Jun 2025 21:06:11 +0000 Subject: [PATCH 5/5] resolve the workspace-hack conflict more appropriately --- Cargo.lock | 2 +- workspace-hack/Cargo.toml | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98e475291e..2df72484bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8134,8 +8134,8 @@ dependencies = [ "ff", "flate2", "foldhash", + "form_urlencoded", "fs-err 3.1.1", - "futures", "futures-channel", "futures-core", "futures-io", diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index ddacba31e2..90277abf30 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -33,8 +33,8 @@ byteorder = { version = "1.5.0" } bytes = { version = "1.10.1", features = ["serde"] } chrono = { version = "0.4.41", features = ["serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } -clap = { version = "4.5.35", features = ["cargo", "derive", "env", "wrap_help"] } -clap_builder = { version = "4.5.35", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } +clap = { version = "4.5.40", features = ["cargo", "derive", "env", "wrap_help"] } +clap_builder = { version = "4.5.40", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } console = { version = "0.15.10" } crossbeam-epoch = { version = "0.9.18" } crossbeam-utils = { version = "0.8.21" } @@ -46,13 +46,13 @@ data-encoding = { version = "2.9.0" } digest = { version = "0.10.7", features = ["mac", "oid", "std"] } ecdsa = { version = "0.16.9", features = ["pem", "signing", "std", "verifying"] } ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core"] } -either = { version = "1.14.0" } +either = { version = "1.15.0", features = ["use_std"] } elliptic-curve = { version = "0.13.8", features = ["ecdh", "hazmat", "pem", "std"] } ff = { version = "0.13.0", default-features = false, features = ["alloc"] } flate2 = { version = "1.1.2" } foldhash = { version = "0.1.5" } +form_urlencoded = { version = "1.2.1" } fs-err = { version = "3.1.1", default-features = false, features = ["tokio"] } -futures = { version = "0.3.31" } futures-channel = { version = "0.3.31", features = ["sink"] } futures-core = { version = "0.3.31" } futures-io = { version = "0.3.31" } @@ -63,7 +63,7 @@ gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway- generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2.15", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } -hashbrown = { version = "0.15.3" } +hashbrown = { version = "0.15.4" } hickory-proto = { version = "0.25.2", features = ["serde", "text-parsing"] } hmac = { version = "0.12.1", default-features = false, features = ["reset"] } hyper = { version = "1.6.0", features = ["full"] } @@ -87,7 +87,7 @@ num-integer = { version = "0.1.46", features = ["i128"] } num-iter = { version = "0.1.45", default-features = false, features = ["i128"] } num-traits = { version = "0.2.19", features = ["i128", "libm"] } once_cell = { version = "1.21.3", features = ["critical-section"] } -openapiv3 = { version = "2.0.0", default-features = false, features = ["skip_serializing_defaults"] } +openapiv3 = { version = "2.2.0", default-features = false, features = ["skip_serializing_defaults"] } peg-runtime = { version = "0.8.5", default-features = false, features = ["std"] } pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["std"] } percent-encoding = { version = "2.3.1" } @@ -166,8 +166,8 @@ bytes = { version = "1.10.1", features = ["serde"] } cc = { version = "1.2.15", default-features = false, features = ["parallel"] } chrono = { version = "0.4.41", features = ["serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } -clap = { version = "4.5.35", features = ["cargo", "derive", "env", "wrap_help"] } -clap_builder = { version = "4.5.35", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } +clap = { version = "4.5.40", features = ["cargo", "derive", "env", "wrap_help"] } +clap_builder = { version = "4.5.40", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } console = { version = "0.15.10" } crossbeam-epoch = { version = "0.9.18" } crossbeam-utils = { version = "0.8.21" } @@ -179,13 +179,13 @@ data-encoding = { version = "2.9.0" } digest = { version = "0.10.7", features = ["mac", "oid", "std"] } ecdsa = { version = "0.16.9", features = ["pem", "signing", "std", "verifying"] } ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core"] } -either = { version = "1.14.0" } +either = { version = "1.15.0", features = ["use_std"] } elliptic-curve = { version = "0.13.8", features = ["ecdh", "hazmat", "pem", "std"] } ff = { version = "0.13.0", default-features = false, features = ["alloc"] } flate2 = { version = "1.1.2" } foldhash = { version = "0.1.5" } +form_urlencoded = { version = "1.2.1" } fs-err = { version = "3.1.1", default-features = false, features = ["tokio"] } -futures = { version = "0.3.31" } futures-channel = { version = "0.3.31", features = ["sink"] } futures-core = { version = "0.3.31" } futures-io = { version = "0.3.31" } @@ -196,7 +196,7 @@ gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway- generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2.15", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } -hashbrown = { version = "0.15.3" } +hashbrown = { version = "0.15.4" } hickory-proto = { version = "0.25.2", features = ["serde", "text-parsing"] } hmac = { version = "0.12.1", default-features = false, features = ["reset"] } hyper = { version = "1.6.0", features = ["full"] } @@ -220,7 +220,7 @@ num-integer = { version = "0.1.46", features = ["i128"] } num-iter = { version = "0.1.45", default-features = false, features = ["i128"] } num-traits = { version = "0.2.19", features = ["i128", "libm"] } once_cell = { version = "1.21.3", features = ["critical-section"] } -openapiv3 = { version = "2.0.0", default-features = false, features = ["skip_serializing_defaults"] } +openapiv3 = { version = "2.2.0", default-features = false, features = ["skip_serializing_defaults"] } peg-runtime = { version = "0.8.5", default-features = false, features = ["std"] } pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["std"] } percent-encoding = { version = "2.3.1" }