Skip to content
Permalink
Browse files

Merge pull request #172 from Jigsaw-Code/bemasc-newservers

Add more server options
  • Loading branch information...
bemasc committed May 13, 2019
2 parents 860d16a + 1a5bc7b commit 6df0e4f0dbfee7940bb864c8fa0b1e336adbccea
Showing with 894 additions and 118 deletions.
  1. +2 −1 Android/app/src/main/java/app/intra/net/doh/DualStackResult.java
  2. +17 −11 Android/app/src/main/java/app/intra/net/doh/StandardServerConnection.java
  3. +30 −1 Android/app/src/main/java/app/intra/sys/IntraVpnService.java
  4. +13 −5 Android/app/src/main/java/app/intra/sys/PersistentState.java
  5. +1 −0 Android/app/src/main/java/app/intra/ui/settings/ServerChooser.java
  6. +101 −19 Android/app/src/main/java/app/intra/ui/settings/ServerChooserFragment.java
  7. +69 −54 Android/app/src/main/res/layout/servers.xml
  8. +0 −7 Android/app/src/main/res/menu/server.xml
  9. +10 −0 Android/app/src/main/res/values-ar-rXB/servers.xml
  10. +10 −0 Android/app/src/main/res/values-ar/servers.xml
  11. +2 −0 Android/app/src/main/res/values-ar/strings.xml
  12. +10 −0 Android/app/src/main/res/values-az/servers.xml
  13. +2 −0 Android/app/src/main/res/values-az/strings.xml
  14. +10 −0 Android/app/src/main/res/values-b+es+419/servers.xml
  15. +2 −0 Android/app/src/main/res/values-b+es+419/strings.xml
  16. +10 −0 Android/app/src/main/res/values-bg/servers.xml
  17. +2 −0 Android/app/src/main/res/values-bg/strings.xml
  18. +10 −0 Android/app/src/main/res/values-ca/servers.xml
  19. +2 −0 Android/app/src/main/res/values-ca/strings.xml
  20. +10 −0 Android/app/src/main/res/values-cs/servers.xml
  21. +2 −0 Android/app/src/main/res/values-cs/strings.xml
  22. +10 −0 Android/app/src/main/res/values-da/servers.xml
  23. +2 −0 Android/app/src/main/res/values-da/strings.xml
  24. +10 −0 Android/app/src/main/res/values-de/servers.xml
  25. +3 −1 Android/app/src/main/res/values-de/strings.xml
  26. +10 −0 Android/app/src/main/res/values-el/servers.xml
  27. +2 −0 Android/app/src/main/res/values-el/strings.xml
  28. +10 −0 Android/app/src/main/res/values-en-rGB/servers.xml
  29. +2 −0 Android/app/src/main/res/values-en-rGB/strings.xml
  30. +10 −0 Android/app/src/main/res/values-en-rXA/servers.xml
  31. +10 −0 Android/app/src/main/res/values-es/servers.xml
  32. +2 −0 Android/app/src/main/res/values-es/strings.xml
  33. +10 −0 Android/app/src/main/res/values-fa/servers.xml
  34. +2 −0 Android/app/src/main/res/values-fa/strings.xml
  35. +10 −0 Android/app/src/main/res/values-fi/servers.xml
  36. +2 −0 Android/app/src/main/res/values-fi/strings.xml
  37. +10 −0 Android/app/src/main/res/values-fil/servers.xml
  38. +2 −0 Android/app/src/main/res/values-fil/strings.xml
  39. +10 −0 Android/app/src/main/res/values-fr/servers.xml
  40. +2 −0 Android/app/src/main/res/values-fr/strings.xml
  41. +10 −0 Android/app/src/main/res/values-hi/servers.xml
  42. +2 −0 Android/app/src/main/res/values-hi/strings.xml
  43. +10 −0 Android/app/src/main/res/values-hr/servers.xml
  44. +2 −0 Android/app/src/main/res/values-hr/strings.xml
  45. +10 −0 Android/app/src/main/res/values-hu/servers.xml
  46. +2 −0 Android/app/src/main/res/values-hu/strings.xml
  47. +10 −0 Android/app/src/main/res/values-id/servers.xml
  48. +2 −0 Android/app/src/main/res/values-id/strings.xml
  49. +10 −0 Android/app/src/main/res/values-it/servers.xml
  50. +2 −0 Android/app/src/main/res/values-it/strings.xml
  51. +10 −0 Android/app/src/main/res/values-iw/servers.xml
  52. +2 −0 Android/app/src/main/res/values-iw/strings.xml
  53. +10 −0 Android/app/src/main/res/values-ja/servers.xml
  54. +4 −2 Android/app/src/main/res/values-ja/strings.xml
  55. +10 −0 Android/app/src/main/res/values-ko/servers.xml
  56. +2 −0 Android/app/src/main/res/values-ko/strings.xml
  57. +10 −0 Android/app/src/main/res/values-lt/servers.xml
  58. +2 −0 Android/app/src/main/res/values-lt/strings.xml
  59. +10 −0 Android/app/src/main/res/values-lv/servers.xml
  60. +2 −0 Android/app/src/main/res/values-lv/strings.xml
  61. +10 −0 Android/app/src/main/res/values-nl/servers.xml
  62. +2 −0 Android/app/src/main/res/values-nl/strings.xml
  63. +10 −0 Android/app/src/main/res/values-no/servers.xml
  64. +2 −0 Android/app/src/main/res/values-no/strings.xml
  65. +10 −0 Android/app/src/main/res/values-pl/servers.xml
  66. +2 −0 Android/app/src/main/res/values-pl/strings.xml
  67. +10 −0 Android/app/src/main/res/values-pt-rBR/servers.xml
  68. +2 −0 Android/app/src/main/res/values-pt-rBR/strings.xml
  69. +10 −0 Android/app/src/main/res/values-pt-rPT/servers.xml
  70. +2 −0 Android/app/src/main/res/values-pt-rPT/strings.xml
  71. +10 −0 Android/app/src/main/res/values-ro/servers.xml
  72. +2 −0 Android/app/src/main/res/values-ro/strings.xml
  73. +10 −0 Android/app/src/main/res/values-ru/servers.xml
  74. +2 −0 Android/app/src/main/res/values-ru/strings.xml
  75. +10 −0 Android/app/src/main/res/values-sk/servers.xml
  76. +2 −0 Android/app/src/main/res/values-sk/strings.xml
  77. +10 −0 Android/app/src/main/res/values-sl/servers.xml
  78. +2 −0 Android/app/src/main/res/values-sl/strings.xml
  79. +10 −0 Android/app/src/main/res/values-sr/servers.xml
  80. +2 −0 Android/app/src/main/res/values-sr/strings.xml
  81. +10 −0 Android/app/src/main/res/values-sv/servers.xml
  82. +2 −0 Android/app/src/main/res/values-sv/strings.xml
  83. +10 −0 Android/app/src/main/res/values-sw/servers.xml
  84. +2 −0 Android/app/src/main/res/values-sw/strings.xml
  85. +10 −0 Android/app/src/main/res/values-th/servers.xml
  86. +2 −0 Android/app/src/main/res/values-th/strings.xml
  87. +10 −0 Android/app/src/main/res/values-tr/servers.xml
  88. +2 −0 Android/app/src/main/res/values-tr/strings.xml
  89. +10 −0 Android/app/src/main/res/values-uk/servers.xml
  90. +2 −0 Android/app/src/main/res/values-uk/strings.xml
  91. +10 −0 Android/app/src/main/res/values-vi/servers.xml
  92. +2 −0 Android/app/src/main/res/values-vi/strings.xml
  93. +10 −0 Android/app/src/main/res/values-zh-rCN/servers.xml
  94. +2 −0 Android/app/src/main/res/values-zh-rCN/strings.xml
  95. +10 −0 Android/app/src/main/res/values-zh-rTW/servers.xml
  96. +2 −0 Android/app/src/main/res/values-zh-rTW/strings.xml
  97. +49 −0 Android/app/src/main/res/values/server_table.xml
  98. +57 −13 Android/app/src/main/res/values/servers.xml
  99. +8 −0 Android/app/src/main/res/values/strings.xml
  100. +8 −4 Android/app/src/test/java/app/intra/net/doh/StandardServerConnectionIntegrationTest.java
