Skip to content

Commit

Permalink
Adds DNS hostname to NetAddress
Browse files Browse the repository at this point in the history
  • Loading branch information
wvanlint committed Jun 24, 2022
1 parent abf6564 commit cf6b4aa
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 19 deletions.
10 changes: 5 additions & 5 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2859,15 +2859,15 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana

#[allow(dead_code)]
// Messages of up to 64KB should never end up more than half full with addresses, as that would
// be absurd. We ensure this by checking that at least 500 (our stated public contract on when
// be absurd. We ensure this by checking that at least 100 (our stated public contract on when
// broadcast_node_announcement panics) of the maximum-length addresses would fit in a 64KB
// message...
const HALF_MESSAGE_IS_ADDRS: u32 = ::core::u16::MAX as u32 / (NetAddress::MAX_LEN as u32 + 1) / 2;
#[deny(const_err)]
#[allow(dead_code)]
// ...by failing to compile if the number of addresses that would be half of a message is
// smaller than 500:
const STATIC_ASSERT: u32 = Self::HALF_MESSAGE_IS_ADDRS - 500;
// smaller than 100:
const STATIC_ASSERT: u32 = Self::HALF_MESSAGE_IS_ADDRS - 100;

/// Regenerates channel_announcements and generates a signed node_announcement from the given
/// arguments, providing them in corresponding events via
Expand All @@ -2884,13 +2884,13 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
/// tying these addresses together and to this node. If you wish to preserve user privacy,
/// addresses should likely contain only Tor Onion addresses.
///
/// Panics if `addresses` is absurdly large (more than 500).
/// Panics if `addresses` is absurdly large (more than 100).
///
/// [`get_and_clear_pending_msg_events`]: MessageSendEventsProvider::get_and_clear_pending_msg_events
pub fn broadcast_node_announcement(&self, rgb: [u8; 3], alias: [u8; 32], mut addresses: Vec<NetAddress>) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

if addresses.len() > 500 {
if addresses.len() > 100 {
panic!("More than half the message size was taken up by public addresses!");
}

Expand Down
59 changes: 45 additions & 14 deletions lightning/src/ln/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use io_extras::read_to_end;

use util::events::MessageSendEventsProvider;
use util::logger;
use util::ser::{Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt};
use util::ser::{Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname};

use ln::{PaymentPreimage, PaymentHash, PaymentSecret};

Expand Down Expand Up @@ -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.
hostname: Hostname,
/// The port on which the node is listening.
port: u16,
},
}
impl NetAddress {
/// Gets the ID of this address type. Addresses in node_announcement messages should be sorted
Expand All @@ -452,6 +459,7 @@ impl NetAddress {
&NetAddress::IPv6 {..} => { 2 },
&NetAddress::OnionV2(_) => { 3 },
&NetAddress::OnionV3 {..} => { 4 },
&NetAddress::Hostname {..} => { 5 },
}
}

Expand All @@ -462,11 +470,12 @@ impl NetAddress {
&NetAddress::IPv6 { .. } => { 18 },
&NetAddress::OnionV2(_) => { 12 },
&NetAddress::OnionV3 { .. } => { 37 },
&NetAddress::Hostname { ref hostname, .. } => { u16::from(hostname.len()) + 3 },
}
}

/// The maximum length of any address descriptor, not including the 1-byte type
pub(crate) const MAX_LEN: u16 = 37;
pub(crate) const MAX_LEN: u16 = 258;
}

