Added DNS configuration functionality. (#24)

* Changed app icon from default to Yggdrasil leaf.

* Added workaround for DNS-reslver and fix for unmetered networks.

* Added DNS configuration functionality.

* Changed DNS configuration UI. Disabled DNS config by default. Added DNS fix for Chrome-based browsers.
This commit is contained in:
Revertron 2022-10-30 22:14:30 +01:00 committed by GitHub
parent 24573625a4
commit 772dfdef8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 620 additions and 26 deletions

View file

@ -55,6 +55,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference-ktx:1.1.0'
testImplementation 'junit:junit:4.+' testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

View file

@ -16,17 +16,19 @@
android:parentActivityName=".MainActivity" /> android:parentActivityName=".MainActivity" />
<activity android:name=".PeersActivity" <activity android:name=".PeersActivity"
android:parentActivityName=".MainActivity" /> android:parentActivityName=".MainActivity" />
<activity android:name=".MainActivity"> <activity android:name=".MainActivity" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".DnsActivity" android:exported="false" />
<service <service
android:name=".PacketTunnelProvider" android:name=".PacketTunnelProvider"
android:permission="android.permission.BIND_VPN_SERVICE"> android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService" /> <action android:name="android.net.VpnService" />
</intent-filter> </intent-filter>

View file

@ -0,0 +1,166 @@
package eu.neilalexander.yggdrasil
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import com.google.android.material.textfield.TextInputEditText
const val KEY_DNS_SERVERS = "dns_servers"
const val KEY_ENABLE_CHROME_FIX = "enable_chrome_fix"
const val DEFAULT_DNS_SERVERS = "302:7991::53,302:db60::53,300:6223::53,301:1088::53"
class DnsActivity : AppCompatActivity() {
private lateinit var config: ConfigurationProxy
private lateinit var inflater: LayoutInflater
private lateinit var serversTableLayout: TableLayout
private lateinit var serversTableLabel: TextView
private lateinit var serversTableHint: TextView
private lateinit var addServerButton: ImageButton
private lateinit var enableChromeFix: Switch
private lateinit var servers: MutableList<String>
private lateinit var preferences: SharedPreferences
@SuppressLint("ApplySharedPref")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dns)
config = ConfigurationProxy(applicationContext)
inflater = LayoutInflater.from(this)
serversTableLayout = findViewById(R.id.configuredDnsTableLayout)
serversTableLabel = findViewById(R.id.configuredDnsLabel)
serversTableHint = findViewById(R.id.configuredDnsHint)
enableChromeFix = findViewById(R.id.enableChromeFix)
addServerButton = findViewById(R.id.addServerButton)
addServerButton.setOnClickListener {
val view = inflater.inflate(R.layout.dialog_add_dns_server, null)
val input = view.findViewById<TextInputEditText>(R.id.addDnsInput)
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
builder.setTitle(getString(R.string.dns_add_server_dialog_title))
builder.setView(view)
builder.setPositiveButton(getString(R.string.add)) { dialog, _ ->
servers.add(input.text.toString())
preferences.edit().apply {
putString(KEY_DNS_SERVERS, servers.joinToString(","))
commit()
}
dialog.dismiss()
updateConfiguredServers()
}
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
}
enableChromeFix.setOnCheckedChangeListener { _, isChecked ->
preferences.edit().apply {
putBoolean(KEY_ENABLE_CHROME_FIX, isChecked)
commit()
}
}
preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
servers = if (serverString!!.isNotEmpty()) {
serverString.split(",").toMutableList()
} else {
mutableListOf()
}
updateUsableServers()
}
override fun onResume() {
super.onResume()
updateConfiguredServers()
enableChromeFix.isChecked = preferences.getBoolean(KEY_ENABLE_CHROME_FIX, false)
}
@SuppressLint("ApplySharedPref")
private fun updateConfiguredServers() {
when (servers.size) {
0 -> {
serversTableLayout.visibility = View.GONE
serversTableLabel.text = getString(R.string.dns_no_configured_servers)
serversTableHint.text = getText(R.string.dns_configured_servers_hint_empty)
}
else -> {
serversTableLayout.visibility = View.VISIBLE
serversTableLabel.text = getString(R.string.dns_configured_servers)
serversTableHint.text = getText(R.string.dns_configured_servers_hint)
serversTableLayout.removeAllViewsInLayout()
for (i in servers.indices) {
val server = servers[i]
val view = inflater.inflate(R.layout.peers_configured, null)
view.findViewById<TextView>(R.id.addressValue).text = server
view.findViewById<ImageButton>(R.id.deletePeerButton).tag = i
view.findViewById<ImageButton>(R.id.deletePeerButton).setOnClickListener { button ->
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle("Remove ${server}?")
builder.setPositiveButton(getString(R.string.remove)) { dialog, _ ->
servers.removeAt(button.tag as Int)
preferences.edit().apply {
this.putString(KEY_DNS_SERVERS, servers.joinToString(","))
this.commit()
}
dialog.dismiss()
updateConfiguredServers()
}
builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
builder.show()
}
serversTableLayout.addView(view)
}
}
}
}
@SuppressLint("ApplySharedPref")
private fun updateUsableServers() {
val usableTableLayout: TableLayout = findViewById(R.id.usableDnsTableLayout)
val defaultServers = DEFAULT_DNS_SERVERS.split(",")
defaultServers.forEach {
val server = it
val view = inflater.inflate(R.layout.dns_server_usable, null)
view.findViewById<TextView>(R.id.serverValue).text = server
val addButton = view.findViewById<ImageButton>(R.id.addButton)
addButton.tag = server
addButton.setOnClickListener { button ->
servers.add(button.tag as String)
preferences.edit().apply {
this.putString(KEY_DNS_SERVERS, servers.joinToString(","))
this.commit()
}
updateConfiguredServers()
}
view.setOnLongClickListener {
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.dns_server_info_dialog_title))
builder.setMessage(getText(R.string.dns_server_info_revertron))
builder.setPositiveButton(getString(R.string.ok)) { dialog, _ ->
dialog.dismiss()
}
builder.show()
true
}
usableTableLayout.addView(view)
}
}
}

