diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/DNSCryptRunFragment.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/DNSCryptRunFragment.java index 522a6e9e5..a18ea16c2 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/DNSCryptRunFragment.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/DNSCryptRunFragment.java @@ -20,13 +20,17 @@ import android.annotation.SuppressLint; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.SharedPreferences; import android.os.Bundle; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; + +import android.os.IBinder; import android.text.Html; import android.text.method.ScrollingMovementMethod; import android.util.Log; @@ -41,7 +45,9 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; import java.util.Objects; import java.util.Timer; import java.util.TimerTask; @@ -58,6 +64,8 @@ import pan.alexander.tordnscrypt.utils.Verifier; import pan.alexander.tordnscrypt.utils.enums.ModuleState; import pan.alexander.tordnscrypt.modules.ModulesStatus; +import pan.alexander.tordnscrypt.vpn.ResourceRecord; +import pan.alexander.tordnscrypt.vpn.service.ServiceVPN; import pan.alexander.tordnscrypt.vpn.service.ServiceVPNHelper; import static pan.alexander.tordnscrypt.TopFragment.DNSCryptVersion; @@ -72,6 +80,7 @@ import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPED; import static pan.alexander.tordnscrypt.utils.enums.ModuleState.STOPPING; import static pan.alexander.tordnscrypt.utils.enums.OperationMode.ROOT_MODE; +import static pan.alexander.tordnscrypt.utils.enums.OperationMode.VPN_MODE; public class DNSCryptRunFragment extends Fragment implements View.OnClickListener { @@ -93,6 +102,11 @@ public class DNSCryptRunFragment extends Fragment implements View.OnClickListene private ModuleState fixedModuleState; private int displayLogPeriod = -1; + private ServiceConnection serviceConnection; + private ServiceVPN serviceVPN; + private boolean bound; + private ArrayList savedResourceRecords; + public DNSCryptRunFragment() { } @@ -144,7 +158,7 @@ public void onReceive(Context context, Intent intent) { if (sb.toString().contains("DNSCrypt_version")) { String[] strArr = sb.toString().split("DNSCrypt_version"); - if (strArr.length > 1 && strArr[1].trim().matches("\\d+\\.\\d+\\.\\d+")) { + if (getActivity() != null && strArr.length > 1 && strArr[1].trim().matches("\\d+\\.\\d+\\.\\d+")) { DNSCryptVersion = strArr[1].trim(); new PrefManager(getActivity()).setStrPref("DNSCryptVersion", DNSCryptVersion); @@ -181,9 +195,7 @@ public void onReceive(Context context, Intent intent) { setDnsCryptSomethingWrong(); } - } - - if (action.equals(TOP_BROADCAST)) { + } else if (action.equals(TOP_BROADCAST)) { if (TOP_BROADCAST.contains("TOP_BROADCAST")) { Log.i(LOG_TAG, "DNSCryptRunFragment onReceive TOP_BROADCAST"); @@ -266,6 +278,8 @@ public void onResume() { modulesStatus = ModulesStatus.getInstance(); + savedResourceRecords = new ArrayList<>(); + logFile = new OwnFileReader(getActivity(), appDataDir + "/logs/DnsCrypt.log"); if (isDNSCryptInstalled()) { @@ -318,6 +332,7 @@ public void onResume() { public void onStop() { super.onStop(); try { + unbindVPNService(getActivity()); stopDisplayLog(); if (br != null) Objects.requireNonNull(getActivity()).unregisterReceiver(br); } catch (Exception e) { @@ -474,6 +489,10 @@ private void refreshDNSCryptState() { displayLog(5000); + if (modulesStatus.getMode() == VPN_MODE && !bound) { + bindToVPNService(getActivity()); + } + } else if (currentModuleState == STOPPED) { if (isSavedDNSStatusRunning()) { @@ -628,24 +647,25 @@ public void run() { displayLog(10000); } - getActivity().runOnUiThread(new Runnable() { + displayDnsResponses(lastLines); - @Override - public void run() { + getActivity().runOnUiThread(() -> { - refreshDNSCryptState(); + refreshDNSCryptState(); - if (!previousLastLines.contentEquals(lastLines)) { + if (!previousLastLines.contentEquals(lastLines)) { - dnsCryptStartedSuccessfully(lastLines); + dnsCryptStartedSuccessfully(lastLines); - dnsCryptStartedWithError(lastLines); + dnsCryptStartedWithError(lastLines); + if (!previousLastLines.isEmpty()) { tvDNSCryptLog.setText(Html.fromHtml(lastLines)); - previousLastLines = lastLines; } + previousLastLines = lastLines; } + }); } @@ -743,4 +763,93 @@ private void cleanLogFileNoRootMethod() { } } + private void bindToVPNService(Context context) { + serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + serviceVPN = ((ServiceVPN.VPNBinder) service).getService(); + bound = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + bound = false; + } + }; + + if (context != null) { + Intent intent = new Intent(context, ServiceVPN.class); + context.bindService(intent, serviceConnection, 0); + } + } + + private void unbindVPNService(Context context) { + if (bound && serviceConnection != null && context != null) { + context.unbindService(serviceConnection); + bound = false; + } + } + + private LinkedList getResourceRecords() { + if (serviceVPN != null) { + return serviceVPN.getResourceRecords(); + } + return new LinkedList<>(); + } + + private void displayDnsResponses(String savedLines) { + if (modulesStatus.getMode() != VPN_MODE) { + if (!savedResourceRecords.isEmpty() && getActivity() != null) { + savedResourceRecords.clear(); + getActivity().runOnUiThread(() -> tvDNSCryptLog.setText(Html.fromHtml(logFile.readLastLines()))); + } + return; + } else if (getActivity() != null && modulesStatus.getMode() == VPN_MODE && !bound) { + bindToVPNService(getActivity()); + } + + ArrayList resourceRecords = new ArrayList<>(getResourceRecords()); + + if (resourceRecords.equals(savedResourceRecords) || resourceRecords.isEmpty()) { + return; + } + + savedResourceRecords = resourceRecords; + + ResourceRecord rr; + StringBuilder line = new StringBuilder(); + + line.append(savedLines); + + line.append("
"); + + for (int i = 0; i < savedResourceRecords.size(); i++) { + rr = savedResourceRecords.get(i); + + if (rr.Resource.equals("0.0.0.0") || rr.HInfo.contains("dnscrypt") || rr.Rcode != 0) { + if (!rr.AName.isEmpty()) { + line.append("").append(rr.AName); + + if (rr.HInfo.contains("block_ipv6")) { + line.append(" ipv6"); + } + + line.append(""); + } else { + line.append("").append(rr.QName).append(""); + } + } else { + line.append("").append(rr.AName).append(""); + } + + if (i < savedResourceRecords.size() - 1) { + line.append("
"); + } + } + + if (getActivity() != null) { + getActivity().runOnUiThread(() -> tvDNSCryptLog.setText(Html.fromHtml(line.toString()))); + } + } + } diff --git a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/ResourceRecord.java b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/ResourceRecord.java index 1ac825827..384efc19c 100644 --- a/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/ResourceRecord.java +++ b/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/ResourceRecord.java @@ -20,18 +20,20 @@ import androidx.annotation.NonNull; +import java.io.Serializable; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Objects; -public class ResourceRecord { +public class ResourceRecord implements Serializable { public long Time; public String QName; public String AName; public String CName; public String HInfo; public String Resource; - public int TTL; + public int Rcode; private static DateFormat formatter = SimpleDateFormat.getDateTimeInstance(); @@ -40,7 +42,6 @@ public ResourceRecord() { private String trimToNotASCIISymbols(String line) { StringBuilder result = new StringBuilder(); - int i = 0; for (char ch : line.toCharArray()) { if (ch < 128) { result.append(ch); @@ -52,6 +53,43 @@ private String trimToNotASCIISymbols(String line) { return result.toString(); } + private String rCodeToString(int Rcode) { + String result = ""; + switch (Rcode) { + case 0: + result = "DNS Query completed successfully"; + break; + case 1: + result = "DNS Query Format Error"; + break; + case 2: + result = "Server failed to complete the DNS request"; + break; + case 3: + result = "Domain name does not exist"; + break; + case 4: + result = "Function not implemented"; + break; + case 5: + result = "The server refused to answer for the query"; + break; + case 6: + result = "Name that should not exist, does exist"; + break; + case 7: + result = "RRset that should not exist, does exist"; + break; + case 8: + result = "Server not authoritative for the zone"; + break; + case 9: + result = "Name not in zone"; + break; + } + return result; + } + @NonNull @Override public String toString() { @@ -62,24 +100,39 @@ public String toString() { " QName " + QName + " AName " + AName + " CName " + CName + - " TTL " + TTL + - " " + formatter.format(new Date(Time + TTL * 1000L).getTime()); + " " + rCodeToString(Rcode); } else if (!Resource.isEmpty()){ result = formatter.format(new Date(Time).getTime()) + " QName " + QName + " AName " + AName + " Resource " + Resource + - " TTL " + TTL + - " " + formatter.format(new Date(Time + TTL * 1000L).getTime()); + " " + rCodeToString(Rcode); } else if (!HInfo.isEmpty()){ result = formatter.format(new Date(Time).getTime()) + " QName " + QName + " AName " + AName + " HINFO " + trimToNotASCIISymbols(HInfo)+ - " TTL " + TTL + - " " + formatter.format(new Date(Time + TTL * 1000L).getTime()); + " " + rCodeToString(Rcode); } return result; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResourceRecord that = (ResourceRecord) o; + return Time == that.Time && + Rcode == that.Rcode && + QName.equals(that.QName) && + AName.equals(that.AName) && + CName.equals(that.CName) && + HInfo.equals(that.HInfo); + } + + @Override + public int hashCode() { + return Objects.hash(Time, QName, AName, CName, HInfo, Rcode); + } } diff --git a/tordnscrypt/src/main/jni/invizible/dns.c b/tordnscrypt/src/main/jni/invizible/dns.c index fd249f964..afc61400c 100644 --- a/tordnscrypt/src/main/jni/invizible/dns.c +++ b/tordnscrypt/src/main/jni/invizible/dns.c @@ -91,7 +91,8 @@ void parse_dns_response(const struct arguments *args, const struct ng_session *s struct dns_header *dns = (struct dns_header *) data; int qcount = ntohs(dns->q_count); int acount = ntohs(dns->ans_count); - if (dns->qr == 1 && dns->opcode == 0 && qcount > 0 && acount > 0) { + uint16_t rcode = dns->rcode; + if (dns->qr == 1 && dns->opcode == 0 && qcount > 0) { log_android(ANDROID_LOG_DEBUG, "DNS response qcount %d acount %d", qcount, acount); if (qcount > 1) log_android(ANDROID_LOG_WARN, "DNS response qcount %d acount %d", qcount, acount); @@ -125,13 +126,19 @@ void parse_dns_response(const struct arguments *args, const struct ng_session *s } } + if (acount == 0 || rcode != 0) { + dns_resolved(args, qname, name, (const char *) &"", (const char *) &"", + (const char *) &"", rcode); + return; + } + int32_t aoff = off; for (int a = 0; a < acount; a++) { off = get_qname(data, *datalen, (uint16_t) off, name); if (off > 0 && off + 10 <= *datalen) { uint16_t qtype = ntohs(*((uint16_t *) (data + off))); uint16_t qclass = ntohs(*((uint16_t *) (data + off + 2))); - uint32_t ttl = ntohl(*((uint32_t *) (data + off + 4))); + //uint32_t ttl = ntohl(*((uint32_t *) (data + off + 4))); uint16_t rdlength = ntohs(*((uint16_t *) (data + off + 8))); off += 10; @@ -153,25 +160,25 @@ void parse_dns_response(const struct arguments *args, const struct ng_session *s } dns_resolved(args, qname, name, (const char *) &"", (const char *) &"", - rd, ttl); + rd, rcode); log_android(ANDROID_LOG_DEBUG, - "DNS answer %d qname %s qtype %d ttl %d data %s", - a, name, qtype, ttl, rd); + "DNS answer %d qname %s qtype %d rcode %d data %s", + a, name, qtype, rcode, rd); } else if (qclass == DNS_QCLASS_IN && qtype == DNS_QTYPE_CNAME) { get_qname(data, *datalen, (uint16_t) off, cname); dns_resolved(args, qname, name, cname, (const char *) &"", - (const char *) &"", ttl); + (const char *) &"", rcode); log_android(ANDROID_LOG_DEBUG, - "DNS answer %d qname %s cname %s qclass %d qtype %d ttl %d length %d", - a, name, cname, qclass, qtype, ttl, rdlength); + "DNS answer %d qname %s cname %s qclass %d qtype %d rcode %d length %d", + a, name, cname, qclass, qtype, rcode, rdlength); } else if (qclass == DNS_QCLASS_IN && qtype == DNS_QTYPE_HINFO) { char *hinfo; hinfo = (char *) ng_malloc(rdlength, "hinfo"); if (rdlength > 1) { hinfo = memcpy(hinfo, data + off + 1, (size_t) (rdlength - 1)); dns_resolved(args, qname, name, (const char *) &"", hinfo, - (const char *) &"", ttl); + (const char *) &"", rcode); log_android(ANDROID_LOG_DEBUG, "DNS answer %d qname %s hinfo %s", a, name, hinfo); @@ -180,8 +187,8 @@ void parse_dns_response(const struct arguments *args, const struct ng_session *s } else log_android(ANDROID_LOG_DEBUG, - "DNS answer %d qname %s qclass %d qtype %d ttl %d length %d", - a, name, qclass, qtype, ttl, rdlength); + "DNS answer %d qname %s qclass %d qtype %d rcode %d length %d", + a, name, qclass, qtype, rcode, rdlength); off += rdlength; } else { diff --git a/tordnscrypt/src/main/jni/invizible/invizible.c b/tordnscrypt/src/main/jni/invizible/invizible.c index 61c2db4f3..a724acc4b 100644 --- a/tordnscrypt/src/main/jni/invizible/invizible.c +++ b/tordnscrypt/src/main/jni/invizible/invizible.c @@ -449,10 +449,10 @@ jfieldID fidAName = NULL; jfieldID fidCName = NULL; jfieldID fidHInfo = NULL; jfieldID fidResource = NULL; -jfieldID fidTTL = NULL; +jfieldID fidRcode = NULL; void dns_resolved(const struct arguments *args, const char *qname, const char *aname, - const char *cname, const char * hinfo, const char *resource, int ttl) { + const char *cname, const char * hinfo, const char *resource, int rcode) { #ifdef PROFILE_JNI float mselapsed; struct timeval start, end; @@ -481,7 +481,7 @@ void dns_resolved(const struct arguments *args, const char *qname, const char *a fidCName = jniGetFieldID(args->env, clsRR, "CName", string); fidHInfo = jniGetFieldID(args->env, clsRR, "HInfo", string); fidResource = jniGetFieldID(args->env, clsRR, "Resource", string); - fidTTL = jniGetFieldID(args->env, clsRR, "TTL", "I"); + fidRcode = jniGetFieldID(args->env, clsRR, "Rcode", "I"); } jlong jtime = time(NULL) * 1000LL; @@ -502,7 +502,7 @@ void dns_resolved(const struct arguments *args, const char *qname, const char *a (*args->env)->SetObjectField(args->env, jrr, fidCName, jcname); (*args->env)->SetObjectField(args->env, jrr, fidHInfo, jhinfo); (*args->env)->SetObjectField(args->env, jrr, fidResource, jresource); - (*args->env)->SetIntField(args->env, jrr, fidTTL, ttl); + (*args->env)->SetIntField(args->env, jrr, fidRcode, rcode); (*args->env)->CallVoidMethod(args->env, args->instance, midDnsResolved, jrr); jniCheckException(args->env); diff --git a/tordnscrypt/src/main/jni/invizible/invizible.h b/tordnscrypt/src/main/jni/invizible/invizible.h index d1b2a7f41..c577cd3eb 100644 --- a/tordnscrypt/src/main/jni/invizible/invizible.h +++ b/tordnscrypt/src/main/jni/invizible/invizible.h @@ -509,7 +509,7 @@ void log_android(int prio, const char *fmt, ...); void log_packet(const struct arguments *args, jobject jpacket); void dns_resolved(const struct arguments *args, const char *qname, const char *aname, - const char *cname, const char *hinfo, const char *resource, int ttl); + const char *cname, const char *hinfo, const char *resource, int rcode); jboolean is_domain_blocked(const struct arguments *args, const char *name);