@@ -36,14 +36,17 @@ use crate::ln::onion_utils;
3636use crate :: onion_message;
3737
3838use crate :: prelude:: * ;
39+ use core:: convert:: TryFrom ;
3940use core:: fmt;
4041use core:: fmt:: Debug ;
42+ use core:: str:: FromStr ;
4143use crate :: io:: { self , Read } ;
4244use crate :: io_extras:: read_to_end;
4345
4446use crate :: events:: { MessageSendEventsProvider , OnionMessageProvider } ;
4547use crate :: util:: logger;
4648use crate :: util:: ser:: { LengthReadable , Readable , ReadableArgs , Writeable , Writer , WithoutLength , FixedLengthReader , HighZeroBytesDroppedBigSize , Hostname , TransactionU16LenLimited , BigSize } ;
49+ use crate :: util:: base32;
4750
4851use 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.
9041007pub enum UnsignedGossipMessage < ' a > {
9051008 /// An unsigned channel announcement.
@@ -2472,12 +2575,14 @@ impl_writeable_msg!(GossipTimestampFilter, {
24722575
24732576#[ cfg( test) ]
24742577mod 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