impl Writeable for NetAddress {
Expand All @@ -492,7 +501,12 @@ impl Writeable for NetAddress {
checksum.write(writer)?;
version.write(writer)?;
port.write(writer)?;
}
},
&NetAddress::Hostname { ref hostname, ref port } => {
5u8.write(writer)?;
hostname.write(writer)?;
port.write(writer)?;
},
}
Ok(())
}
Expand Down Expand Up @@ -523,6 +537,12 @@ impl Readable for Result<NetAddress, u8> {
port: Readable::read(reader)?,
}))
},
5 => {
Ok(Ok(NetAddress::Hostname {
hostname: Readable::read(reader)?,
port: Readable::read(reader)?,
}))
},
_ => return Ok(Err(byte)),
}
}
Expand Down Expand Up @@ -1829,7 +1849,7 @@ mod tests {
use ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
use ln::msgs;
use ln::msgs::{FinalOnionHopData, OptionalField, OnionErrorPacket, OnionHopDataFormat};
use util::ser::{Writeable, Readable};
use util::ser::{Writeable, Readable, Hostname};

use bitcoin::hashes::hex::FromHex;
use bitcoin::util::address::Address;
Expand All @@ -1843,6 +1863,7 @@ mod tests {

use io::Cursor;
use prelude::*;
use core::convert::TryFrom;

#[test]
fn encoding_channel_reestablish_no_secret() {
Expand Down Expand Up @@ -1971,7 +1992,7 @@ mod tests {
do_encoding_channel_announcement(true, true);
}

fn do_encoding_node_announcement(unknown_features_bits: bool, ipv4: bool, ipv6: bool, onionv2: bool, onionv3: bool, excess_address_data: bool, excess_data: bool) {
fn do_encoding_node_announcement(unknown_features_bits: bool, ipv4: bool, ipv6: bool, onionv2: bool, onionv3: bool, hostname: bool, excess_address_data: bool, excess_data: bool) {
let secp_ctx = Secp256k1::new();
let (privkey_1, pubkey_1) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
let sig_1 = get_sig_on!(privkey_1, secp_ctx, String::from("01010101010101010101010101010101"));
Expand Down Expand Up @@ -2007,6 +2028,12 @@ mod tests {
port: 9735
});
}
if hostname {
addresses.push(msgs::NetAddress::Hostname {
hostname: Hostname::try_from(String::from("host")).unwrap(),
port: 9735,
});
}
let mut addr_len = 0;
for addr in &addresses {
addr_len += addr.len() + 1;
Expand Down Expand Up @@ -2047,6 +2074,9 @@ mod tests {
if onionv3 {
target_value.append(&mut hex::decode("04fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e00020102607").unwrap());
}
if hostname {
target_value.append(&mut hex::decode("0504686f73742607").unwrap());
}
if excess_address_data {
target_value.append(&mut hex::decode("216c280b5395a2546e7e4b2663e04f811622f15a4f92e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d269").unwrap());
}
Expand All @@ -2058,15 +2088,16 @@ mod tests {

#[test]
fn encoding_node_announcement() {
do_encoding_node_announcement(true, true, true, true, true, true, true);
do_encoding_node_announcement(false, false, false, false, false, false, false);
do_encoding_node_announcement(false, true, false, false, false, false, false);
do_encoding_node_announcement(false, false, true, false, false, false, false);
do_encoding_node_announcement(false, false, false, true, false, false, false);
do_encoding_node_announcement(false, false, false, false, true, false, false);
do_encoding_node_announcement(false, false, false, false, false, true, false);
do_encoding_node_announcement(false, true, false, true, false, true, false);
do_encoding_node_announcement(false, false, true, false, true, false, false);
do_encoding_node_announcement(true, true, true, true, true, true, true, true);
do_encoding_node_announcement(false, false, false, false, false, false, false, false);
do_encoding_node_announcement(false, true, false, false, false, false, false, false);
do_encoding_node_announcement(false, false, true, false, false, false, false, false);
do_encoding_node_announcement(false, false, false, true, false, false, false, false);
do_encoding_node_announcement(false, false, false, false, true, false, false, false);
do_encoding_node_announcement(false, false, false, false, false, true, false, false);
do_encoding_node_announcement(false, false, false, false, false, false, true, false);
do_encoding_node_announcement(false, true, false, true, false, false, true, false);
do_encoding_node_announcement(false, false, true, false, true, false, false, false);
}

fn do_encoding_channel_update(direction: bool, disable: bool, htlc_maximum_msat: bool, excess_data: bool) {
Expand Down
109 changes: 109 additions & 0 deletions lightning/src/util/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use io_extras::{copy, sink};
use core::hash::Hash;
use sync::Mutex;
use core::cmp;
use core::convert::TryFrom;
use core::ops::Deref;

use bitcoin::secp256k1::{PublicKey, SecretKey};
use bitcoin::secp256k1::constants::{PUBLIC_KEY_SIZE, SECRET_KEY_SIZE, COMPACT_SIGNATURE_SIZE};
Expand Down Expand Up @@ -913,6 +915,82 @@ impl Readable for String {
}
}

/// Represents a hostname with validation for serialization purposes.
/// The hostname follows the preferred form summarized in e.g. RFC 3696.
/// Its length is guaranteed to be representable by a single byte.
/// This serialization is used by Bolt 7 hostnames.
#[derive(Clone, Debug, PartialEq)]
pub struct Hostname(String);
impl Hostname {
/// Returns the length of the hostname with an appropriate return type.
pub fn len(&self) -> u8 {
(&self.0).len() as u8
}
}
impl Deref for Hostname {
type Target = String;

fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<Hostname> for String {
fn from(short_s: Hostname) -> Self {
short_s.0
}
}
impl TryFrom<String> for Hostname {
type Error = ();

fn try_from(s: String) -> Result<Self, Self::Error> {
if s.len() > 255 {
return Err(());
}
let mut labels = s.split('.').enumerate().peekable();
while let Some((label_idx, label)) = labels.next() {
let last_label = labels.peek().is_none();
let bytes = label.as_bytes();
// Trailing period for fully-qualified name.
if bytes.len() == 0 {
if !last_label || label_idx == 0 {
return Err(());
}
}
if bytes.len() > 63 {
return Err(());
}
for byte_idx in 0 .. bytes.len() {
let b = bytes[byte_idx];
if !b.is_ascii_alphanumeric() && b != b'-' {
return Err(());
}
if b == b'-' && (byte_idx == 0 || byte_idx == bytes.len() - 1) {
return Err(());
}
}
};
Ok(Hostname(s))
}
}
impl Writeable for Hostname {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.len().write(w)?;
w.write_all(self.as_bytes())
}
}
impl Readable for Hostname {
#[inline]
fn read<R: Read>(r: &mut R) -> Result<Hostname, DecodeError> {
let len: u8 = Readable::read(r)?;
let mut vec = Vec::with_capacity(len as usize);
vec.resize(len as usize, 0);
r.read_exact(&mut vec)?;
let s = String::from_utf8(vec).map_err(|_| DecodeError::InvalidValue)?;
Hostname::try_from(s).map_err(|_| DecodeError::InvalidValue)
}
}

impl Writeable for Duration {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
Expand All @@ -928,3 +1006,34 @@ impl Readable for Duration {
Ok(Duration::new(secs, nanos))
}
}

#[cfg(test)]
mod tests {
use core::convert::TryFrom;
use util::ser::{Readable, Hostname, Writeable};

#[test]
fn hostname_conversion() {
assert_eq!(Hostname::try_from(String::from("test")).unwrap().as_str(), "test");
assert_eq!(Hostname::try_from(String::from("test-1")).unwrap().as_str(), "test-1");
assert_eq!(Hostname::try_from(String::from("1-test")).unwrap().as_str(), "1-test");
assert_eq!(Hostname::try_from(String::from("test.com")).unwrap().as_str(), "test.com");
assert_eq!(Hostname::try_from(String::from("test.com.")).unwrap().as_str(), "test.com.");
assert_eq!(Hostname::try_from(String::from("a.test.com")).unwrap().as_str(), "a.test.com");

assert!(Hostname::try_from(String::from("⚡")).is_err());
assert!(Hostname::try_from(String::from("-end")).is_err());
assert!(Hostname::try_from(String::from("start-")).is_err());
let mut large_vec = Vec::with_capacity(256);
large_vec.resize(256, b'A');
assert!(Hostname::try_from(String::from_utf8(large_vec).unwrap()).is_err());
}

#[test]
fn hostname_serialization() {
let short_s = Hostname::try_from(String::from("test")).unwrap();
let mut buf: Vec<u8> = Vec::new();
short_s.write(&mut buf).unwrap();
assert_eq!(Hostname::read(&mut buf.as_slice()).unwrap().as_str(), "test");
}
}

0 comments on commit cf6b4aa

Please sign in to comment.