WIP SOCKSSocket

This commit is contained in:
sneurlax 2023-08-09 12:50:12 -05:00
parent 29341fc0b0
commit 1eeed74cf6
2 changed files with 100 additions and 70 deletions

View file

@ -14,6 +14,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mutex/mutex.dart'; import 'package:mutex/mutex.dart';
import 'package:stackwallet/networking/socks5.dart';
import 'package:stackwallet/networking/tor_service.dart'; import 'package:stackwallet/networking/tor_service.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/prefs.dart';
@ -35,7 +36,8 @@ class JsonRPC {
final _requestMutex = Mutex(); final _requestMutex = Mutex();
final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue(); final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue();
Socket? _socket; // TODO make a SocksSocket extension/wrapper or similar Socket? _socket;
SOCKSSocket? _socksSocket;
StreamSubscription<Uint8List>? _subscription; StreamSubscription<Uint8List>? _subscription;
void _dataHandler(List<int> data) { void _dataHandler(List<int> data) {
@ -79,7 +81,12 @@ class JsonRPC {
_requestQueue.nextIncompleteReq.then((req) { _requestQueue.nextIncompleteReq.then((req) {
if (req != null) { if (req != null) {
// \r\n required by electrumx server // \r\n required by electrumx server
if (_socket != null) {
_socket!.write('${req.jsonRequest}\r\n'); _socket!.write('${req.jsonRequest}\r\n');
}
if (_socksSocket != null) {
_socksSocket!.write('${req.jsonRequest}\r\n');
}
// TODO different timeout length? // TODO different timeout length?
req.initiateTimeout( req.initiateTimeout(
@ -96,6 +103,7 @@ class JsonRPC {
Duration requestTimeout, Duration requestTimeout,
) async { ) async {
await _requestMutex.protect(() async { await _requestMutex.protect(() async {
if (!Prefs.instance.useTor) {
if (_socket == null) { if (_socket == null) {
Logging.instance.log( Logging.instance.log(
"JsonRPC request: opening socket $host:$port", "JsonRPC request: opening socket $host:$port",
@ -103,6 +111,15 @@ class JsonRPC {
); );
await connect(); await connect();
} }
} else {
if (_socksSocket == null) {
Logging.instance.log(
"JsonRPC request: opening SOCKS socket $host:$port",
level: LogLevel.Info,
);
await connect();
}
}
}); });
final req = _JsonRPCRequest( final req = _JsonRPCRequest(
@ -141,6 +158,9 @@ class JsonRPC {
_subscription = null; _subscription = null;
_socket?.destroy(); _socket?.destroy();
_socket = null; _socket = null;
unawaited(_socksSocket?.close(keepOpen: false));
// TODO check that it's ok to not await this
_socksSocket = null;
// clean up remaining queue // clean up remaining queue
await _requestQueue.completeRemainingWithError( await _requestQueue.completeRemainingWithError(
@ -150,68 +170,7 @@ class JsonRPC {
} }
Future<void> connect() async { Future<void> connect() async {
if (_socket != null) { if (!Prefs.instance.useTor) {
throw Exception(
"JsonRPC attempted to connect to an already existing socket!",
);
}
if (Prefs.instance.useTor) {
if (proxyInfo == null) {
// TODO await tor / make sure it's running
proxyInfo = (
host: InternetAddress.loopbackIPv4.address,
port: TorService.sharedInstance.port
);
Logging.instance.log(
"ElectrumX.connect(): no tor proxy info, read $proxyInfo",
level: LogLevel.Warning);
}
// TODO connect to proxy socket...
// https://github.com/LacticWhale/socks_dart/blob/master/lib/src/client/socks_client.dart#L50C46-L50C56
// TODO implement ssl over tor
// if (useSSL) {
// _socket = await SecureSocket.connect(
// host,
// port,
// timeout: connectionTimeout,
// onBadCertificate: (_) => true,
// ); // TODO do not automatically trust bad certificates
// final _client = SocksSocket.protected(_socket, type);
// } else {
_socket = await Socket.connect(
proxyInfo!.host,
proxyInfo!.port,
timeout: connectionTimeout,
);
// final _client = SocksSocket.protected(
// _socket!, SocksConnectionType.connect
// );
// final InternetAddress _host =
// await InternetAddress.lookup(host).then((value) => value.first);
// var _socket = await SocksSocket.initialize(
// [
// ProxySettings(
// InternetAddress.loopbackIPv4,
// proxyInfo!.port,
// )
// ],
// _host,
// port,
// SocksConnectionType.connect,
// );
if (_socket == null) {
Logging.instance.log(
"JsonRPC.connect(): failed to connect to $host over tor proxy at $proxyInfo",
level: LogLevel.Error);
throw Exception("JsonRPC.connect(): failed to connect to tor proxy");
} else {
Logging.instance.log(
"JsonRPC.connect(): connected to $host over tor proxy at $proxyInfo",
level: LogLevel.Info);
}
} else {
if (useSSL) { if (useSSL) {
_socket = await SecureSocket.connect( _socket = await SecureSocket.connect(
host, host,
@ -226,6 +185,61 @@ class JsonRPC {
timeout: connectionTimeout, timeout: connectionTimeout,
); );
} }
} else {
if (proxyInfo == null) {
// TODO await tor / make sure it's running
proxyInfo = (
host: InternetAddress.loopbackIPv4.address,
port: TorService.sharedInstance.port
);
Logging.instance.log(
"ElectrumX.connect(): no tor proxy info, read $proxyInfo",
level: LogLevel.Warning);
}
// TODO connect to proxy socket...
// TODO implement ssl over tor
// if (useSSL) {
// _socket = await SecureSocket.connect(
// host,
// port,
// timeout: connectionTimeout,
// onBadCertificate: (_) => true,
// ); // TODO do not automatically trust bad certificates
// final _client = SocksSocket.protected(_socket, type);
// } else {
final sock = await RawSocket.connect(
InternetAddress.loopbackIPv4, proxyInfo!.port);
_socksSocket = SOCKSSocket(sock);
if (_socksSocket == null) {
Logging.instance.log(
"JsonRPC.connect(): failed to create SOCKS socket at $proxyInfo",
level: LogLevel.Error);
throw Exception(
"JsonRPC.connect(): failed to create SOCKS socket at $proxyInfo");
} else {
Logging.instance.log(
"JsonRPC.connect(): created SOCKS socket at $proxyInfo",
level: LogLevel.Info);
}
try {
if (!isIpAddress(host)) {
await _socksSocket!.connect("$host:$port");
} else {
await _socksSocket!.connectIp(InternetAddress(host), port);
}
Logging.instance.log(
"JsonRPC.connect(): connected to $host:$port over SOCKS socket at $proxyInfo",
level: LogLevel.Info);
} catch (e) {
Logging.instance.log(
"JsonRPC.connect(): failed to connect to $host over tor proxy at $proxyInfo, $e",
level: LogLevel.Error);
throw Exception(
"JsonRPC.connect(): failed to connect to tor proxy, $e");
}
} }
_subscription = _socket!.listen( _subscription = _socket!.listen(
@ -356,3 +370,14 @@ class JsonRPCResponse {
JsonRPCResponse({this.data, this.exception}); JsonRPCResponse({this.data, this.exception});
} }
bool isIpAddress(String host) {
try {
// if the string can be parsed into an InternetAddress, it's an IP.
InternetAddress(host);
return true;
} catch (e) {
// if parsing fails, it's not an IP.
return false;
}
}

View file

@ -377,4 +377,9 @@ class SOCKSSocket {
throw "Must be in RequestReady state, current state $_state"; throw "Must be in RequestReady state, current state $_state";
} }
} }
void write(Object? object) {
_sock.write(utf8.encode(object.toString()));
// TODO make sure the is correct; see _writeRequest above, may need to construct a SOCKSRequest from the data coming in
}
} }