[GH-ISSUE #1353] Resolver claims network is unreachable when iodef URL parsing bails #654

Closed
opened 2026-03-15 23:42:19 +03:00 by kerem · 8 comments
Owner

Originally created by @saethlin on GitHub (Jan 12, 2021).
Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1353

Observed on released trust-dns-proto/resolver 0.20.0 and 8f1a8eeff8

This parsing failure of a probably-invalid iodef eventually causes the resolver to report that the network is unreachable, which seems pretty wrong to me. Ideally I would like to be able to see the bytes that failed to parse as a URL, but the resolver should definitely report the correct problem, and I'd really like to be able to see the rest of the parsed Message, perhaps with the malformed record missing. I'm willing to contribute a patch to fix this if you can offer some guidance.

Offline reproducer:

use trust_dns_proto::op::message::Message;
fn main() {
    let message: &[u8] = &[
        91, 195, 129, 144, 0, 1, 0, 13, 0, 0, 0, 1, 6, 99, 112, 97, 110, 101, 108, 3, 99, 111, 109,
        0, 0, 255, 0, 1, 192, 12, 0, 1, 0, 1, 0, 0, 0, 99, 0, 4, 208, 74, 121, 151, 192, 12, 0, 1,
        0, 1, 0, 0, 0, 99, 0, 4, 208, 74, 123, 84, 192, 12, 1, 1, 0, 1, 0, 0, 56, 63, 0, 19, 0, 5,
        105, 115, 115, 117, 101, 99, 111, 109, 111, 100, 111, 99, 97, 46, 99, 111, 109, 192, 12, 1,
        1, 0, 1, 0, 0, 56, 63, 0, 35, 0, 5, 105, 111, 100, 101, 102, 109, 97, 105, 108, 116, 111,
        58, 99, 97, 97, 45, 110, 111, 116, 105, 102, 121, 64, 99, 112, 97, 110, 101, 108, 46, 110,
        101, 116, 192, 12, 1, 1, 0, 1, 0, 0, 56, 63, 0, 28, 0, 5, 105, 111, 100, 101, 102, 99, 97,
        97, 45, 110, 111, 116, 105, 102, 121, 64, 99, 112, 97, 110, 101, 108, 46, 110, 101, 116,
        192, 12, 0, 16, 0, 1, 0, 0, 0, 99, 0, 29, 28, 118, 61, 115, 112, 102, 49, 32, 109, 120, 32,
        109, 120, 58, 99, 112, 97, 110, 101, 108, 46, 110, 101, 116, 32, 45, 97, 108, 108, 192, 12,
        0, 16, 0, 1, 0, 0, 0, 99, 0, 130, 129, 104, 111, 117, 45, 49, 46, 102, 114, 111, 110, 116,
        101, 110, 100, 46, 119, 119, 119, 46, 112, 114, 111, 100, 46, 99, 112, 97, 110, 101, 108,
        46, 110, 101, 116, 58, 32, 84, 117, 101, 32, 74, 97, 110, 32, 49, 50, 32, 49, 51, 58, 51,
        49, 58, 49, 49, 32, 50, 48, 50, 49, 32, 72, 84, 84, 80, 32, 79, 75, 58, 32, 72, 84, 84, 80,
        47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 32, 45, 32, 51, 52, 51, 51, 50, 32, 98, 121,
        116, 101, 115, 32, 105, 110, 32, 48, 46, 48, 54, 51, 32, 115, 101, 99, 111, 110, 100, 32,
        114, 101, 115, 112, 111, 110, 115, 101, 32, 116, 105, 109, 101, 192, 12, 0, 16, 0, 1, 0, 0,
        0, 99, 0, 130, 129, 100, 97, 108, 45, 49, 46, 102, 114, 111, 110, 116, 101, 110, 100, 46,
        119, 119, 119, 46, 112, 114, 111, 100, 46, 99, 112, 97, 110, 101, 108, 46, 110, 101, 116,
        58, 32, 84, 117, 101, 32, 74, 97, 110, 32, 49, 50, 32, 49, 51, 58, 51, 50, 58, 51, 57, 32,
        50, 48, 50, 49, 32, 72, 84, 84, 80, 32, 79, 75, 58, 32, 72, 84, 84, 80, 47, 49, 46, 49, 32,
        50, 48, 48, 32, 79, 75, 32, 45, 32, 51, 52, 51, 51, 50, 32, 98, 121, 116, 101, 115, 32,
        105, 110, 32, 48, 46, 48, 49, 49, 32, 115, 101, 99, 111, 110, 100, 32, 114, 101, 115, 112,
        111, 110, 115, 101, 32, 116, 105, 109, 101, 192, 12, 0, 6, 0, 1, 0, 0, 56, 103, 0, 40, 1,
        99, 6, 99, 112, 97, 110, 101, 108, 3, 110, 101, 116, 0, 3, 100, 110, 115, 194, 5, 120, 28,
        79, 3, 0, 0, 56, 64, 0, 0, 28, 32, 0, 36, 234, 0, 0, 1, 81, 128, 192, 12, 0, 2, 0, 1, 0, 0,
        56, 63, 0, 2, 194, 3, 192, 12, 0, 2, 0, 1, 0, 0, 56, 63, 0, 5, 2, 109, 110, 194, 5, 192,
        12, 0, 2, 0, 1, 0, 0, 56, 63, 0, 5, 2, 104, 103, 194, 5, 192, 12, 0, 15, 0, 1, 0, 0, 0,
        139, 0, 8, 0, 0, 3, 109, 120, 49, 192, 12, 0, 0, 41, 2, 0, 0, 0, 128, 0, 0, 0,
    ][..];
    println!("{:?}", Message::from_vec(message));
}

Online reproducer:

use trust_dns_proto::rr::record_type::RecordType;
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use trust_dns_resolver::Resolver;
fn main() {
    let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
    let response = resolver.lookup("cpanel.net", RecordType::CAA);
    println!("{:?}", response);
}
Originally created by @saethlin on GitHub (Jan 12, 2021). Original GitHub issue: https://github.com/hickory-dns/hickory-dns/issues/1353 Observed on released trust-dns-proto/resolver 0.20.0 and 8f1a8eeff8389817d747ee621044f9c73a7f4622 This parsing failure of a probably-invalid iodef eventually causes the resolver to report that the network is unreachable, which seems pretty wrong to me. Ideally I would like to be able to see the bytes that failed to parse as a URL, but the resolver should definitely report the correct problem, and I'd really like to be able to see the rest of the parsed Message, perhaps with the malformed record missing. I'm willing to contribute a patch to fix this if you can offer some guidance. Offline reproducer: ```rust use trust_dns_proto::op::message::Message; fn main() { let message: &[u8] = &[ 91, 195, 129, 144, 0, 1, 0, 13, 0, 0, 0, 1, 6, 99, 112, 97, 110, 101, 108, 3, 99, 111, 109, 0, 0, 255, 0, 1, 192, 12, 0, 1, 0, 1, 0, 0, 0, 99, 0, 4, 208, 74, 121, 151, 192, 12, 0, 1, 0, 1, 0, 0, 0, 99, 0, 4, 208, 74, 123, 84, 192, 12, 1, 1, 0, 1, 0, 0, 56, 63, 0, 19, 0, 5, 105, 115, 115, 117, 101, 99, 111, 109, 111, 100, 111, 99, 97, 46, 99, 111, 109, 192, 12, 1, 1, 0, 1, 0, 0, 56, 63, 0, 35, 0, 5, 105, 111, 100, 101, 102, 109, 97, 105, 108, 116, 111, 58, 99, 97, 97, 45, 110, 111, 116, 105, 102, 121, 64, 99, 112, 97, 110, 101, 108, 46, 110, 101, 116, 192, 12, 1, 1, 0, 1, 0, 0, 56, 63, 0, 28, 0, 5, 105, 111, 100, 101, 102, 99, 97, 97, 45, 110, 111, 116, 105, 102, 121, 64, 99, 112, 97, 110, 101, 108, 46, 110, 101, 116, 192, 12, 0, 16, 0, 1, 0, 0, 0, 99, 0, 29, 28, 118, 61, 115, 112, 102, 49, 32, 109, 120, 32, 109, 120, 58, 99, 112, 97, 110, 101, 108, 46, 110, 101, 116, 32, 45, 97, 108, 108, 192, 12, 0, 16, 0, 1, 0, 0, 0, 99, 0, 130, 129, 104, 111, 117, 45, 49, 46, 102, 114, 111, 110, 116, 101, 110, 100, 46, 119, 119, 119, 46, 112, 114, 111, 100, 46, 99, 112, 97, 110, 101, 108, 46, 110, 101, 116, 58, 32, 84, 117, 101, 32, 74, 97, 110, 32, 49, 50, 32, 49, 51, 58, 51, 49, 58, 49, 49, 32, 50, 48, 50, 49, 32, 72, 84, 84, 80, 32, 79, 75, 58, 32, 72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 32, 45, 32, 51, 52, 51, 51, 50, 32, 98, 121, 116, 101, 115, 32, 105, 110, 32, 48, 46, 48, 54, 51, 32, 115, 101, 99, 111, 110, 100, 32, 114, 101, 115, 112, 111, 110, 115, 101, 32, 116, 105, 109, 101, 192, 12, 0, 16, 0, 1, 0, 0, 0, 99, 0, 130, 129, 100, 97, 108, 45, 49, 46, 102, 114, 111, 110, 116, 101, 110, 100, 46, 119, 119, 119, 46, 112, 114, 111, 100, 46, 99, 112, 97, 110, 101, 108, 46, 110, 101, 116, 58, 32, 84, 117, 101, 32, 74, 97, 110, 32, 49, 50, 32, 49, 51, 58, 51, 50, 58, 51, 57, 32, 50, 48, 50, 49, 32, 72, 84, 84, 80, 32, 79, 75, 58, 32, 72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 32, 45, 32, 51, 52, 51, 51, 50, 32, 98, 121, 116, 101, 115, 32, 105, 110, 32, 48, 46, 48, 49, 49, 32, 115, 101, 99, 111, 110, 100, 32, 114, 101, 115, 112, 111, 110, 115, 101, 32, 116, 105, 109, 101, 192, 12, 0, 6, 0, 1, 0, 0, 56, 103, 0, 40, 1, 99, 6, 99, 112, 97, 110, 101, 108, 3, 110, 101, 116, 0, 3, 100, 110, 115, 194, 5, 120, 28, 79, 3, 0, 0, 56, 64, 0, 0, 28, 32, 0, 36, 234, 0, 0, 1, 81, 128, 192, 12, 0, 2, 0, 1, 0, 0, 56, 63, 0, 2, 194, 3, 192, 12, 0, 2, 0, 1, 0, 0, 56, 63, 0, 5, 2, 109, 110, 194, 5, 192, 12, 0, 2, 0, 1, 0, 0, 56, 63, 0, 5, 2, 104, 103, 194, 5, 192, 12, 0, 15, 0, 1, 0, 0, 0, 139, 0, 8, 0, 0, 3, 109, 120, 49, 192, 12, 0, 0, 41, 2, 0, 0, 0, 128, 0, 0, 0, ][..]; println!("{:?}", Message::from_vec(message)); } ``` Online reproducer: ```rust use trust_dns_proto::rr::record_type::RecordType; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use trust_dns_resolver::Resolver; fn main() { let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap(); let response = resolver.lookup("cpanel.net", RecordType::CAA); println!("{:?}", response); } ```
kerem 2026-03-15 23:42:19 +03:00
Author
Owner

@djc commented on GitHub (Jan 13, 2021):

You might try turning on the logs for your online reproducer and see what it comes up with (use env_logger and tune it all the way up to DEBUG). I suspect this might be hitting the code at https://github.com/bluejekyll/trust-dns/blob/main/crates/proto/src/udp/udp_client_stream.rs#L249, but am not sure.

<!-- gh-comment-id:759274583 --> @djc commented on GitHub (Jan 13, 2021): You might try turning on the logs for your online reproducer and see what it comes up with (use env_logger and tune it all the way up to DEBUG). I suspect this might be hitting the code at https://github.com/bluejekyll/trust-dns/blob/main/crates/proto/src/udp/udp_client_stream.rs#L249, but am not sure.
Author
Owner

@saethlin commented on GitHub (Jan 13, 2021):

[2021-01-13T14:49:27Z DEBUG trust_dns_proto::udp::udp_stream] created socket successfully
[2021-01-13T14:49:27Z DEBUG trust_dns_proto::udp::udp_stream] created socket successfully
[2021-01-13T14:49:27Z WARN  trust_dns_proto::udp::udp_client_stream] dropped malformed message waiting for id: 30569 err: url parsing error
[2021-01-13T14:49:27Z WARN  trust_dns_proto::udp::udp_client_stream] dropped malformed message waiting for id: 52207 err: url parsing error
[2021-01-13T14:49:32Z DEBUG trust_dns_resolver::name_server::name_server] name_server connection failure: request timed out
[2021-01-13T14:49:32Z DEBUG trust_dns_resolver::name_server::name_server] name_server connection failure: request timed out

Sure looks like it.

<!-- gh-comment-id:759499030 --> @saethlin commented on GitHub (Jan 13, 2021): ``` [2021-01-13T14:49:27Z DEBUG trust_dns_proto::udp::udp_stream] created socket successfully [2021-01-13T14:49:27Z DEBUG trust_dns_proto::udp::udp_stream] created socket successfully [2021-01-13T14:49:27Z WARN trust_dns_proto::udp::udp_client_stream] dropped malformed message waiting for id: 30569 err: url parsing error [2021-01-13T14:49:27Z WARN trust_dns_proto::udp::udp_client_stream] dropped malformed message waiting for id: 52207 err: url parsing error [2021-01-13T14:49:32Z DEBUG trust_dns_resolver::name_server::name_server] name_server connection failure: request timed out [2021-01-13T14:49:32Z DEBUG trust_dns_resolver::name_server::name_server] name_server connection failure: request timed out ``` Sure looks like it.
Author
Owner

@djc commented on GitHub (Jan 13, 2021):

What kind of guidance are you looking for as to improving the situation here? I'm not sure what the relevant specs have to say about returning partial messages up to the caller, and in general Rust error handling gets a little cumbersome when you have both a result and some validation error. There may also be good reasons to just drop invalid messages in favor of potentially trying again, though trust-dns could potentially be more principled about how this is handled.

<!-- gh-comment-id:759538416 --> @djc commented on GitHub (Jan 13, 2021): What kind of guidance are you looking for as to improving the situation here? I'm not sure what the relevant specs have to say about returning partial messages up to the caller, and in general Rust error handling gets a little cumbersome when you have both a result and some validation error. There may also be good reasons to just drop invalid messages in favor of potentially trying again, though trust-dns could potentially be more principled about how this is handled.
Author
Owner

@bluejekyll commented on GitHub (Jan 13, 2021):

I suppose we could make the URL parsing lazy instead of eager. We could store the URL as bytes (assuming the character-data is well-formed) and then have lazy URL parsing on the get for the field. That way message processing wouldn't fail, but you'd still know that field is a malformed URL on access.

<!-- gh-comment-id:759581710 --> @bluejekyll commented on GitHub (Jan 13, 2021): I suppose we could make the URL parsing lazy instead of eager. We could store the URL as bytes (assuming the character-data is well-formed) and then have lazy URL parsing on the get for the field. That way message processing wouldn't fail, but you'd still know that field is a malformed URL on access.
Author
Owner

@bluejekyll commented on GitHub (Jan 13, 2021):

In terms of better error reporting, that might be possible? It's tough though, since we're dropping the entire message (including the id) on a parse failure. Maybe we could pull out the message Header separately from the entire Message, and that would allow for some additional matching of returned malformed Response to the Request.

<!-- gh-comment-id:759584554 --> @bluejekyll commented on GitHub (Jan 13, 2021): In terms of better error reporting, that might be possible? It's tough though, since we're dropping the entire message (including the id) on a parse failure. Maybe we could pull out the message Header separately from the entire Message, and that would allow for some additional matching of returned malformed Response to the Request.
Author
Owner

@saethlin commented on GitHub (Jan 13, 2021):

Trust already reports partial messages to the caller, using RData::Unknown. That's probably the wrong tool here, but there's precedent for partial success.

I'd prefer the error reporting to be more accurate, even if no data from the message is reported. The network wasn't unreachable, that's just a lie. We got a message, but it didn't parse, and we tried again. Can the resolver report that?

<!-- gh-comment-id:759585419 --> @saethlin commented on GitHub (Jan 13, 2021): Trust already reports partial messages to the caller, using `RData::Unknown`. That's probably the wrong tool here, but there's precedent for partial success. I'd prefer the error reporting to be more accurate, even if no data from the message is reported. The network _wasn't_ unreachable, that's just a lie. We got a message, but it didn't parse, and we tried again. Can the resolver report that?
Author
Owner

@bluejekyll commented on GitHub (Jan 13, 2021):

See my comment on the error reporting. Of course it can be better. The challenge is that to fix that, we need to build in support to get the partial message associated back to the request, and return that.

Basically, it’s work :)

