Skip to content

Commit

Permalink
Parse SocketAddress::OnionV2
Browse files Browse the repository at this point in the history
  • Loading branch information
yanganto committed Oct 18, 2023
1 parent edc5490 commit 789bcc7
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 64 deletions.
119 changes: 56 additions & 63 deletions lightning/src/ln/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,8 +774,13 @@ pub enum SocketAddress {
/// An old-style Tor onion address/port on which the peer is listening.
///
/// This field is deprecated and the Tor network generally no longer supports V2 Onion
/// addresses. Thus, the details are not parsed here.
OnionV2([u8; 12]),
/// addresses.
OnionV2{
/// The base32 of first 80 bits
addr: [u8; 10],
/// The port on which the node is listening
port: u16,
},
/// A new-style Tor onion address/port on which the peer is listening.
///
/// To create the human-readable "hostname", concatenate the ED25519 pubkey, checksum, and version,
Expand Down Expand Up @@ -805,7 +810,7 @@ impl SocketAddress {
match self {
&SocketAddress::TcpIpV4 {..} => { 1 },
&SocketAddress::TcpIpV6 {..} => { 2 },
&SocketAddress::OnionV2(_) => { 3 },
&SocketAddress::OnionV2 {..} => { 3 },
&SocketAddress::OnionV3 {..} => { 4 },
&SocketAddress::Hostname {..} => { 5 },
}
Expand All @@ -816,7 +821,7 @@ impl SocketAddress {
match self {
&SocketAddress::TcpIpV4 { .. } => { 6 },
&SocketAddress::TcpIpV6 { .. } => { 18 },
&SocketAddress::OnionV2(_) => { 12 },
&SocketAddress::OnionV2 { .. } => { 12 },
&SocketAddress::OnionV3 { .. } => { 37 },
// Consists of 1-byte hostname length, hostname bytes, and 2-byte port.
&SocketAddress::Hostname { ref hostname, .. } => { u16::from(hostname.len()) + 3 },
Expand All @@ -842,9 +847,10 @@ impl Writeable for SocketAddress {
addr.write(writer)?;
port.write(writer)?;
},
&SocketAddress::OnionV2(bytes) => {
&SocketAddress::OnionV2{ ref addr, ref port } => {
3u8.write(writer)?;
bytes.write(writer)?;
addr.write(writer)?;
port.write(writer)?;
},
&SocketAddress::OnionV3 { ref ed25519_pubkey, ref checksum, ref version, ref port } => {
4u8.write(writer)?;
Expand Down Expand Up @@ -879,7 +885,10 @@ impl Readable for Result<SocketAddress, u8> {
port: Readable::read(reader)?,
}))
},
3 => Ok(Ok(SocketAddress::OnionV2(Readable::read(reader)?))),
3 => Ok(Ok(SocketAddress::OnionV2 {
addr: Readable::read(reader)?,
port: Readable::read(reader)?,
})),
4 => {
Ok(Ok(SocketAddress::OnionV3 {
ed25519_pubkey: Readable::read(reader)?,
Expand Down Expand Up @@ -918,10 +927,8 @@ pub enum SocketAddressParseError {
InvalidInput,
/// Invalid port
InvalidPort,
/// Invalid onion v2 address
InvalidOnionV2,
/// Invalid onion v3 address
InvalidOnionV3,
/// Invalid onion address
InvalidOnion,
}

impl fmt::Display for SocketAddressParseError {
Expand All @@ -931,8 +938,7 @@ impl fmt::Display for SocketAddressParseError {
SocketAddressParseError::InvalidInput => write!(f, "Invalid input format. \
Expected: \"<ipv4>:<port>\", \"[<ipv6>]:<port>\", \"<onion address>.onion:<port>\" or \"<hostname>:<port>\""),
SocketAddressParseError::InvalidPort => write!(f, "Invalid port"),
SocketAddressParseError::InvalidOnionV2 => write!(f, "Invalid onion v2 address"),
SocketAddressParseError::InvalidOnionV3 => write!(f, "Invalid onion v3 address"),
SocketAddressParseError::InvalidOnion => write!(f, "Invalid onion address"),
}
}
}
Expand Down Expand Up @@ -961,39 +967,23 @@ impl From<std::net::SocketAddr> for SocketAddress {
}
}

/// Parses an OnionV2 host and port into a [`SocketAddress::OnionV2`].
/// Parses an Onion host and port into a [`SocketAddress::OnionV3`].
///
/// The host part must end with ".onion".
pub fn parse_onion_v2_address(host: &str) -> Result<SocketAddress, SocketAddressParseError> {
pub fn parse_onion_address(host: &str, port: u16) -> Result<SocketAddress, SocketAddressParseError> {
if host.ends_with(".onion") {
let domain = &host[..host.len() - ".onion".len()];
if domain.len() != 16 {
return Err(SocketAddressParseError::InvalidOnionV2);
if domain.len() != 56 && domain.len() !=16 {
return Err(SocketAddressParseError::InvalidOnion);
}
let onion = base32::Alphabet::RFC4648 { padding: false }.decode(&domain).map_err(|_| SocketAddressParseError::InvalidOnionV2)?;
if onion.len() != 12 {
return Err(SocketAddressParseError::InvalidOnionV2);
let onion = base32::Alphabet::RFC4648 { padding: false }.decode(&domain).map_err(|_| SocketAddressParseError::InvalidOnion)?;
if onion.len() != 35 && onion.len() != 10 {
return Err(SocketAddressParseError::InvalidOnion);
}
let mut bytes = [0; 12];
bytes.copy_from_slice(&onion);
return Ok(SocketAddress::OnionV2(bytes));
} else {
return Err(SocketAddressParseError::InvalidInput);
}
}

/// Parses an OnionV3 host and port into a [`SocketAddress::OnionV3`].
///
/// The host part must end with ".onion".
pub fn parse_onion_v3_address(host: &str, port: u16) -> Result<SocketAddress, SocketAddressParseError> {
if host.ends_with(".onion") {
let domain = &host[..host.len() - ".onion".len()];
if domain.len() != 56 {
return Err(SocketAddressParseError::InvalidOnionV3);
}
let onion = base32::Alphabet::RFC4648 { padding: false }.decode(&domain).map_err(|_| SocketAddressParseError::InvalidOnionV3)?;
if onion.len() != 35 {
return Err(SocketAddressParseError::InvalidOnionV3);
if onion.len() == 10 {
let mut addr = [0; 10];
addr.copy_from_slice(&onion);
return Ok(SocketAddress::OnionV2{ addr, port });
}
let version = onion[0];
let first_checksum_flag = onion[1];
Expand All @@ -1009,9 +999,9 @@ pub fn parse_onion_v3_address(host: &str, port: u16) -> Result<SocketAddress, So
}

/// [`SocketAddress::OnionV2`] to onion address string
pub fn to_onion_v2_string(bytes: &[u8; 12]) -> String {
let onion = base32::Alphabet::RFC4648 { padding: false }.encode(&bytes[..]);
format!("{onion}.onion")
pub fn to_onion_v2_string(addr: &[u8; 10], port: &u16) -> String {
let onion = base32::Alphabet::RFC4648 { padding: false }.encode(&addr[..]);
format!("{onion}.onion:{port}")
}

/// [`SocketAddress::OnionV3`] to onion address string
Expand All @@ -1029,7 +1019,7 @@ impl Display for SocketAddress {
match self {
SocketAddress::TcpIpV4{addr, port} => write!(f, "{}:{port}", std::net::Ipv4Addr::from(*addr))?,
SocketAddress::TcpIpV6{addr, port} => write!(f, "[{}]:{port}", std::net::Ipv6Addr::from(*addr))?,
SocketAddress::OnionV2(bytes) => write!(f, "{}", to_onion_v2_string(&bytes))?,
SocketAddress::OnionV2{addr, port} => write!(f, "{}", to_onion_v2_string(addr, port))?,
SocketAddress::OnionV3 {
ed25519_pubkey,
checksum,
Expand All @@ -1052,13 +1042,12 @@ impl FromStr for SocketAddress {
Err(_) => {
let trimmed_input = match s.rfind(":") {
Some(pos) => pos,
None if s.ends_with(".onion") => return parse_onion_v2_address(s),
_ => return Err(SocketAddressParseError::InvalidInput),
None => return Err(SocketAddressParseError::InvalidInput),
};
let host = &s[..trimmed_input];
let port: u16 = s[trimmed_input + 1..].parse().map_err(|_| SocketAddressParseError::InvalidPort)?;
if host.ends_with(".onion") {
return parse_onion_v3_address(host, port);
return parse_onion_address(host, port);
};
if let Ok(hostname) = Hostname::try_from(s[..trimmed_input].to_string()) {
return Ok(SocketAddress::Hostname { hostname, port });
Expand Down Expand Up @@ -2915,9 +2904,10 @@ mod tests {
});
}
if onionv2 {
addresses.push(msgs::SocketAddress::OnionV2(
[255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]
));
addresses.push(msgs::SocketAddress::OnionV2 {
addr: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246],
port: 9735,
});
}
if onionv3 {
addresses.push(msgs::SocketAddress::OnionV3 {
Expand Down Expand Up @@ -4125,19 +4115,19 @@ mod tests {
#[test]
#[cfg(feature = "std")]
fn test_socket_address_from_str() {
let v4 = SocketAddress::TcpIpV4 {
let tcpip_v4 = SocketAddress::TcpIpV4 {
addr: Ipv4Addr::new(127, 0, 0, 1).octets(),
port: 1234,
};
assert_eq!(v4, SocketAddress::from_str("127.0.0.1:1234").unwrap());
assert_eq!(v4, SocketAddress::from_str(&v4.to_string()).unwrap());
assert_eq!(tcpip_v4, SocketAddress::from_str("127.0.0.1:1234").unwrap());
assert_eq!(tcpip_v4, SocketAddress::from_str(&tcpip_v4.to_string()).unwrap());

let v6 = SocketAddress::TcpIpV6 {
let tcpip_v6 = SocketAddress::TcpIpV6 {
addr: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).octets(),
port: 1234,
};
assert_eq!(v6, SocketAddress::from_str("[0:0:0:0:0:0:0:1]:1234").unwrap());
assert_eq!(v6, SocketAddress::from_str(&v6.to_string()).unwrap());
assert_eq!(tcpip_v6, SocketAddress::from_str("[0:0:0:0:0:0:0:1]:1234").unwrap());
assert_eq!(tcpip_v6, SocketAddress::from_str(&tcpip_v6.to_string()).unwrap());

let hostname = SocketAddress::Hostname {
hostname: Hostname::try_from("lightning-node.mydomain.com".to_string()).unwrap(),
Expand All @@ -4146,21 +4136,24 @@ mod tests {
assert_eq!(hostname, SocketAddress::from_str("lightning-node.mydomain.com:1234").unwrap());
assert_eq!(hostname, SocketAddress::from_str(&hostname.to_string()).unwrap());

let v2 = SocketAddress::OnionV2 ([37, 24, 75, 5, 25, 73, 117, 194, 139, 102, 182, 107]);
assert_eq!(v2, SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234").unwrap());
assert_eq!(v2, SocketAddress::from_str(&v2.to_string()).unwrap());
let onion_v2 = SocketAddress::OnionV2{
addr: [40, 4, 64, 185, 202, 19, 162, 75, 90, 200],
port: 80,
};
assert_eq!(onion_v2, SocketAddress::from_str("facebookcorewwwi.onion:80").unwrap());
assert_eq!(onion_v2, SocketAddress::from_str(&onion_v2.to_string()).unwrap());

let v3 = SocketAddress::OnionV3 {
let onion_v3 = SocketAddress::OnionV3 {
ed25519_pubkey: [37, 24, 75, 5, 25, 73, 117, 194, 139, 102, 182, 107, 4, 105, 247, 246, 85,
111, 177, 172, 49, 137, 167, 155, 64, 221, 163, 47, 31, 33, 71, 3],
checksum: 48326,
version: 121,
port: 1234
};
assert_eq!(v3, SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234").unwrap());
assert_eq!(v3, SocketAddress::from_str(&v3.to_string()).unwrap());
assert_eq!(onion_v3, SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234").unwrap());
assert_eq!(onion_v3, SocketAddress::from_str(&onion_v3.to_string()).unwrap());

assert_eq!(Err(SocketAddressParseError::InvalidOnionV3), SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6.onion:1234"));
assert_eq!(Err(SocketAddressParseError::InvalidOnion), SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6.onion:1234"));
assert_eq!(Err(SocketAddressParseError::InvalidInput), SocketAddress::from_str("127.0.0.1@1234"));
assert_eq!(Err(SocketAddressParseError::InvalidInput), "".parse::<SocketAddress>());
assert!(SocketAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion.onion:9735:94").is_err());
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/util/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ macro_rules! impl_array {

impl_array!(3, u8); // for rgb, ISO 4712 code
impl_array!(4, u8); // for IPv4
impl_array!(12, u8); // for OnionV2
impl_array!(10, u8); // for OnionV2
impl_array!(16, u8); // for IPv6
impl_array!(32, u8); // for channel id & hmac
impl_array!(PUBLIC_KEY_SIZE, u8); // for PublicKey
Expand Down

0 comments on commit 789bcc7

Please sign in to comment.