View file

@ -27,6 +27,8 @@ class MainActivity : AppCompatActivity() {
private lateinit var coordinatesLabel: TextView private lateinit var coordinatesLabel: TextView
private lateinit var peersLabel: TextView private lateinit var peersLabel: TextView
private lateinit var peersRow: TableRow private lateinit var peersRow: TableRow
private lateinit var dnsLabel: TextView
private lateinit var dnsRow: TableRow
private lateinit var settingsRow: TableRow private lateinit var settingsRow: TableRow
private fun start() { private fun start() {
@ -47,13 +49,15 @@ class MainActivity : AppCompatActivity() {
findViewById<TextView>(R.id.versionValue).text = Mobile.getVersion() findViewById<TextView>(R.id.versionValue).text = Mobile.getVersion()
enabledSwitch = findViewById(R.id.enableMulticastBeacon) enabledSwitch = findViewById(R.id.enableYggdrasil)
enabledLabel = findViewById(R.id.yggdrasilStatusLabel) enabledLabel = findViewById(R.id.yggdrasilStatusLabel)
ipAddressLabel = findViewById(R.id.ipAddressValue) ipAddressLabel = findViewById(R.id.ipAddressValue)
subnetLabel = findViewById(R.id.subnetValue) subnetLabel = findViewById(R.id.subnetValue)
coordinatesLabel = findViewById(R.id.coordinatesValue) coordinatesLabel = findViewById(R.id.coordinatesValue)
peersLabel = findViewById(R.id.peersValue) peersLabel = findViewById(R.id.peersValue)
peersRow = findViewById(R.id.peersTableRow) peersRow = findViewById(R.id.peersTableRow)
dnsLabel = findViewById(R.id.dnsValue)
dnsRow = findViewById(R.id.dnsTableRow)
settingsRow = findViewById(R.id.settingsTableRow) settingsRow = findViewById(R.id.settingsTableRow)
enabledLabel.setTextColor(Color.GRAY) enabledLabel.setTextColor(Color.GRAY)
@ -82,6 +86,12 @@ class MainActivity : AppCompatActivity() {
startActivity(intent) startActivity(intent)
} }
dnsRow.isClickable = true
dnsRow.setOnClickListener {
val intent = Intent(this, DnsActivity::class.java)
startActivity(intent)
}
settingsRow.isClickable = true settingsRow.isClickable = true
settingsRow.setOnClickListener { settingsRow.setOnClickListener {
val intent = Intent(this, SettingsActivity::class.java) val intent = Intent(this, SettingsActivity::class.java)
@ -94,6 +104,18 @@ class MainActivity : AppCompatActivity() {
LocalBroadcastManager.getInstance(this).registerReceiver( LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, IntentFilter(PacketTunnelState.RECEIVER_INTENT) receiver, IntentFilter(PacketTunnelState.RECEIVER_INTENT)
) )
val preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
if (serverString!!.isNotEmpty()) {
val servers = serverString.split(",")
dnsLabel.text = when (servers.size) {
0 -> "No servers"
1 -> "1 server"
else -> "${servers.size} servers"
}
} else {
dnsLabel.text = "No servers"
}
} }
private val receiver: BroadcastReceiver = object : BroadcastReceiver() { private val receiver: BroadcastReceiver = object : BroadcastReceiver() {

View file

@ -17,6 +17,8 @@ import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread import kotlin.concurrent.thread
private const val TAG = "PacketTunnelProvider"
class PacketTunnelProvider: VpnService() { class PacketTunnelProvider: VpnService() {
companion object { companion object {
const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.MESSAGE" const val RECEIVER_INTENT = "eu.neilalexander.yggdrasil.PacketTunnelProvider.MESSAGE"
@ -50,16 +52,16 @@ class PacketTunnelProvider: VpnService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) { if (intent == null) {
Log.d("PacketTunnelProvider", "Intent is null") Log.d(TAG, "Intent is null")
return START_NOT_STICKY return START_NOT_STICKY
} }
return when (intent.action ?: ACTION_STOP) { return when (intent.action ?: ACTION_STOP) {
ACTION_STOP -> { ACTION_STOP -> {
Log.d("PacketTunnelProvider", "Stopping...") Log.d(TAG, "Stopping...")
stop(); START_NOT_STICKY stop(); START_NOT_STICKY
} }
else -> { else -> {
Log.d("PacketTunnelProvider", "Starting...") Log.d(TAG, "Starting...")
start(); START_STICKY start(); START_STICKY
} }
} }
@ -70,11 +72,11 @@ class PacketTunnelProvider: VpnService() {
return return
} }
Log.d("PacketTunnelProvider", config.getJSON().toString()) Log.d(TAG, config.getJSON().toString())
yggdrasil.startJSON(config.getJSONByteArray()) yggdrasil.startJSON(config.getJSONByteArray())
val address = yggdrasil.addressString val address = yggdrasil.addressString
var builder = Builder() val builder = Builder()
.addAddress(address, 7) .addAddress(address, 7)
.addRoute("200::", 7) .addRoute("200::", 7)
// We do this to trick the DNS-resolver into thinking that we have "regular" IPv6, // We do this to trick the DNS-resolver into thinking that we have "regular" IPv6,
@ -97,6 +99,21 @@ class PacketTunnelProvider: VpnService() {
builder.setMetered(false) builder.setMetered(false)
} }
val preferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this.baseContext)
val serverString = preferences.getString(KEY_DNS_SERVERS, "")
if (serverString!!.isNotEmpty()) {
val servers = serverString.split(",")
if (servers.isNotEmpty()) {
servers.forEach {
Log.i(TAG, "Using DNS server $it")
builder.addDnsServer(it)
}
}
}
if (preferences.getBoolean(KEY_ENABLE_CHROME_FIX, false)) {
builder.addRoute("2001:4860:4860::8888", 128)
}
parcel = builder.establish() parcel = builder.establish()
val parcel = parcel val parcel = parcel
if (parcel == null || !parcel.fileDescriptor.valid()) { if (parcel == null || !parcel.fileDescriptor.valid()) {
@ -123,7 +140,7 @@ class PacketTunnelProvider: VpnService() {
intent.putExtra("ip", yggdrasil.addressString) intent.putExtra("ip", yggdrasil.addressString)
intent.putExtra("subnet", yggdrasil.subnetString) intent.putExtra("subnet", yggdrasil.subnetString)
intent.putExtra("coords", yggdrasil.coordsString) intent.putExtra("coords", yggdrasil.coordsString)
intent.putExtra("peers", JSONArray(yggdrasil.peersJSON).length()) intent.putExtra("peers", yggdrasil.peersJSON)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent) LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
} }
@ -214,7 +231,7 @@ class PacketTunnelProvider: VpnService() {
} }
private fun reader() { private fun reader() {
var b = ByteArray(65535) val b = ByteArray(65535)
reads@ while (started.get()) { reads@ while (started.get()) {
val readerStream = readerStream val readerStream = readerStream
val readerThread = readerThread val readerThread = readerThread

View file

@ -49,14 +49,14 @@ class PeersActivity : AppCompatActivity() {
addPeerButton = findViewById(R.id.addPeerButton) addPeerButton = findViewById(R.id.addPeerButton)
addPeerButton.setOnClickListener { addPeerButton.setOnClickListener {
var view = inflater.inflate(R.layout.dialog_addpeer, null) val view = inflater.inflate(R.layout.dialog_addpeer, null)
var input = view.findViewById<TextInputEditText>(R.id.addPeerInput) val input = view.findViewById<TextInputEditText>(R.id.addPeerInput)
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog)) val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
builder.setTitle("Add Configured Peer") builder.setTitle("Add Configured Peer")
builder.setView(view) builder.setView(view)
builder.setPositiveButton("Add") { dialog, _ -> builder.setPositiveButton("Add") { dialog, _ ->
config.updateJSON { json -> config.updateJSON { json ->
json.getJSONArray("Peers").put(input.text) json.getJSONArray("Peers").put(input.text.toString().trim())
} }
dialog.dismiss() dialog.dismiss()
updateConfiguredPeers() updateConfiguredPeers()
@ -90,7 +90,7 @@ class PeersActivity : AppCompatActivity() {
configuredTableLayout.removeAllViewsInLayout() configuredTableLayout.removeAllViewsInLayout()
for (i in 0 until peers.length()) { for (i in 0 until peers.length()) {
val peer = peers[i].toString() val peer = peers[i].toString()
var view = inflater.inflate(R.layout.peers_configured, null) val view = inflater.inflate(R.layout.peers_configured, null)
view.findViewById<TextView>(R.id.addressValue).text = peer view.findViewById<TextView>(R.id.addressValue).text = peer
view.findViewById<ImageButton>(R.id.deletePeerButton).tag = i view.findViewById<ImageButton>(R.id.deletePeerButton).tag = i
@ -130,7 +130,7 @@ class PeersActivity : AppCompatActivity() {
connectedTableLayout.removeAllViewsInLayout() connectedTableLayout.removeAllViewsInLayout()
for (i in 0 until peers.length()) { for (i in 0 until peers.length()) {
val peer = peers.getJSONObject(i) val peer = peers.getJSONObject(i)
var view = inflater.inflate(R.layout.peers_connected, null) val view = inflater.inflate(R.layout.peers_connected, null)
val ip = peer.getString("IP") val ip = peer.getString("IP")
view.findViewById<TextView>(R.id.addressLabel).text = ip view.findViewById<TextView>(R.id.addressLabel).text = ip
view.findViewById<TextView>(R.id.detailsLabel).text = peer.getString("Remote") view.findViewById<TextView>(R.id.detailsLabel).text = peer.getString("Remote")

View file

@ -0,0 +1,245 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DnsActivity">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerPadding="4pt"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/pageTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8pt"
android:text="@string/dns_activity_title"
android:textColor="?attr/textDefault"
android:textSize="24sp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<ImageButton
android:id="@+id/addServerButton"
android:layout_width="16pt"
android:layout_height="16pt"
android:layout_marginTop="6pt"
android:layout_marginRight="8pt"
android:background="@android:color/transparent"
app:srcCompat="@drawable/ic_baseline_add_circle_24" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/configuredDnsLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text=""
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/configuredDnsTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle" />
<TextView
android:id="@+id/configuredDnsHint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_configured_servers_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/usableDnsLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_usable_servers"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/usableDnsTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="4pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_usable_servers_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<Space
android:layout_width="match_parent"
android:layout_height="32px" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:layout_marginBottom="2pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_fixes"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<TableLayout
android:id="@+id/fixesTableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8pt"
android:layout_marginLeft="8pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:background="@drawable/rounded"
android:divider="#46878787"
android:dividerPadding="4pt"
android:paddingLeft="4pt"
android:paddingTop="2pt"
android:paddingRight="4pt"
android:paddingBottom="2pt"
android:showDividers="middle">
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:paddingStart="4pt"
android:paddingTop="2pt"
android:paddingEnd="4pt"
android:paddingBottom="4pt">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dns_fix_chrome_based_browsers"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<Switch
android:id="@+id/enableChromeFix"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</TableRow>
</TableLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16pt"
android:layout_marginLeft="16pt"
android:layout_marginTop="4pt"
android:layout_marginEnd="8pt"
android:layout_marginRight="8pt"
android:alpha="0.7"
android:paddingRight="8pt"
android:text="@string/dns_fix_chrome_based_browsers_hint"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
</FrameLayout>
</LinearLayout>

View file

@ -94,7 +94,7 @@
android:layout_weight="2" /> android:layout_weight="2" />
<Switch <Switch
android:id="@+id/enableMulticastBeacon" android:id="@+id/enableYggdrasil"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
@ -347,6 +347,50 @@
app:srcCompat="@drawable/ic_baseline_chevron_right_24" /> app:srcCompat="@drawable/ic_baseline_chevron_right_24" />
</TableRow> </TableRow>
<TableRow
android:id="@+id/dnsTableRow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="4pt"
android:paddingTop="6pt"
android:paddingEnd="4pt"
android:paddingBottom="6pt">
<TextView
android:id="@+id/dnsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DNS servers"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<TextView
android:id="@+id/dnsValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.5"
android:text="No servers"
android:textAlignment="textEnd" />
<ImageView
android:id="@+id/dnsChevron"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="2pt"
android:alpha="0.4"
android:cropToPadding="false"
android:scaleType="fitEnd"
android:scaleX="1.2"
android:scaleY="1.2"
app:srcCompat="@drawable/ic_baseline_chevron_right_24" />
</TableRow>
<TableRow <TableRow
android:id="@+id/settingsTableRow" android:id="@+id/settingsTableRow"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -426,12 +470,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16pt" android:layout_marginStart="16pt"
android:layout_marginLeft="16pt" android:layout_marginLeft="16pt"
android:layout_marginTop="2pt" android:layout_marginTop="4pt"
android:layout_marginEnd="8pt" android:layout_marginEnd="8pt"
android:layout_marginRight="8pt" android:layout_marginRight="8pt"
android:alpha="0.7" android:alpha="0.7"
android:paddingRight="8pt" android:paddingRight="8pt"
android:text="You must re-enable Yggdrasil after modifying Peers or Settings to make any changes effective." android:text="You must re-enable Yggdrasil after modifying Peers, DNS servers or Settings to make any changes effective."
android:textAllCaps="false" android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" /> android:textSize="12sp" />

View file

@ -128,7 +128,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16pt" android:layout_marginStart="16pt"
android:layout_marginLeft="16pt" android:layout_marginLeft="16pt"
android:layout_marginTop="2pt" android:layout_marginTop="4pt"
android:layout_marginEnd="8pt" android:layout_marginEnd="8pt"
android:layout_marginRight="8pt" android:layout_marginRight="8pt"
android:layout_marginBottom="4pt" android:layout_marginBottom="4pt"
@ -237,7 +237,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16pt" android:layout_marginStart="16pt"
android:layout_marginLeft="16pt" android:layout_marginLeft="16pt"
android:layout_marginTop="2pt" android:layout_marginTop="4pt"
android:layout_marginEnd="8pt" android:layout_marginEnd="8pt"
android:layout_marginRight="8pt" android:layout_marginRight="8pt"
android:alpha="0.7" android:alpha="0.7"

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="10pt"
android:paddingTop="4pt"
android:paddingRight="10pt"
android:paddingBottom="4pt">
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Enter the IP address of DNS server to add. It will be used by OS and all apps when Yggdrasil starts." />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/addDnsInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4pt"
android:lines="1"
android:hint="8.8.8.8 or 302:7991::53" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</FrameLayout>

View file

@ -27,6 +27,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4pt" android:layout_marginTop="4pt"
android:lines="1"
android:hint="tcp://address:port" /> android:hint="tcp://address:port" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/tableRow"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/serverValue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="18dp"
android:layout_marginBottom="12dp"
android:ellipsize="end"
android:singleLine="true"
android:text=""
android:textColor="?attr/textDefault"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/addButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/space" />
<Space
android:id="@+id/space"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/addButton"
app:layout_constraintEnd_toStartOf="@+id/addButton"
app:layout_constraintStart_toEndOf="@+id/serverValue"
app:layout_constraintTop_toTopOf="@+id/addButton" />
<ImageButton
android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="10dp"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_baseline_add_circle_24" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tableRow" android:id="@+id/tableRow"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -16,7 +15,7 @@
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
android:text="TextView" android:text=""
android:textColor="?attr/textDefault" android:textColor="?attr/textDefault"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/deletePeerButton" app:layout_constraintEnd_toStartOf="@+id/deletePeerButton"

View file

@ -1,3 +1,20 @@
<resources> <resources>
<string name="app_name">Yggdrasil</string> <string name="app_name">Yggdrasil</string>
<string name="dns_configured_servers_hint">Yggdrasil will use these DNS servers in VPN config when service starts. All your DNS requests will be resolved by them.</string>
<string name="dns_configured_servers_hint_empty">Yggdrasil will not configure any DNS servers when service starts. All your DNS requests will be resolved by system resolver.</string>
<string name="dns_usable_servers_hint">These are DNS servers run by community members. Click plus button to add them to a list above. Long-tap to see more info.</string>
<string name="dns_no_configured_servers">No servers configured</string>
<string name="dns_configured_servers">Configured servers</string>
<string name="dns_server_info_revertron">The server supports resolving regular ICANN domains, ALFIS domains, OpenNIC domains.\n\nAlso, it blocks ads, analytics and malware websites.\n\nThe server is run by Revertron.</string>
<string name="dns_server_info_dialog_title">Server info</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="remove">Remove</string>
<string name="add">Add</string>
<string name="dns_add_server_dialog_title">Add DNS server</string>
<string name="dns_activity_title">DNS</string>
<string name="dns_usable_servers">Usable servers</string>
<string name="dns_fix_chrome_based_browsers">Fix Chrome-based browsers</string>
<string name="dns_fix_chrome_based_browsers_hint">Chrome-based browsers need an additional fix to understand that you have IPv6 connectivity.</string>
<string name="dns_fixes">DNS fixes</string>
</resources> </resources>

View file

@ -1,12 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = "1.5.0" ext.kotlin_version = '1.7.20'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "com.android.tools.build:gradle:4.2.1" classpath "com.android.tools.build:gradle:4.2.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@ -18,7 +18,6 @@ allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
} }
} }

View file

@ -26,7 +26,7 @@ cp /tmp/yggdrasil-go/yggdrasil.aar /tmp/yggdrasil-android/app/libs/
``` ```
cd /tmp/yggdrasil-android cd /tmp/yggdrasil-android
./gradew assembleRelease ./gradlew assembleRelease
``` ```
note: you will need to use jdk-11 as jdk-16 `"doesn't work" ™` note: you will need to use jdk-11 as jdk-16 `"doesn't work" ™`