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.
This commit is contained in:
Miguel Jacq 2022-03-28 12:14:02 +11:00
parent 52cb5cf71a
commit 9c4a598fc0
No known key found for this signature in database
GPG Key ID: EEA4341C6D97A0B6
2 changed files with 67 additions and 43 deletions

View File

@ -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)

View File

@ -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(