From 9c4a598fc01b43f112107fa63b32ce0ed17e2ad1 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 28 Mar 2022 12:14:02 +1100 Subject: [PATCH] Support for Censorship Circumvention API's /default route. Fall back to trying Censorship API's /default route if no other bridges found. At time of writing, this returns a couple of built-in obfs4 bridges. If even that request fails, we will still fall back to our own full list of obfs4 bridges. --- cli/onionshare_cli/censorship.py | 96 ++++++++++++++++------------ desktop/onionshare/connection_tab.py | 14 +++- 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/cli/onionshare_cli/censorship.py b/cli/onionshare_cli/censorship.py index 4ab5c366..4b1bb040 100644 --- a/cli/onionshare_cli/censorship.py +++ b/cli/onionshare_cli/censorship.py @@ -100,7 +100,7 @@ class CensorshipCircumvention(object): if r.status_code != 200: self.common.log( "CensorshipCircumvention", - "censorship_obtain_map", + "request_map", f"status_code={r.status_code}", ) return False @@ -110,7 +110,7 @@ class CensorshipCircumvention(object): if "errors" in result: self.common.log( "CensorshipCircumvention", - "censorship_obtain_map", + "request_map", f"errors={result['errors']}", ) return False @@ -138,7 +138,7 @@ class CensorshipCircumvention(object): if country: self.common.log( "CensorshipCircumvention", - "censorship_obtain_settings", + "request_settings", f"Trying to obtain bridges for country={country}", ) data = {"country": country} @@ -154,7 +154,7 @@ class CensorshipCircumvention(object): if r.status_code != 200: self.common.log( "CensorshipCircumvention", - "censorship_obtain_settings", + "request_settings", f"status_code={r.status_code}", ) return False @@ -164,7 +164,7 @@ class CensorshipCircumvention(object): if "errors" in result: self.common.log( "CensorshipCircumvention", - "censorship_obtain_settings", + "request_settings", f"errors={result['errors']}", ) return False @@ -175,7 +175,7 @@ class CensorshipCircumvention(object): if not "settings" in result: self.common.log( "CensorshipCircumvention", - "censorship_obtain_settings", + "request_settings", "No settings found for this country", ) return False @@ -200,7 +200,7 @@ class CensorshipCircumvention(object): if r.status_code != 200: self.common.log( "CensorshipCircumvention", - "censorship_obtain_builtin_bridges", + "request_builtin_bridges", f"status_code={r.status_code}", ) return False @@ -210,7 +210,7 @@ class CensorshipCircumvention(object): if "errors" in result: self.common.log( "CensorshipCircumvention", - "censorship_obtain_builtin_bridges", + "request_builtin_bridges", f"errors={result['errors']}", ) return False @@ -237,42 +237,15 @@ class CensorshipCircumvention(object): f"Obtained bridges: {bridges}", ) bridge_strings = bridges["bridge_strings"] - bridge_type = bridges["type"] - bridge_source = bridges["source"] - # If the recommended bridge source is to use the built-in - # bridges, set that in our settings, as if the user had - # selected the built-in bridges for a specific PT themselves. - # - if bridge_source == "builtin": - self.common.log( - "CensorshipCircumvention", - "save_settings", - "Will be using built-in bridges", - ) - self.settings.set("bridges_type", "built-in") - if bridge_type == "obfs4": - self.settings.set("bridges_builtin_pt", "obfs4") - if bridge_type == "snowflake": - self.settings.set("bridges_builtin_pt", "snowflake") - if bridge_type == "meek": - self.settings.set("bridges_builtin_pt", "meek-azure") + self.settings.set("bridges_type", "custom") + + # Sanity check the bridges provided from the Tor API before saving + bridges_checked = self.common.check_bridges_valid(bridge_strings) + + if bridges_checked: + self.settings.set("bridges_custom", "\n".join(bridges_checked)) bridges_ok = True - else: - self.common.log( - "CensorshipCircumvention", - "save_settings", - "Will be using custom bridges", - ) - # Any other type of bridge we can treat as custom. - self.settings.set("bridges_type", "custom") - - # Sanity check the bridges provided from the Tor API before saving - bridges_checked = self.common.check_bridges_valid(bridge_strings) - - if bridges_checked: - self.settings.set("bridges_custom", "\n".join(bridges_checked)) - bridges_ok = True # If we got any good bridges, save them to settings and return. if bridges_ok: @@ -291,3 +264,42 @@ class CensorshipCircumvention(object): "Could not use any of the obtained bridges.", ) return False + + + def request_default_bridges(self): + """ + Retrieves the list of default fall-back bridges from the Tor Project. + + These are intended for when no censorship settings were found for a + specific country, but maybe there was some connection issue anyway. + """ + if not self.api_proxies: + return False + endpoint = "https://bridges.torproject.org/moat/circumvention/defaults" + try: + r = requests.get( + endpoint, + headers={"Content-Type": "application/vnd.api+json"}, + proxies=self.api_proxies, + ) + if r.status_code != 200: + self.common.log( + "CensorshipCircumvention", + "request_default_bridges", + f"status_code={r.status_code}", + ) + return False + + result = r.json() + + if "errors" in result: + self.common.log( + "CensorshipCircumvention", + "request_default_bridges", + f"errors={result['errors']}", + ) + return False + + return result + except requests.exceptions.RequestException as e: + raise CensorshipCircumventionError(e) diff --git a/desktop/onionshare/connection_tab.py b/desktop/onionshare/connection_tab.py index 1cf3c376..0dedf55d 100644 --- a/desktop/onionshare/connection_tab.py +++ b/desktop/onionshare/connection_tab.py @@ -181,7 +181,9 @@ class AutoConnectTab(QtWidgets.QWidget): self.tor_con.start(self.curr_settings) def _got_no_bridges(self): - # If we got no bridges, try connecting again using built-in obfs4 bridges + # If we got no bridges, even after trying the default bridges + # provided by the Censorship API, try connecting again using + # our built-in obfs4 bridges self.curr_settings.set("bridges_type", "built-in") self.curr_settings.set("bridges_builtin_pt", "obfs4") self.curr_settings.set("bridges_enabled", True) @@ -244,6 +246,16 @@ class AutoConnectTab(QtWidgets.QWidget): bridge_settings = self.censorship_circumvention.request_settings( country=country ) + + if not bridge_settings: + # Fall back to trying the default bridges from the API + self.common.log( + "AutoConnectTab", + "use_bridge_connect_clicked", + "Falling back to trying default bridges provided by the Censorship Circumvention API", + ) + bridge_settings = self.censorship_circumvention.request_default_bridges() + self.common.gui.meek.cleanup() if bridge_settings and self.censorship_circumvention.save_settings(