From 63b05e398a0bd2ad0b3c27609dfffcd09a754230 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Sun, 23 Oct 2022 08:12:59 -0500 Subject: [PATCH] Rework tor exceptions to be more useful (#4854) --- .../org/bitcoins/tor/Socks5Connection.scala | 69 +++++++-------- .../org/bitcoins/tor/TorConnectionError.scala | 86 +++++++++++++++++++ 2 files changed, 116 insertions(+), 39 deletions(-) create mode 100644 tor/src/main/scala/org/bitcoins/tor/TorConnectionError.scala diff --git a/tor/src/main/scala/org/bitcoins/tor/Socks5Connection.scala b/tor/src/main/scala/org/bitcoins/tor/Socks5Connection.scala index 7015cb2780..769ce34e99 100644 --- a/tor/src/main/scala/org/bitcoins/tor/Socks5Connection.scala +++ b/tor/src/main/scala/org/bitcoins/tor/Socks5Connection.scala @@ -73,7 +73,8 @@ class Socks5Connection( context.parent ! Socks5Connected(connectedAddress) isConnected = true case Failure(err) => - logger.error(s"Tor connection request failed to $target", err) + logger.error( + s"Tor connection request failed to $target errMsg=${err.toString}") } } @@ -125,18 +126,6 @@ object Socks5Connection { val NoAuth: Byte = 0x00 val PasswordAuth: Byte = 0x02 - val connectErrors: Map[Byte, String] = Map[Byte, String]( - (0x00, "Request granted"), - (0x01, "General failure"), - (0x02, "Connection not allowed by ruleset"), - (0x03, "Network unreachable"), - (0x04, "Host unreachable"), - (0x05, "Connection refused by destination host"), - (0x06, "TTL expired"), - (0x07, "Command not supported / protocol error"), - (0x08, "Address type not supported") - ) - def socks5Greeting(passwordAuth: Boolean): ByteString = ByteString( 0x05, // SOCKS version 0x01, // number of authentication methods supported @@ -213,34 +202,39 @@ object Socks5Connection { } } - def parseConnectedAddress(data: ByteString): InetSocketAddress = { + def tryParseConnectedAddress(data: ByteString): Try[InetSocketAddress] = { if (data(0) != 0x05) { throw Socks5Error("Invalid proxy version") } else { val status = data(1) if (status != 0) { - val errMsg = - connectErrors.getOrElse(status, s"Unknown SOCKS5 error $status") - throw Socks5Error(errMsg + s" data=$data") - } - data(3) match { - case 0x01 => - val ip = Array(data(4), data(5), data(6), data(7)) - val port = data(8).toInt << 8 | data(9) - new InetSocketAddress(InetAddress.getByAddress(ip), port) - case 0x03 => - val len = data(4) - val start = 5 - val end = start + len - val domain = data.slice(start, end).utf8String - val port = data(end).toInt << 8 | data(end + 1) - new InetSocketAddress(domain, port) - case 0x04 => - val ip = Array.ofDim[Byte](16) - data.copyToArray(ip, 4, 4 + ip.length) - val port = data(4 + ip.length).toInt << 8 | data(4 + ip.length + 1) - new InetSocketAddress(InetAddress.getByAddress(ip), port) - case b => throw Socks5Error(s"Unrecognized address type $b") + val connectionError = TorConnectionError.fromByte(status) + Failure(connectionError.exn) + } else { + data(3) match { + case 0x01 => + val ip = Array(data(4), data(5), data(6), data(7)) + val port = data(8).toInt << 8 | data(9) + val socket = + new InetSocketAddress(InetAddress.getByAddress(ip), port) + Success(socket) + case 0x03 => + val len = data(4) + val start = 5 + val end = start + len + val domain = data.slice(start, end).utf8String + val port = data(end).toInt << 8 | data(end + 1) + val socket = new InetSocketAddress(domain, port) + Success(socket) + case 0x04 => + val ip = Array.ofDim[Byte](16) + data.copyToArray(ip, 4, 4 + ip.length) + val port = data(4 + ip.length).toInt << 8 | data(4 + ip.length + 1) + val socket = + new InetSocketAddress(InetAddress.getByAddress(ip), port) + Success(socket) + case b => Failure(Socks5Error(s"Unrecognized address type $b")) + } } } } @@ -250,9 +244,6 @@ object Socks5Connection { def tryParseAuth(data: ByteString): Try[Boolean] = Try(parseAuth(data)) - def tryParseConnectedAddress(data: ByteString): Try[InetSocketAddress] = Try( - parseConnectedAddress(data)) - } case class Socks5ProxyParams( diff --git a/tor/src/main/scala/org/bitcoins/tor/TorConnectionError.scala b/tor/src/main/scala/org/bitcoins/tor/TorConnectionError.scala new file mode 100644 index 0000000000..38dd806526 --- /dev/null +++ b/tor/src/main/scala/org/bitcoins/tor/TorConnectionError.scala @@ -0,0 +1,86 @@ +package org.bitcoins.tor + +sealed trait TorConnectionError { + def prefix: Byte + + def exn: RuntimeException = new RuntimeException(toString) +} + +object TorConnectionError { + + val connectErrors: Map[Byte, String] = Map[Byte, String]( + (0x00, "Request granted"), + (0x01, "General failure"), + (0x02, "Connection not allowed by ruleset"), + (0x03, "Network unreachable"), + (0x04, "Host unreachable"), + (0x05, "Connection refused by destination host"), + (0x06, "TTL expired"), + (0x07, "Command not supported / protocol error"), + (0x08, "Address type not supported") + ) + + private val all: Vector[TorConnectionError] = Vector( + RequestGranted, + GeneralFailure, + ConnectionNotAllowed, + NetworkUnreachable, + HostUnreachable, + ConnectionRefusedByDestination, + TTLExpired, + CommandNotSupportedOrProtocolError, + AddressTypeNotSupported + ) + + def fromByte(byte: Byte): TorConnectionError = { + fromByteOpt(byte) match { + case Some(err) => err + case None => + sys.error(s"Could not find tor connection error by prefix=$byte") + } + } + + def fromByteOpt(byte: Byte): Option[TorConnectionError] = { + all.find(_.prefix == byte) + } + + case object RequestGranted extends TorConnectionError { + override val prefix: Byte = 0.toByte + } + + case object GeneralFailure extends TorConnectionError { + override val prefix: Byte = 1.toByte + + override def toString: String = + s"General failure, this likely means tor timed out when trying to connect. Try using telnet to reproduce error." + } + + case object ConnectionNotAllowed extends TorConnectionError { + override val prefix: Byte = 0x02.toByte + override val toString: String = "Connection not allowed by ruleset" + } + + case object NetworkUnreachable extends TorConnectionError { + override val prefix: Byte = 0x03.toByte + } + + case object HostUnreachable extends TorConnectionError { + override val prefix: Byte = 0x04.toByte + } + + case object ConnectionRefusedByDestination extends TorConnectionError { + override val prefix: Byte = 0x05.toByte + } + + case object TTLExpired extends TorConnectionError { + override val prefix: Byte = 0x06.toByte + } + + case object CommandNotSupportedOrProtocolError extends TorConnectionError { + override val prefix: Byte = 0x07.toByte + } + + case object AddressTypeNotSupported extends TorConnectionError { + override val prefix: Byte = 0x08.toByte + } +}