supabase / supabase-flutter

Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products.
https://supabase.com/
MIT License
656 stars 154 forks source link

Realtime: Unable to build WebSocket connection through Tor proxy #925

Closed nns52k closed 1 month ago

nns52k commented 1 month ago

Describe the bug

Unable to create a WebSocket connection to access Realtime on an .onion server.

To Reproduce

  1. Have a working Tor serving Supabase.
  2. Run this Dart script:
    
    import 'dart:io';
    import 'package:http/io_client.dart';
    import 'package:supabase/supabase.dart';
    import 'package:socks5_proxy/socks_client.dart';

void main(List arguments) async { HttpClient httpClient = await connectTorProxy(9050); final supabase = SupabaseClient( 'http://your_onion_address.onion', 'your_anon_key', httpClient: IOClient(httpClient), ); await listenToATable(supabase); } Future connectTorProxy(int portNumber) async { final httpClient = HttpClient(); SocksTCPClient.assignToHttpClient(httpClient, [ ProxySettings(InternetAddress.loopbackIPv4, portNumber), ]); return httpClient; } Future listenToATable(SupabaseClient supabase) async { final SupabaseQueryBuilder queryBuilder = supabase.from('countries'); // Assume we have SQL table countries. final SupabaseStreamFilterBuilder streamFilterBuilder = queryBuilder.stream(primaryKey: ['id']); streamFilterBuilder.listen((List<Map<String, dynamic>> resultSet) { print(supabase.realtime.isConnected); printResultSet(resultSet); }); } void printResultSet(List<Map<String, dynamic>> resultSet, {bool hasRowSeparator = true}) { for (Map<String, dynamic> row in resultSet) { row.forEach((String columnName, dynamic columnValue) { print('$columnName: $columnValue'); }); if (hasRowSeparator) print('----------'); } }


**Expected behavior**

Whenever the SQL table `countries` in the server is changed, our Dart script shall receive an update, but because of the bug, it doesn't. There is no WebSocket connection between our client app and the .onion server providing Supabase.

**Version (please complete the following information):**
On Linux/macOS

├── supabase 2.1.2 │ ├── functions_client 2.1.0 │ ├── gotrue 2.6.1 │ ├── postgrest 2.1.1 │ ├── realtime_client 2.0.4 │ ├── storage_client 2.0.1


**Additional context**

According to [Tor Project: FAQ | Tor onion services: How do I access onion services?](https://2019.www.torproject.org/docs/faq.html.en#AccessOnionServices), the client shan't lookup .onion address. Please note that the top-level domain is `.onion`. It will doom to fail to lookup .onion address, because .onion is only recognizable by Tor Network. The client shall simply pass the .onion address to Tor proxy without looking up the .onion address in advance.

When running the above code example that reproduces the bug, it will throw this exception:
``` the exception
SocketException (SocketException: Failed host lookup: 'your_onion_address.onion' (OS Error: Name or service not known, errno = -2))

The callstack of the DNS lookup that causes the exception is as follows:

_NativeSocket.staggeredLookup.<anonymous closure>.lookupAddresses (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_internal/vm/bin/socket_patch.dart:633)
_NativeSocket.staggeredLookup.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_internal/vm/bin/socket_patch.dart:655)
_runGuarded (~/.local/packages/flutter/bin/cache/dart-sdk/lib/async/stream_controller.dart:823)
_StreamController._subscribe.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/async/stream_controller.dart:702)
_BufferingStreamSubscription._guardCallback (~/.local/packages/flutter/bin/cache/dart-sdk/lib/async/stream_impl.dart:415)
_StreamController._subscribe (~/.local/packages/flutter/bin/cache/dart-sdk/lib/async/stream_controller.dart:701)
_ControllerStream._createSubscription (~/.local/packages/flutter/bin/cache/dart-sdk/lib/async/stream_controller.dart:836)
_StreamImpl.listen (~/.local/packages/flutter/bin/cache/dart-sdk/lib/async/stream_impl.dart:471)
_NativeSocket.tryConnectToResolvedAddresses (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_internal/vm/bin/socket_patch.dart:966)
_NativeSocket.startConnect.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_internal/vm/bin/socket_patch.dart:740)
<asynchronous gap> (Unknown Source:0)
_RawSocket.startConnect.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_internal/vm/bin/socket_patch.dart:1915)
<asynchronous gap> (Unknown Source:0)
Socket._startConnect.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_internal/vm/bin/socket_patch.dart:2140)
<asynchronous gap> (Unknown Source:0)
_ConnectionTarget.connect.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_http/http_impl.dart:2490)
<asynchronous gap> (Unknown Source:0)
_HttpClient._openUrl.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_http/http_impl.dart:2787)
<asynchronous gap> (Unknown Source:0)
_WebSocketImpl.connect.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_http/websocket_impl.dart:1021)
<asynchronous gap> (Unknown Source:0)
_WebSocketImpl.connect.<anonymous closure> (~/.local/packages/flutter/bin/cache/dart-sdk/lib/_http/websocket_impl.dart:1048)
<asynchronous gap> (Unknown Source:0)
new IOWebSocketChannel.connect.<anonymous closure> (~/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5/lib/io.dart:90)
<asynchronous gap> (Unknown Source:0)
nns52k commented 1 month ago

There is no way to pass a HttpClient to WebSocketChannel like what we can do to SupabaseClient, but defining HttpOverrides.createHttpClient and running all Supabase code in a HttpOverrides.runZoned is a workaround.