Skip to content

Commit 56b168a

Browse files
committed
implement from_str trait for NetAddress
1 parent 387b32a commit 56b168a

File tree

1 file changed

+160
-10
lines changed

1 file changed

+160
-10
lines changed

lightning/src/ln/msgs.rs

Lines changed: 160 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ use crate::ln::onion_utils;
3636
use crate::onion_message;
3737

3838
use crate::prelude::*;
39+
use core::convert::TryFrom;
3940
use core::fmt;
4041
use core::fmt::Debug;
42+
use core::str::FromStr;
4143
use crate::io::{self, Read};
4244
use crate::io_extras::read_to_end;
4345

4446
use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
4547
use crate::util::logger;
4648
use crate::util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize};
49+
use crate::util::base32;
4750

4851
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
4952

@@ -900,6 +903,106 @@ impl Readable for NetAddress {
900903
}
901904
}
902905

906+
/// [`NetAddress`] error variants
907+
#[derive(Debug, Eq, PartialEq, Clone)]
908+
pub enum NetAddressParseError {
909+
/// Socket address (IPv4/IPv6) parsing error
910+
SocketAddrParse,
911+
/// Invalid input format
912+
InvalidInput,
913+
/// Invalid port
914+
InvalidPort,
915+
/// Invalid onion v3 address
916+
InvalidOnionV3,
917+
}
918+
919+
impl fmt::Display for NetAddressParseError {
920+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
921+
match self {
922+
NetAddressParseError::SocketAddrParse => write!(f, "Socket address (IPv4/IPv6) parsing error"),
923+
NetAddressParseError::InvalidInput => write!(f, "{}", "Invalid input format. \
924+
Expected: \"<ipv4>:<port>\", \"[<ipv6>]:<port>\", \"<onion address>.onion:<port>\" or \"<hostname>:<port>\""),
925+
NetAddressParseError::InvalidPort => write!(f, "Invalid port"),
926+
NetAddressParseError::InvalidOnionV3 => write!(f, "Invalid onion v3 address"),
927+
}
928+
}
929+
}
930+
931+
#[cfg(feature = "std")]
932+
impl From<std::net::SocketAddrV4> for NetAddress {
933+
fn from(addr: std::net::SocketAddrV4) -> Self {
934+
NetAddress::IPv4 { addr: addr.ip().octets(), port: addr.port() }
935+
}
936+
}
937+
938+
#[cfg(feature = "std")]
939+
impl From<std::net::SocketAddrV6> for NetAddress {
940+
fn from(addr: std::net::SocketAddrV6) -> Self {
941+
NetAddress::IPv6 { addr: addr.ip().octets(), port: addr.port() }
942+
}
943+
}
944+
945+
#[cfg(feature = "std")]
946+
impl From<std::net::SocketAddr> for NetAddress {
947+
fn from(addr: std::net::SocketAddr) -> Self {
948+
match {
949+
addr
950+
} {
951+
std::net::SocketAddr::V4(addr) => addr.into(),
952+
std::net::SocketAddr::V6(addr) => addr.into(),
953+
}
954+
}
955+
}
956+
957+
fn parse_onion_address(host: &str, port: u16) -> Result<NetAddress, NetAddressParseError> {
958+
if host.ends_with(".onion") {
959+
let domain = &host[..host.len() - ".onion".len()];
960+
if domain.len() != 56 {
961+
return Err(NetAddressParseError::InvalidOnionV3);
962+
}
963+
let onion = base32::Alphabet::RFC4648 { padding: false }.decode(&domain).map_err(|_| NetAddressParseError::InvalidOnionV3)?;
964+
if onion.len() != 35 {
965+
return Err(NetAddressParseError::InvalidOnionV3);
966+
}
967+
let version = onion[0];
968+
let first_checksum_flag = onion[1];
969+
let second_checksum_flag = onion[2];
970+
let mut ed25519_pubkey = [0; 32];
971+
ed25519_pubkey.copy_from_slice(&onion[3..35]);
972+
let checksum = u16::from_be_bytes([first_checksum_flag, second_checksum_flag]);
973+
return Ok(NetAddress::OnionV3 { ed25519_pubkey, checksum, version, port });
974+
975+
} else {
976+
return Err(NetAddressParseError::InvalidInput);
977+
}
978+
}
979+
980+
#[cfg(feature = "std")]
981+
impl FromStr for NetAddress {
982+
type Err = NetAddressParseError;
983+
984+
fn from_str(s: &str) -> Result<Self, Self::Err> {
985+
match std::net::SocketAddr::from_str(s) {
986+
Ok(addr) => Ok(addr.into()),
987+
Err(_) => {
988+
let trimmed_input = match s.rfind(":") {
989+
Some(pos) => pos,
990+
None => return Err(NetAddressParseError::InvalidInput),
991+
};
992+
let host = &s[..trimmed_input];
993+
let port: u16 = s[trimmed_input + 1..].parse().map_err(|_| NetAddressParseError::InvalidPort)?;
994+
if host.ends_with(".onion") {
995+
return parse_onion_address(host, port);
996+
};
997+
if let Ok(hostname) = Hostname::try_from(s[..trimmed_input].to_string()) {
998+
return Ok(NetAddress::Hostname { hostname, port });
999+
};
1000+
return Err(NetAddressParseError::SocketAddrParse)
1001+
},
1002+
}
1003+
}
1004+
}
1005+
9031006
/// Represents the set of gossip messages that require a signature from a node's identity key.
9041007
pub enum UnsignedGossipMessage<'a> {
9051008
/// An unsigned channel announcement.
@@ -2472,12 +2575,14 @@ impl_writeable_msg!(GossipTimestampFilter, {
24722575

24732576
#[cfg(test)]
24742577
mod tests {
2578+
use std::convert::TryFrom;
24752579
use bitcoin::blockdata::constants::ChainHash;
24762580
use bitcoin::{Transaction, PackedLockTime, TxIn, Script, Sequence, Witness, TxOut};
24772581
use hex;
24782582
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
24792583
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
24802584
use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket};
2585+
use crate::ln::msgs::NetAddress;
24812586
use crate::routing::gossip::{NodeAlias, NodeId};
24822587
use crate::util::ser::{Writeable, Readable, Hostname, TransactionU16LenLimited};
24832588

@@ -2493,11 +2598,13 @@ mod tests {
24932598

24942599
use crate::io::{self, Cursor};
24952600
use crate::prelude::*;
2496-
use core::convert::TryFrom;
24972601
use core::str::FromStr;
2498-
24992602
use crate::chain::transaction::OutPoint;
25002603

2604+
#[cfg(feature = "std")]
2605+
use std::net::{Ipv4Addr, Ipv6Addr};
2606+
use crate::ln::msgs::NetAddressParseError;
2607+
25012608
#[test]
25022609
fn encoding_channel_reestablish() {
25032610
let public_key = {
@@ -2663,32 +2770,32 @@ mod tests {
26632770
};
26642771
let mut addresses = Vec::new();
26652772
if ipv4 {
2666-
addresses.push(msgs::NetAddress::IPv4 {
2773+
addresses.push(NetAddress::IPv4 {
26672774
addr: [255, 254, 253, 252],
26682775
port: 9735
26692776
});
26702777
}
26712778
if ipv6 {
2672-
addresses.push(msgs::NetAddress::IPv6 {
2779+
addresses.push(NetAddress::IPv6 {
26732780
addr: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240],
26742781
port: 9735
26752782
});
26762783
}
26772784
if onionv2 {
2678-
addresses.push(msgs::NetAddress::OnionV2(
2785+
addresses.push(NetAddress::OnionV2(
26792786
[255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]
26802787
));
26812788
}
26822789
if onionv3 {
2683-
addresses.push(msgs::NetAddress::OnionV3 {
2790+
addresses.push(NetAddress::OnionV3 {
26842791
ed25519_pubkey: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224],
26852792
checksum: 32,
26862793
version: 16,
26872794
port: 9735
26882795
});
26892796
}
26902797
if hostname {
2691-
addresses.push(msgs::NetAddress::Hostname {
2798+
addresses.push(NetAddress::Hostname {
26922799
hostname: Hostname::try_from(String::from("host")).unwrap(),
26932800
port: 9735,
26942801
});
@@ -3296,10 +3403,10 @@ mod tests {
32963403
let shutdown = msgs::Shutdown {
32973404
channel_id: [2; 32],
32983405
scriptpubkey:
3299-
if script_type == 1 { Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey() }
3406+
if script_type == 1 { Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey() }
33003407
else if script_type == 2 { Address::p2sh(&script, Network::Testnet).unwrap().script_pubkey() }
33013408
else if script_type == 3 { Address::p2wpkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).unwrap().script_pubkey() }
3302-
else { Address::p2wsh(&script, Network::Testnet).script_pubkey() },
3409+
else { Address::p2wsh(&script, Network::Testnet).script_pubkey() },
33033410
};
33043411
let encoded_value = shutdown.encode();
33053412
let mut target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap();
@@ -3504,7 +3611,7 @@ mod tests {
35043611
}.encode(), hex::decode("00000000014001010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202").unwrap());
35053612
let init_msg = msgs::Init { features: InitFeatures::from_le_bytes(vec![]),
35063613
networks: Some(vec![mainnet_hash]),
3507-
remote_network_address: Some(msgs::NetAddress::IPv4 {
3614+
remote_network_address: Some(NetAddress::IPv4 {
35083615
addr: [127, 0, 0, 1],
35093616
port: 1000,
35103617
}),
@@ -3869,4 +3976,47 @@ mod tests {
38693976
}
38703977
Ok(encoded_payload)
38713978
}
3979+
3980+
#[test]
3981+
#[cfg(feature = "std")]
3982+
fn test_net_address_from_str() {
3983+
assert_eq!(NetAddress::IPv4 {
3984+
addr: Ipv4Addr::new(127, 0, 0, 1).octets(),
3985+
port: 1234,
3986+
}, NetAddress::from_str("127.0.0.1:1234").unwrap());
3987+
3988+
assert_eq!(NetAddress::IPv6 {
3989+
addr: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).octets(),
3990+
port: 1234,
3991+
}, NetAddress::from_str("[0:0:0:0:0:0:0:1]:1234").unwrap());
3992+
assert_eq!(
3993+
NetAddress::Hostname {
3994+
hostname: Hostname::try_from("lightning-node.mydomain.com".to_string()).unwrap(),
3995+
port: 1234,
3996+
}, NetAddress::from_str("lightning-node.mydomain.com:1234").unwrap());
3997+
assert_eq!(
3998+
NetAddress::Hostname {
3999+
hostname: Hostname::try_from("example.com".to_string()).unwrap(),
4000+
port: 1234,
4001+
}, NetAddress::from_str("example.com:1234").unwrap());
4002+
assert_eq!(NetAddress::OnionV3 {
4003+
ed25519_pubkey: [37, 24, 75, 5, 25, 73, 117, 194, 139, 102, 182, 107, 4, 105, 247, 246, 85,
4004+
111, 177, 172, 49, 137, 167, 155, 64, 221, 163, 47, 31, 33, 71, 3],
4005+
checksum: 48326,
4006+
version: 121,
4007+
port: 1234
4008+
}, NetAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234").unwrap());
4009+
assert_eq!(Err(NetAddressParseError::InvalidOnionV3), NetAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6.onion:1234"));
4010+
assert_eq!(Err(NetAddressParseError::InvalidInput), NetAddress::from_str("127.0.0.1@1234"));
4011+
assert_eq!(Err(NetAddressParseError::InvalidInput), "".parse::<NetAddress>());
4012+
assert!(NetAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion.onion:9735:94").is_err());
4013+
assert!(NetAddress::from_str("wrong$%#.com:1234").is_err());
4014+
assert_eq!(Err(NetAddressParseError::InvalidPort), NetAddress::from_str("example.com:wrong"));
4015+
assert!("localhost".parse::<NetAddress>().is_err());
4016+
assert!("localhost:invalid-port".parse::<NetAddress>().is_err());
4017+
assert!( "invalid-onion-v3-hostname.onion:8080".parse::<NetAddress>().is_err());
4018+
assert!("b32.example.onion:invalid-port".parse::<NetAddress>().is_err());
4019+
assert!("invalid-address".parse::<NetAddress>().is_err());
4020+
assert!(NetAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion.onion:1234").is_err());
4021+
}
38724022
}

0 commit comments

Comments
 (0)