<!-- gh-comment-id:759598481 --> @bluejekyll commented on GitHub (Jan 13, 2021): See my comment on the error reporting. Of course it can be better. The challenge is that to fix that, we need to build in support to get the partial message associated back to the request, and return that. Basically, it’s work :)
Author
Owner

@divergentdave commented on GitHub (Mar 31, 2025):

I agree that we should defer parsing the iodef value as a URL. The message fuzzer has found a CAA record that does not round-trip successfully due to this field.

Diff < left / right > :
 Message {
...
             rdata: CAA(
                 CAA {
                     issuer_critical: false,
                     reserved_flags: 0,
                     tag: Iodef,
                     value: Url(
                         Url {
                             scheme: "file",
                             cannot_be_a_base: false,
                             username: "",
                             password: None,
<                            host: Some(
<                                Ipv4(
<                                    0.0.0.5,
<                                ),
<                            ),
>                            host: None,
                             port: None,
                             path: "/e://000000000000000000000000005///////////////////////////////",
                             query: None,
                             fragment: Some(
                                 "////////////////////////////////////ftp//%01%00%00%00%00%00%00///////////////////%00%00%00%00=%00%00%00%00%00%00%00%00%02%00%00%00%00%00%01%01%01%01%01%01%01%01%06%00\\%%01%01%01%012222//////////////////////////////////%00%00y.x-n-T%00%00\\",
                             ),
                         },
                     ),
                 },
             ),
...
}

Similarly, any canonicalization that the Url type does may cause signature verification failures.

<!-- gh-comment-id:2766999161 --> @divergentdave commented on GitHub (Mar 31, 2025): I agree that we should defer parsing the `iodef` value as a URL. The `message` fuzzer has found a CAA record that does not round-trip successfully due to this field. ``` Diff < left / right > : Message { ... rdata: CAA( CAA { issuer_critical: false, reserved_flags: 0, tag: Iodef, value: Url( Url { scheme: "file", cannot_be_a_base: false, username: "", password: None, < host: Some( < Ipv4( < 0.0.0.5, < ), < ), > host: None, port: None, path: "/e://000000000000000000000000005///////////////////////////////", query: None, fragment: Some( "////////////////////////////////////ftp//%01%00%00%00%00%00%00///////////////////%00%00%00%00=%00%00%00%00%00%00%00%00%02%00%00%00%00%00%01%01%01%01%01%01%01%01%06%00\\%%01%01%01%012222//////////////////////////////////%00%00y.x-n-T%00%00\\", ), }, ), }, ), ... } ``` Similarly, any canonicalization that the `Url` type does may cause signature verification failures.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/hickory-dns#654
No description provided.