Skip to content

Commit

Permalink
Adds DNS hostname to NetAddress
Browse files Browse the repository at this point in the history
Supports Bolt 7 DNS hostnames specified by lightning/bolts#911.
  • Loading branch information
wvanlint committed Jun 21, 2022
1 parent abf6564 commit 33f8be2
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 19 deletions.
10 changes: 5 additions & 5 deletions lightning/src/ln/channelmanager.rs
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
60 changes: 46 additions & 14 deletions lightning/src/ln/msgs.rs
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, ShortAsciiString};

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

Expand Down Expand Up @@ -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
Expand All @@ -452,6 +460,7 @@ impl NetAddress {
&NetAddress::IPv6 {..} => { 2 },
&NetAddress::OnionV2(_) => { 3 },
&NetAddress::OnionV3 {..} => { 4 },
&NetAddress::DNSHostname {..} => { 5 },
}
}

Expand All @@ -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 {
Expand All @@ -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(())
}
Expand Down Expand Up @@ -523,6 +538,12 @@ impl Readable for Result<NetAddress, u8> {
port: Readable::read(reader)?,
}))
},
5 => {
Ok(Ok(NetAddress::DNSHostname {
hostname: Readable::read(reader)?,
port: Readable::read(reader)?,
}))
},
_ => return Ok(Err(byte)),
}
}
Expand Down Expand Up @@ -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;
Expand All @@ -1843,6 +1864,7 @@ mod tests {

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

#[test]
fn encoding_channel_reestablish_no_secret() {
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
Expand All @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions lightning/src/util/errors.rs
Expand Up @@ -78,6 +78,10 @@ impl fmt::Debug for APIError {
}
}

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

#[inline]
pub(crate) fn get_onion_debug_field(error_code: u16) -> (&'static str, usize) {
match error_code & 0xff {
Expand Down
96 changes: 96 additions & 0 deletions lightning/src/util/ser.rs
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 All @@ -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;
Expand Down Expand Up @@ -913,6 +916,67 @@ impl Readable for String {
}
}

/// Wraps a string with additional validation for serialization purposes.
/// The string is guaranteed to contain only ASCII characters and its length
/// is guaranteed to be representable by a single byte.
/// This serialization is used for example by Bolt 7 DNS hostnames.
#[derive(Clone, Debug, PartialEq)]
pub struct ShortAsciiString(String);
impl ShortAsciiString {
/// Returns the length of the string with an appropriate return type for the validated length.
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<ShortAsciiString> for String {
fn from(short_s: ShortAsciiString) -> Self {
short_s.0
}
}
impl TryFrom<String> for ShortAsciiString {
type Error = ConversionError;

fn try_from(s: String) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
ShortAsciiString::try_from(String::from(s))
}
}
impl Writeable for ShortAsciiString {
#[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 ShortAsciiString {
#[inline]
fn read<R: Read>(r: &mut R) -> Result<ShortAsciiString, 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)?;
ShortAsciiString::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 +992,35 @@ impl Readable for Duration {
Ok(Duration::new(secs, nanos))
}
}

#[cfg(test)]
mod tests {
use core::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("⚡").expect_err(
"Expected non-ASCII string to fail conversion"
);

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()).expect_err(
"Expected string with exceeding length to fail conversion"
);
}

#[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");
}
}

0 comments on commit 33f8be2

Please sign in to comment.