From d007f07dcf2c1f476a5ae3dad45fec2b3fd78bca Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Sat, 6 Apr 2024 12:40:02 -0700 Subject: [PATCH 1/4] Break up read and write v1 functions --- proxy/src/bin/v1.rs | 45 +++++++++-------------- proxy/src/lib.rs | 88 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 37 deletions(-) diff --git a/proxy/src/bin/v1.rs b/proxy/src/bin/v1.rs index 04ff294..0e22da6 100644 --- a/proxy/src/bin/v1.rs +++ b/proxy/src/bin/v1.rs @@ -1,10 +1,11 @@ //! Simple V1 proxy to test hooking things up end to end. +use bip324_proxy::{read_v1, write_v1}; use tokio::net::{TcpListener, TcpStream}; use tokio::select; /// Validate and bootstrap proxy connection. -async fn proxy_conn(mut client: TcpStream) -> Result<(), Box> { +async fn proxy_conn(mut client: TcpStream) -> Result<(), bip324_proxy::Error> { let remote_ip = bip324_proxy::peek_addr(&client).await?; println!("Initialing remote connection {}.", remote_ip); @@ -12,43 +13,29 @@ async fn proxy_conn(mut client: TcpStream) -> Result<(), Box { + res = read_v1(&mut client_reader) => { match res { - Ok(bytes) => { - println!("Responded to {} with {bytes} bytes.", remote_ip); - if bytes == 0 { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "Client closed connection. Disconnecting.", - ))); - } + Ok(msg) => { + println!("Read {} message from client, writing to remote.", msg.cmd()); + write_v1(&mut remote_writer, msg).await?; }, - Err(_) => { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "Remote closed connection: Disconnecting.", - ))); + Err(e) => { + return Err(e); }, } }, - res = tokio::io::copy(&mut remote_reader, &mut client_writer) => { + res = read_v1(&mut remote_reader) => { match res { - Ok(bytes) => { - println!("Responded to local with {bytes} bytes."); - if bytes == 0 { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "Client closed connection. Disconnecting.", - ))); - } + Ok(msg) => { + println!("Read {} message from remote, writing to client.", msg.cmd()); + write_v1(&mut client_writer, msg).await?; }, - Err(_) => { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "Client closed connection. Disconnecting.", - ))); + Err(e) => { + return Err(e); }, } }, diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index 4763268..6608465 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -1,28 +1,68 @@ -use std::{error::Error, net::SocketAddr}; +//! Helper functions for bitcoin p2p proxies. +//! +//! The V1 and V2 p2p protocols have different header encodings, so a proxy has to do +//! a little more work than just encrypt/decrypt. -use bitcoin::consensus::Decodable; +use std::fmt; +use std::net::SocketAddr; + +use bitcoin::consensus::{Decodable, Encodable}; +pub use bitcoin::p2p::message::RawNetworkMessage; use bitcoin::p2p::{Address, Magic}; use hex::prelude::*; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::TcpStream; /// Default to local host on port 1324. pub const DEFAULT_PROXY: &str = "127.0.0.1:1324"; /// Default to the signet network. -const DEFAULT_MAGIC: Magic = Magic::SIGNET; +const DEFAULT_MAGIC: Magic = Magic::BITCOIN; +/// All V1 messages have a 24 byte header. +const V1_HEADER_BYTES: usize = 24; + +/// An error occured while establishing the proxy connection or during the main loop. +#[derive(Debug)] +pub enum Error { + UnknownMessage, + Network, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::UnknownMessage => write!(f, "Received unknown message"), + Error::Network => write!(f, "Network error"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::UnknownMessage => None, + Error::Network => None, + } + } +} + +// Convert IO errors. +impl From for Error { + fn from(_: std::io::Error) -> Self { + Error::Network + } +} /// Peek the input stream and pluck the remote address based on the version message. -pub async fn peek_addr(client: &TcpStream) -> Result> { +pub async fn peek_addr(client: &TcpStream) -> Result { println!("Validating client connection."); // Peek the first 70 bytes, 24 for the header and 46 for the first part of the version message. let mut peek_bytes = [0; 70]; let n = client.peek(&mut peek_bytes).await?; + println!("Bytes read from local connection: {n}"); println!("Got magic: {}", &peek_bytes[0..4].to_lower_hex_string()); if DEFAULT_MAGIC.to_bytes().ne(&peek_bytes[0..4]) { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "Invalid magic.", - ))); + return Err(Error::UnknownMessage); } let mut addr_bytes = &peek_bytes[44..]; @@ -31,3 +71,35 @@ pub async fn peek_addr(client: &TcpStream) -> Result> Ok(socket_addr) } + +/// Read a network message off of the input stream. +pub async fn read_v1(input: &mut T) -> Result { + let mut header_bytes = [0; V1_HEADER_BYTES]; + input.read_exact(&mut header_bytes).await?; + + let payload_len = u32::from_le_bytes( + header_bytes[16..20] + .try_into() + .expect("4 header length bytes"), + ); + + let mut payload = vec![0u8; payload_len as usize]; + input.read_exact(&mut payload).await?; + let mut full_message = header_bytes.to_vec(); + full_message.append(&mut payload); + let message = RawNetworkMessage::consensus_decode(&mut full_message.as_slice()) + .expect("raw network message"); + + Ok(message) +} + +/// Write the network message to the output stream. +pub async fn write_v1( + output: &mut T, + msg: RawNetworkMessage, +) -> Result<(), Error> { + let mut write_bytes = vec![]; + msg.consensus_encode(&mut write_bytes) + .expect("write to vector"); + Ok(output.write_all(&write_bytes).await?) +} From 839c5f01989ec194a95416973370ed579c90217b Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Sat, 6 Apr 2024 12:53:29 -0700 Subject: [PATCH 2/4] Slightly better error variants --- proxy/src/lib.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index 6608465..7fea70e 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -19,19 +19,25 @@ pub const DEFAULT_PROXY: &str = "127.0.0.1:1324"; const DEFAULT_MAGIC: Magic = Magic::BITCOIN; /// All V1 messages have a 24 byte header. const V1_HEADER_BYTES: usize = 24; +/// Hex encoding of ascii version command. +const VERSION_COMMAND: [u8; 12] = [ + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, +]; /// An error occured while establishing the proxy connection or during the main loop. #[derive(Debug)] pub enum Error { - UnknownMessage, + WrongNetwork, + WrongCommand, Network, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::UnknownMessage => write!(f, "Received unknown message"), + Error::WrongNetwork => write!(f, "Recieved message on wrong network"), Error::Network => write!(f, "Network error"), + Error::WrongCommand => write!(f, "Recieved message with wrong command"), } } } @@ -39,8 +45,9 @@ impl fmt::Display for Error { impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Error::UnknownMessage => None, Error::Network => None, + Error::WrongNetwork => None, + Error::WrongCommand => None, } } } @@ -57,14 +64,21 @@ pub async fn peek_addr(client: &TcpStream) -> Result { println!("Validating client connection."); // Peek the first 70 bytes, 24 for the header and 46 for the first part of the version message. let mut peek_bytes = [0; 70]; - let n = client.peek(&mut peek_bytes).await?; + client.peek(&mut peek_bytes).await?; - println!("Bytes read from local connection: {n}"); + // Check network magic. println!("Got magic: {}", &peek_bytes[0..4].to_lower_hex_string()); if DEFAULT_MAGIC.to_bytes().ne(&peek_bytes[0..4]) { - return Err(Error::UnknownMessage); + return Err(Error::WrongNetwork); } + // Check command. + println!("Got command: {}", &peek_bytes[4..16].to_lower_hex_string()); + if VERSION_COMMAND.ne(&peek_bytes[4..16]) { + return Err(Error::WrongCommand); + } + + // Pull off address. let mut addr_bytes = &peek_bytes[44..]; let remote_addr = Address::consensus_decode(&mut addr_bytes).expect("network address bytes"); let socket_addr = remote_addr.socket_addr().expect("IP"); From defb5178d84650ae25183df668c143b06266f866 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Sat, 6 Apr 2024 13:18:40 -0700 Subject: [PATCH 3/4] Add network error to type --- proxy/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index 7fea70e..c3b082f 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -29,14 +29,14 @@ const VERSION_COMMAND: [u8; 12] = [ pub enum Error { WrongNetwork, WrongCommand, - Network, + Network(std::io::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::WrongNetwork => write!(f, "Recieved message on wrong network"), - Error::Network => write!(f, "Network error"), + Error::Network(e) => write!(f, "Network error {}", e), Error::WrongCommand => write!(f, "Recieved message with wrong command"), } } @@ -45,7 +45,7 @@ impl fmt::Display for Error { impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Error::Network => None, + Error::Network(e) => Some(e), Error::WrongNetwork => None, Error::WrongCommand => None, } @@ -54,8 +54,8 @@ impl std::error::Error for Error { // Convert IO errors. impl From for Error { - fn from(_: std::io::Error) -> Self { - Error::Network + fn from(e: std::io::Error) -> Self { + Error::Network(e) } } From ab72e99c3871a6164ccb5698ccc33e8f687070d5 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Sat, 6 Apr 2024 13:20:16 -0700 Subject: [PATCH 4/4] Flip back to signet --- proxy/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index c3b082f..c79de5f 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -16,7 +16,7 @@ use tokio::net::TcpStream; /// Default to local host on port 1324. pub const DEFAULT_PROXY: &str = "127.0.0.1:1324"; /// Default to the signet network. -const DEFAULT_MAGIC: Magic = Magic::BITCOIN; +const DEFAULT_MAGIC: Magic = Magic::SIGNET; /// All V1 messages have a 24 byte header. const V1_HEADER_BYTES: usize = 24; /// Hex encoding of ascii version command.