diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c277a7438da..3ca71a6a982 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2859,15 +2859,15 @@ impl 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 @@ -2884,13 +2884,13 @@ impl 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) { 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!"); } diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 0e5b2e07e7a..28736491a7c 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -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, ShortAsciiString}; use ln::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -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 { + /// The hostname as an ASCII string with a variable length below or equal to 255. + /// Can be a Punycode encoding. + hostname: ShortAsciiString, + /// 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 @@ -452,6 +460,7 @@ impl NetAddress { &NetAddress::IPv6 {..} => { 2 }, &NetAddress::OnionV2(_) => { 3 }, &NetAddress::OnionV3 {..} => { 4 }, + &NetAddress::DNSHostname {..} => { 5 }, } } @@ -462,11 +471,12 @@ impl NetAddress { &NetAddress::IPv6 { .. } => { 18 }, &NetAddress::OnionV2(_) => { 12 }, &NetAddress::OnionV3 { .. } => { 37 }, + &NetAddress::DNSHostname { 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 = u8::MAX as u16 + 3; } impl Writeable for NetAddress { @@ -492,7 +502,12 @@ impl Writeable for NetAddress { checksum.write(writer)?; version.write(writer)?; port.write(writer)?; - } + }, + &NetAddress::DNSHostname { ref hostname, ref port } => { + 5u8.write(writer)?; + hostname.write(writer)?; + port.write(writer)?; + }, } Ok(()) } @@ -523,6 +538,12 @@ impl Readable for Result { port: Readable::read(reader)?, })) }, + 5 => { + Ok(Ok(NetAddress::DNSHostname { + hostname: Readable::read(reader)?, + port: Readable::read(reader)?, + })) + }, _ => return Ok(Err(byte)), } } @@ -1829,7 +1850,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, ShortAsciiString}; use bitcoin::hashes::hex::FromHex; use bitcoin::util::address::Address; @@ -1843,6 +1864,7 @@ mod tests { use io::Cursor; use prelude::*; + use std::convert::TryFrom; #[test] fn encoding_channel_reestablish_no_secret() { @@ -1971,7 +1993,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, dns_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")); @@ -2007,6 +2029,12 @@ mod tests { port: 9735 }); } + if dns_hostname { + addresses.push(msgs::NetAddress::DNSHostname { + hostname: ShortAsciiString::try_from("host").unwrap(), + port: 9735, + }); + } let mut addr_len = 0; for addr in &addresses { addr_len += addr.len() + 1; @@ -2047,6 +2075,9 @@ mod tests { if onionv3 { target_value.append(&mut hex::decode("04fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e00020102607").unwrap()); } + if dns_hostname { + target_value.append(&mut hex::decode("0504686f73742607").unwrap()); + } if excess_address_data { target_value.append(&mut hex::decode("216c280b5395a2546e7e4b2663e04f811622f15a4f92e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d269").unwrap()); } @@ -2058,15 +2089,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) { diff --git a/lightning/src/util/errors.rs b/lightning/src/util/errors.rs index 820bf31c6e0..1ff4333e36e 100644 --- a/lightning/src/util/errors.rs +++ b/lightning/src/util/errors.rs @@ -13,6 +13,7 @@ use ln::script::ShutdownScript; use alloc::string::String; use core::fmt; +use std::error; /// Indicates an error on the client's part (usually some variant of attempting to use too-low or /// too-high values) @@ -78,6 +79,17 @@ impl fmt::Debug for APIError { } } +#[derive(Debug)] +pub struct ConversionError; + +impl fmt::Display for ConversionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } +} + +impl error::Error for ConversionError {} + #[inline] pub(crate) fn get_onion_debug_field(error_code: u16) -> (&'static str, usize) { match error_code & 0xff { diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 428adbc5e66..0422ad882fa 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -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}; @@ -32,6 +34,7 @@ use ln::msgs::DecodeError; use ln::{PaymentPreimage, PaymentHash, PaymentSecret}; use util::byte_utils::{be48_to_array, slice_to_be48}; +use util::errors::ConversionError; /// serialization buffer size pub const MAX_BUF_SIZE: usize = 64 * 1024; @@ -913,6 +916,62 @@ impl Readable for String { } } +#[derive(Clone, Debug, PartialEq)] +pub struct ShortAsciiString(String); +impl ShortAsciiString { + pub fn len(&self) -> u8 { + (&self.0).len() as u8 + } +} +impl Deref for ShortAsciiString { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl From for String { + fn from(short_s: ShortAsciiString) -> Self { + short_s.0 + } +} +impl TryFrom for ShortAsciiString { + type Error = ConversionError; + + fn try_from(s: String) -> Result { + if s.is_ascii() && s.len() <= u8::MAX.into() { + Ok(ShortAsciiString(s)) + } else { + Err(ConversionError) + } + } +} +impl TryFrom<&str> for ShortAsciiString { + type Error = ConversionError; + + fn try_from(s: &str) -> Result { + ShortAsciiString::try_from(String::from(s)) + } +} +impl Writeable for ShortAsciiString { + #[inline] + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.len().write(w)?; + w.write_all(self.as_bytes()) + } +} +impl Readable for ShortAsciiString { + #[inline] + fn read(r: &mut R) -> Result { + 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)?; + ShortAsciiString::try_from(s).map_err(|_| DecodeError::InvalidValue) + } +} + impl Writeable for Duration { #[inline] fn write(&self, w: &mut W) -> Result<(), io::Error> { @@ -928,3 +987,32 @@ impl Readable for Duration { Ok(Duration::new(secs, nanos)) } } + + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + use std::io::Cursor; + use util::ser::{Readable, ShortAsciiString, Writeable}; + + #[test] + fn short_ascii_string_conversion() { + let short_s = ShortAsciiString::try_from("test").unwrap(); + assert_eq!(short_s.as_str(), "test"); + + ShortAsciiString::try_from("⚡").unwrap_err(); + + let mut large_vec = Vec::with_capacity(u8::MAX as usize + 1); + large_vec.resize(u8::MAX as usize + 1, u8::try_from('A').unwrap()); + ShortAsciiString::try_from(String::from_utf8(large_vec).unwrap()).unwrap_err(); + } + + #[test] + fn short_ascii_string_serialization() { + let short_s = ShortAsciiString::try_from("test").unwrap(); + let mut buf = Cursor::new(Vec::new()); + short_s.write(&mut buf).unwrap(); + buf.set_position(0); + assert_eq!(ShortAsciiString::read(&mut buf).unwrap().as_str(), "test"); + } +}