@@ -19,6 +19,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

@@ -45,7 +46,7 @@ public DualStackResult(String[] v4, String[] v6) {
}
}

DualStackResult(List<InetAddress> addresses) {
DualStackResult(Collection<InetAddress> addresses) {
for (InetAddress address : addresses) {
if (address instanceof Inet4Address) {
v4.add(address);
@@ -15,14 +15,18 @@
*/
package app.intra.net.doh;

import android.util.Log;
import app.intra.BuildConfig;
import app.intra.net.dns.DnsUdpQuery;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
@@ -36,16 +40,18 @@
* Allows the caller to perform DNS-over-HTTPS queries using the IETF draft protocol.
*/
public class StandardServerConnection implements ServerConnection {
// The class name is longer than Android's log tag length limit.
private static final String LOG_TAG = "StandardDOH";

private final String url;
private OkHttpClient client;
private final List<InetAddress> ips;
private final Collection<InetAddress> ips;

private class PinnedDns implements Dns {

private final List<InetAddress> ips;

PinnedDns(List<InetAddress> ips) {
PinnedDns(Collection<InetAddress> ips) {
DualStackResult result = new DualStackResult(ips);
this.ips = result.getInterleaved();
}
@@ -57,31 +63,31 @@

}

public static StandardServerConnection get(String url) {
public static StandardServerConnection get(String url, Collection<InetAddress> fixedIps) {
URL parsedUrl;
try {
parsedUrl = new URL(url);
} catch (MalformedURLException e) {
// TODO: log
Log.w(LOG_TAG, "Malformed URL: " + url);
return null;
}
if (!"https".equals(parsedUrl.getProtocol())) {
return null;
}
InetAddress[] ips;
Set<InetAddress> allIps = new HashSet<>(fixedIps);
try {
ips = InetAddress.getAllByName(parsedUrl.getHost());
List<InetAddress> resolvedIps = Arrays.asList(InetAddress.getAllByName(parsedUrl.getHost()));
allIps.addAll(resolvedIps);
} catch (UnknownHostException e) {
// TODO: log
return null;
Log.i(LOG_TAG, "Couldn't resolve server name: " + parsedUrl.getHost());
}
if (ips.length == 0) {
if (allIps.isEmpty()) {
return null;
}
return new StandardServerConnection(url, Arrays.asList(ips));
return new StandardServerConnection(url, allIps);
}

private StandardServerConnection(String url, List<InetAddress> ips) {
private StandardServerConnection(String url, Collection<InetAddress> ips) {
this.url = url;
this.ips = ips;

@@ -48,7 +48,13 @@
import app.intra.ui.MainActivity;
import com.crashlytics.android.Crashlytics;
import com.google.firebase.analytics.FirebaseAnalytics;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;

public class IntraVpnService extends VpnService implements NetworkListener,
SharedPreferences.OnSharedPreferenceChangeListener {
@@ -184,6 +190,28 @@ public ServerConnection getServerConnection() {
return serverConnection;
}

private Collection<InetAddress> getKnownIps(String url) {
String[] urls = getResources().getStringArray(R.array.urls);
String[] ips = getResources().getStringArray(R.array.ips);
for (int i = 0; i < urls.length; ++i) {
// TODO: Consider relaxing this equality condition to a match on
// just the domain.
if (urls[i].equals(url)) {
String[] ipStrings = ips[i].split(",");
List<InetAddress> ret = new ArrayList<>();
for (int j = 0; j < ipStrings.length; ++j) {
try {
ret.addAll(Arrays.asList(InetAddress.getAllByName(ipStrings[j])));
} catch (IOException e) {
Log.e(LOG_TAG, "Invalid IP address in servers resource");
}
}
return ret;
}
}
return new ArrayList<>();
}

@WorkerThread
private void updateServerConnection() {
// This method consists of three steps:
@@ -231,7 +259,8 @@ private void updateServerConnection() {
}
newConnection = googleServerConnection;
} else {
newConnection = StandardServerConnection.get(url);
Collection<InetAddress> ips = getKnownIps(url);
newConnection = StandardServerConnection.get(url, ips);
}

if (newConnection != null) {
@@ -17,6 +17,7 @@

import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.Log;
import app.intra.R;
@@ -78,7 +79,7 @@ public static void syncLegacyState(Context context) {

// There is no URL setting, so read the legacy server name.
SharedPreferences settings = getInternalState(context);
String defaultDomain = context.getResources().getStringArray(R.array.domains)[0];
String defaultDomain = context.getResources().getString(R.string.domain0);
String domain = settings.getString(SERVER_KEY, defaultDomain);

if (domain == null) {
@@ -87,12 +88,19 @@ public static void syncLegacyState(Context context) {
return;
}

// Get the corresponding URL.
String[] domains = context.getResources().getStringArray(R.array.domains);
// Special case: url 0 is the nonstandard DoH service on dns.google.com.
// TODO: Remove this special case once dns.google.com transitions to standard DoH.
String[] urls = context.getResources().getStringArray(R.array.urls);
String url = null;
for (int i = 0; i < domains.length; ++i) {
if (domains[i].equals(domain)) {
if (domain.equals(defaultDomain)) {
url = urls[0];
}

// Look for the corresponding URL among the other builtin servers.
for (int i = 1; i < urls.length; ++i) {
Uri parsed = Uri.parse(urls[i]);

if (domain.equals(parsed.getHost())) {
url = urls[i];
break;
}
@@ -65,6 +65,7 @@ private void initialize(Context context) {
context.getResources().getString(R.string.server_choice_summary);
defaultDomain =
context.getResources().getString(R.string.domain0);
setPositiveButtonText(R.string.intro_accept);
}

@Override
@@ -18,12 +18,18 @@
import android.app.Dialog;
import android.os.Bundle;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceDialogFragmentCompat;
@@ -36,11 +42,24 @@
*/

public class ServerChooserFragment extends PreferenceDialogFragmentCompat
implements RadioGroup.OnCheckedChangeListener, TextWatcher, EditText.OnEditorActionListener {
implements RadioGroup.OnCheckedChangeListener, TextWatcher, EditText.OnEditorActionListener,
OnItemSelectedListener {
private RadioGroup buttons = null;
private EditText text = null;

// Builtin servers
private Spinner spinner = null;
private TextView description = null;
private TextView serverWebsite = null;

// Custom server
private EditText customServerUrl = null;
private TextView warning = null;

// Builtin server resources, initialized in onBindDialogView.
private String[] urls = null;
private String[] descriptions = null;
private String[] websiteLinks = null;

static ServerChooserFragment newInstance(String key) {
final ServerChooserFragment fragment = new ServerChooserFragment();
final Bundle bundle = new Bundle(1);
@@ -51,19 +70,20 @@ static ServerChooserFragment newInstance(String key) {

private String getUrl() {
int checkedId = buttons.getCheckedRadioButtonId();
if (checkedId == R.id.pref_server_google) {
return getResources().getString(R.string.url0);
} else if (checkedId == R.id.pref_server_cloudflare) {
return getResources().getString(R.string.url1);
} else {
return text.getText().toString();
if (checkedId == R.id.pref_server_custom) {
return customServerUrl.getText().toString();
}

return urls[spinner.getSelectedItemPosition()];
}

private void updateUI() {
int checkedId = buttons.getCheckedRadioButtonId();
boolean custom = checkedId == R.id.pref_server_custom;
text.setEnabled(custom);
customServerUrl.setEnabled(custom);
spinner.setEnabled(!custom);
description.setEnabled(!custom);
serverWebsite.setEnabled(!custom);
if (custom) {
setValid(checkUrl(Untemplate.strip(getUrl())));
} else {
@@ -118,25 +138,87 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
return false;
}

@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
onBuiltinServerSelected(i);
}

private void onBuiltinServerSelected(int i) {
description.setText(descriptions[i]);

// Update website text with a link pointing to the correct website.
// The string resource contains a dummy hyperlink that must be replaced with a new link to
// the correct destination.
CharSequence template = getResources().getText(R.string.server_choice_website_notice);
SpannableStringBuilder websiteMessage = new SpannableStringBuilder(template);

// Trim leading whitespace introduced by indentation in strings.xml.
while (Character.isWhitespace(websiteMessage.charAt(0))) {
websiteMessage.delete(0, 1);
}

// Replace hyperlink with a new link for the current URL.
URLSpan templateLink =
websiteMessage.getSpans(0, websiteMessage.length(), URLSpan.class)[0];
int linkStart = websiteMessage.getSpanStart(templateLink);
int linkEnd = websiteMessage.getSpanEnd(templateLink);
websiteMessage.removeSpan(templateLink);
websiteMessage.setSpan(new URLSpan(websiteLinks[i]), linkStart, linkEnd, 0);

serverWebsite.setText(websiteMessage);
}

@Override
public void onNothingSelected(AdapterView<?> adapterView) {
updateUI();
}

@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
ServerChooser preference = (ServerChooser) getPreference();
String url = preference.getUrl();

urls = getResources().getStringArray(R.array.urls);
descriptions = getResources().getStringArray(R.array.descriptions);
websiteLinks = getResources().getStringArray(R.array.server_websites);

buttons = view.findViewById(R.id.pref_server_radio_group);
text = view.findViewById(R.id.custom_server_url);
spinner = view.findViewById(R.id.builtin_server_spinner);
description = view.findViewById(R.id.server_description);
serverWebsite = view.findViewById(R.id.server_website);
customServerUrl = view.findViewById(R.id.custom_server_url);
warning = view.findViewById(R.id.url_warning);
if (url == null || url.equals(getResources().getString(R.string.url0))) {
buttons.check(R.id.pref_server_google);
} else if (url.equals(getResources().getString(R.string.url1))) {
buttons.check(R.id.pref_server_cloudflare);

// Make website link clickable.
serverWebsite.setMovementMethod(LinkMovementMethod.getInstance());

// Check if we are using one of the built-in servers.
int index = -1;
if (url == null || url.isEmpty()) {
// TODO: Remove special case once Google DNS moves to standard DOH.
index = 0;
} else {
for (int i = 1; i < urls.length; ++i) {
if (urls[i].equals(url)) {
index = i;
break;
}
}
}

if (index >= 0) {
buttons.check(R.id.pref_server_builtin);
spinner.setSelection(index);
onBuiltinServerSelected(index);
} else {
buttons.check(R.id.pref_server_custom);
text.setText(url);
customServerUrl.setText(url);
}
buttons.setOnCheckedChangeListener(this);
text.addTextChangedListener(this);
text.setOnEditorActionListener(this);
spinner.setOnItemSelectedListener(this);
customServerUrl.addTextChangedListener(this);
customServerUrl.setOnEditorActionListener(this);
updateUI();
}

@@ -146,8 +228,8 @@ public void onDialogClosed(boolean positiveResult) {
ServerChooser preference = (ServerChooser) getPreference();
preference.setUrl(getUrl());
}
text.removeTextChangedListener(this);
text.setOnEditorActionListener(null);
customServerUrl.removeTextChangedListener(this);
customServerUrl.setOnEditorActionListener(null);
buttons.setOnCheckedChangeListener(null);
}

0 comments on commit 6df0e4f

Please sign in to comment.
You can’t perform that action at this time.