Issue #12: Use proper DNS library instead of handcrafted DNS query to fetch statistics.

This commit is contained in:
Benjamin Erhart 2026-05-29 17:25:29 +02:00
parent ddcf2070f7
commit 23952582e6
3 changed files with 62 additions and 32 deletions

View file

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
A051B5122FC9E0D700EACDC0 /* AsyncDNSResolver in Frameworks */ = {isa = PBXBuildFile; productRef = A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */; };
A08264132FC718790077B227 /* Stickers.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A082640C2FC718790077B227 /* Stickers.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; A08264132FC718790077B227 /* Stickers.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A082640C2FC718790077B227 /* Stickers.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -74,6 +75,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A051B5122FC9E0D700EACDC0 /* AsyncDNSResolver in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -125,6 +127,7 @@
); );
name = dns; name = dns;
packageProductDependencies = ( packageProductDependencies = (
A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */,
); );
productName = dns; productName = dns;
productReference = 069DCCFA2F8C0DCE00F1EB16 /* dns.app */; productReference = 069DCCFA2F8C0DCE00F1EB16 /* dns.app */;
@ -178,6 +181,9 @@
); );
mainGroup = 069DCCF12F8C0DCD00F1EB16; mainGroup = 069DCCF12F8C0DCD00F1EB16;
minimizedProjectReferenceProxies = 1; minimizedProjectReferenceProxies = 1;
packageReferences = (
A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */,
);
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = 069DCCFB2F8C0DCE00F1EB16 /* Products */; productRefGroup = 069DCCFB2F8C0DCE00F1EB16 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -529,6 +535,25 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-async-dns-resolver/";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.7.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */ = {
isa = XCSwiftPackageProductDependency;
package = A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */;
productName = AsyncDNSResolver;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 069DCCF22F8C0DCD00F1EB16 /* Project object */; rootObject = 069DCCF22F8C0DCD00F1EB16 /* Project object */;
} }

View file

@ -0,0 +1,15 @@
{
"originHash" : "8d197ac71cfa33d9e3396085f37fbee66795a894a6d74e24ff28bd887e01b9f2",
"pins" : [
{
"identity" : "swift-async-dns-resolver",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-async-dns-resolver/",
"state" : {
"revision" : "9900d9b4113427a170d5f26075c2128be94984df",
"version" : "0.7.0"
}
}
],
"version" : 3
}

View file

@ -1,9 +1,13 @@
import SwiftUI import SwiftUI
import Foundation import Foundation
import AsyncDNSResolver
struct BlockedCount: View { struct BlockedCount: View {
@Environment(\.scenePhase)
private var scenePhase
@EnvironmentObject @EnvironmentObject
private var viewModel: ViewModel private var viewModel: ViewModel
@ -21,46 +25,32 @@ struct BlockedCount: View {
var body: some View { var body: some View {
Text(txtRecord) Text(txtRecord)
.onAppear { .onAppear {
fetchTXTRecord() fetchTxtRecord()
} }
.onChange(of: viewModel.blocklist) { _ in .onChange(of: viewModel.blocklist) { _ in
fetchTXTRecord() fetchTxtRecord()
}
.onChange(of: scenePhase) { newPhase in
if newPhase == .active {
fetchTxtRecord()
}
} }
} }
func parseResponse(data: Data) -> String? { private func fetchTxtRecord() {
// This is a DNS wire format response and we make a lot of assumptions
// It is not critical functionality so just let it fail if it fails
guard data.count > Self.startIndex else {
return nil
}
// Find the first space character (ASCII 32).
guard let endIndex = data.suffix(from: Self.startIndex).firstIndex(of: 32) else {
return nil
}
guard let numberString = String(data: data[Self.startIndex ..< endIndex], encoding: .utf8)
else {
return nil
}
return Self.formatter.string(for: Int(numberString))
}
func fetchTXTRecord() {
let dohURL = URL(string: "https://\(viewModel.blocklist.server)/dns-query?dns=DoQBAAABAAAAAAAABXN0YXRzB2ludmFsaWQAABAAAQ")!
let request = URLRequest(url: dohURL)
txtRecord = ""
Task { Task {
do { do {
let (data, _) = try await URLSession.shared.data(for: request) let resolver = try AsyncDNSResolver()
if let count = parseResponse(data: data) { let records = try await resolver.queryTXT(name: "stats.invalid")
txtRecord = count
let text = records.map { $0.txt }.joined()
if let number = text.split(separator: " ").first,
let intValue = Int(number),
let formatted = Self.formatter.string(for: intValue)
{
txtRecord = formatted
} }
else { else {
txtRecord = NSLocalizedString("Error", comment: "") txtRecord = NSLocalizedString("Error", comment: "")