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

Adds DNS hostname to NetAddress #1553

Merged
merged 1 commit into from Jul 5, 2022

Conversation

wvanlint
Copy link
Contributor

Supports Bolt 7 DNS hostnames specified by lightning/bolts#911.

type Error = ConversionError;

fn try_from(s: String) -> Result<Self, Self::Error> {
if s.is_ascii() && s.len() <= u8::MAX.into() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should also check that all characters are in the printable range (and maybe no spaces? probably no special chars? Dunno what's valid in DNS, we should check). With that in mind, maybe we should name the type something in reference to DNS?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems DNS permits any sequence of bytes but has a preferred format. I implemented the preferred format based on RFC 3696 and others linked there.

@@ -442,6 +442,14 @@ pub enum NetAddress {
/// The port on which the node is listening
port: u16,
},
/// A DNS hostname/port on which the peer is listening.
DNSHostname {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we just call it hostname? DNS is kinda obvious? Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adjusted that, I was mainly mirroring the BOLT PR.

@TheBlueMatt
Copy link
Collaborator

Nice, thanks! Looks like this is failing CI.

@wvanlint wvanlint force-pushed the dns_hostname branch 4 times, most recently from 9518a45 to 8892a48 Compare June 23, 2022 07:21
@tnull tnull linked an issue Jun 23, 2022 that may be closed by this pull request

fn try_from(s: String) -> Result<Self, Self::Error> {
// Regular expressions can't be used with no-std.
let labels: Vec<&str> = s.split(".").collect();
Copy link
Collaborator

Choose a reason for hiding this comment

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

You should be able to avoid the collect here. In general, allocating a Vec via collect is not super trivial, and we should avoid it if at all possible, preferring to just keep and work with the generated iterator directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -78,6 +78,10 @@ impl fmt::Debug for APIError {
}
}

/// Indicates an error on conversions by core::convert traits.
#[derive(Debug)]
pub struct ConversionError;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this worth it? If its just a dummy type can we not just use () instead? It seems basically as clear either way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I initially wanted to implement the Error trait for boxing purposes but I wasn't sure if it made sense to do this conditionally only when the standard library is used. Changed to () but let me know if you prefer a type with an Error trait instead.

@codecov-commenter
Copy link

codecov-commenter commented Jun 24, 2022

Codecov Report

Merging #1553 (c30dcf1) into main (abf6564) will increase coverage by 0.03%.
The diff coverage is 80.00%.

@@            Coverage Diff             @@
##             main    #1553      +/-   ##
==========================================
+ Coverage   90.96%   91.00%   +0.03%     
==========================================
  Files          80       80              
  Lines       43610    44197     +587     
  Branches    43610    44197     +587     
==========================================
+ Hits        39670    40221     +551     
- Misses       3940     3976      +36     
Impacted Files Coverage Δ
lightning/src/ln/msgs.rs 86.49% <75.00%> (-0.32%) ⬇️
lightning/src/util/ser.rs 90.96% <82.60%> (-0.32%) ⬇️
lightning/src/ln/channelmanager.rs 84.73% <100.00%> (+0.34%) ⬆️
lightning/src/util/chacha20poly1305rfc.rs 96.25% <0.00%> (-1.71%) ⬇️
lightning/src/chain/onchaintx.rs 93.98% <0.00%> (-0.93%) ⬇️
lightning/src/chain/channelmonitor.rs 90.91% <0.00%> (-0.33%) ⬇️
lightning/src/routing/scoring.rs 96.80% <0.00%> (-0.29%) ⬇️
lightning/src/ln/functional_tests.rs 96.81% <0.00%> (-0.25%) ⬇️
lightning-invoice/src/lib.rs 87.39% <0.00%> (ø)
lightning/src/ln/reorg_tests.rs 100.00% <0.00%> (ø)
... and 16 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update abf6564...c30dcf1. Read the comment docs.

@wvanlint wvanlint marked this pull request as ready for review June 24, 2022 06:25
// Trailing period for fully-qualified name.
if bytes.len() == 0 {
if !last_label || label_idx == 0 {
break false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of breaking out of the loop with a validity flag, the structure here would be more readable if we just returned the error directly if something is invalid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 954 to 959
let last_label;
if let None = labels.peek() {
last_label = true;
} else {
last_label = false;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let last_label;
if let None = labels.peek() {
last_label = true;
} else {
last_label = false;
}
let last_label = labels.peek().is_none();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 948 to 953
let valid_labels = loop {
let label;
match labels.next() {
Some(x) => label = x,
None => break true,
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let valid_labels = loop {
let label;
match labels.next() {
Some(x) => label = x,
None => break true,
}
while let Some(label) = labels.next() {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


fn try_from(s: String) -> Result<Self, Self::Error> {
let mut labels = s.split('.').peekable();
let mut label_idx = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of tracking the idx explicitly, maybe just wrap labels with an .enumerate().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
}
}
impl TryFrom<&str> for Hostname {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This gives the (incorrect) impression that From<&str> is relatively efficient, but its not. Its maybe simpler to force the user to do the String::from() or .to_string() explicitly themselves to communicate what's really going on here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's true, removed this implementation.

@TheBlueMatt
Copy link
Collaborator

It looks like somehow your rebase ended up reverting several recent PRs.

@TheBlueMatt
Copy link
Collaborator

TheBlueMatt commented Jun 25, 2022

It looks like somehow your rebase ended up reverting several recent PRs.

Oops, sorry, it looks like the GitHub mobile app just...shows incorrect diffs sometimes?

This basically LGTM, lets get another reviewer on it.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 25, 2022
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Basically LGTM.

#[derive(Clone, Debug, PartialEq)]
pub struct Hostname(String);
impl Hostname {
/// Returns the length of the hostname with an appropriate return type.
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/with an appropriate return type//?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Thank you for having a go at this! Generally looks good, only a few nits and questions.

lightning/src/util/ser.rs Outdated Show resolved Hide resolved
lightning/src/util/ser.rs Outdated Show resolved Hide resolved
lightning/src/ln/msgs.rs Show resolved Hide resolved
lightning/src/util/ser.rs Outdated Show resolved Hide resolved
lightning/src/util/ser.rs Outdated Show resolved Hide resolved
lightning/src/util/ser.rs Show resolved Hide resolved
lightning/src/util/ser.rs Outdated Show resolved Hide resolved
@@ -442,6 +442,13 @@ pub enum NetAddress {
/// The port on which the node is listening
port: u16,
},
/// A hostname/port on which the peer is listening.
Hostname {
/// The hostname on which the port is listening.
Copy link
Contributor

Choose a reason for hiding this comment

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

Think "port" should be "node" or "peer"?

Suggested change
/// The hostname on which the port is listening.
/// The hostname on which the node is listening.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Generally LGTM.

One note: it's easier for reviewers to follow the changes if you do not immediately squash them, but leave individual commits.

@wvanlint wvanlint requested a review from TheBlueMatt July 1, 2022 17:47
@TheBlueMatt
Copy link
Collaborator

Sorry for the delay, been a super busy week.

Generally, now that we have all the logic for it, and now that I think about it a bit more, do we really need to do this much verification of a hostname? Like, yea, the DNS won't resolve if the hostname is just one long string, but what happens if users start shoving non-hostname things in the hostname field (ie treating it as a second node alias, listing some policy info, etc) - should we outright drop their gossip, or simply fail to connect to the hostname and try the IP addresses instead? I do think we should ensure the strong is ASCII, is all in the printable range (ie no control chars) but do we need to care about label length?

@wvanlint
Copy link
Contributor Author

wvanlint commented Jul 2, 2022

@TheBlueMatt I agree, the concern of hostname validation can be left to DNS resolution and separated from the serialization concern here which was my original intent. I reverted back to the first approach with generic serialization prior to #1553 (comment).

Extending this rationale, I would also be open to relaxing validation further and allowing any UTF-8 string within the length constraint (although this is not included in the BOLT). Libraries are able to handle Punycode encoding e.g. "münchen.de:443".to_socket_addrs().unwrap(). I think the field could be misused no matter how strict the validation is.

Out of curiosity, are terminal escape sequence injections the main concern with control characters? I am unsure about their severity for modern terminals.

@TheBlueMatt
Copy link
Collaborator

Extending this rationale, I would also be open to relaxing validation further and allowing any UTF-8 string within the length constraint (although this is not included in the BOLT)

Hmm, there's two sides to this, though - we have to support reading hostnames in announcements and be kinda liberal there, but also want to be restrictive as to what users include as others may do strict validation. In theory we could split the two but I'm not sure it's really worth it - our users are developers, and can manage to just put a hostname.

Out of curiosity, are terminal escape sequence injections the main concern with control characters? I am unsure about their severity for modern terminals.

Yea, you never know, users of our devlopers' software will grep things and pass them to dig and...boom. With that in mind I'd suggest we also reject chars like $, ", ', backtick, etc. Probably easiest to just accept alphanumeric plus . and -. If its easy its best to be as conservative as possible but not bother doing strict hostname validation so much as just strict character set validation.

@wvanlint wvanlint force-pushed the dns_hostname branch 2 times, most recently from fc3c757 to 2767a41 Compare July 3, 2022 21:58
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Oooooooo-Kkkkkkkk. Thanks for sticking with me on this one, this LGTM as-is. Will let the other reviewers chime in, but feel free to squash down the fixups once they do.

@wvanlint wvanlint requested a review from tnull July 4, 2022 04:52
tnull
tnull previously approved these changes Jul 4, 2022
Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

LGTM. I just restarted the one stuck CI job.

@wvanlint
Copy link
Contributor Author

wvanlint commented Jul 4, 2022

Thanks for sticking with me on this one, this LGTM as-is. Will let the other reviewers chime in, but feel free to squash down the fixups once they do.

Thanks for the review as well! Squashed down the fixups.

@TheBlueMatt TheBlueMatt merged commit daeb5a6 into lightningdevkit:main Jul 5, 2022
@wvanlint wvanlint deleted the dns_hostname branch July 7, 2022 03:59
tnull added a commit to tnull/rust-lightning that referenced this pull request Jul 25, 2022
Fixes a deserialization incompatibility introduced with lightningdevkit#1553.
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

Successfully merging this pull request may close these issues.

Support DNS hostnames in gossip
5 participants