feat(dnssec): add auth and trust dnssec

This commit is contained in:
2025-09-28 13:11:58 +01:00
parent 4a549cfea7
commit a966c1e98d
10 changed files with 345 additions and 184 deletions

View File

@@ -17,7 +17,7 @@ def map_server_to_resolver(server):
elif 'adguard' in server_lower:
return 'AdGuard'
else:
return server
return server # Fallback to original server name
def extract_from_new_format(filename):
"""Parse new filename format: protocol[-flags]-timestamp.csv"""
@@ -25,65 +25,97 @@ def extract_from_new_format(filename):
parts = base.split('-')
if len(parts) < 2:
return None, None, None
return None, None, None, None
protocol = parts[0]
timestamp = parts[-1]
# Flags are everything between protocol and timestamp
flags_str = '-'.join(parts[1:-1])
dnssec_status = 'on' if 'dnssec' in flags_str else 'off'
# Determine DNSSEC status
if 'auth' in flags_str:
dnssec_status = 'auth' # Authoritative DNSSEC
elif 'trust' in flags_str:
dnssec_status = 'trust' # Trust-based DNSSEC
else:
dnssec_status = 'off'
keepalive_status = 'on' if 'persist' in flags_str else 'off'
return protocol, dnssec_status, keepalive_status
return protocol, dnssec_status, keepalive_status, flags_str
def extract_server_info(file_path, dns_server_field):
"""Extract info using directory structure and filename"""
def extract_server_info_from_csv(row):
"""Extract DNSSEC info from CSV row data"""
dnssec = row.get('dnssec', 'false').lower() == 'true'
auth_dnssec = row.get('auth_dnssec', 'false').lower() == 'true'
keepalive = row.get('keep_alive', 'false').lower() == 'true'
if dnssec:
if auth_dnssec:
dnssec_status = 'auth'
else:
dnssec_status = 'trust'
else:
dnssec_status = 'off'
keepalive_status = 'on' if keepalive else 'off'
return dnssec_status, keepalive_status
def extract_server_info(file_path, row):
"""Extract info using directory structure, filename, and CSV data"""
path = Path(file_path)
# Expect structure like: results/resolver/date/filename.csv
parts = path.parts
if len(parts) >= 3 and parts[-2].isdigit() and len(parts[-2]) == 10: # date folder like 2024-03-01
server = parts[-3] # resolver folder (e.g., cloudflare)
filename = parts[-1]
# First try to get DNSSEC info from CSV row (most accurate)
try:
csv_dnssec_status, csv_keepalive_status = extract_server_info_from_csv(row)
protocol = row.get('protocol', '').lower()
protocol, dnssec_status, keepalive_status = extract_from_new_format(filename)
if protocol:
return protocol, server, dnssec_status, keepalive_status
# Get server from directory structure
parts = path.parts
if len(parts) >= 4:
potential_date = parts[-2]
# Check if it's a date like YYYY-MM-DD
if len(potential_date) == 10 and potential_date[4] == '-' and potential_date[7] == '-' and potential_date.replace('-', '').isdigit():
server = parts[-3] # resolver folder (e.g., cloudflare)
return protocol, server, csv_dnssec_status, csv_keepalive_status
# Fallback to DNS server field
server = row.get('dns_server', '')
return protocol, server, csv_dnssec_status, csv_keepalive_status
except (KeyError, ValueError):
pass
# Fallback to old parsing if structure doesn't match
# Fallback to filename parsing
filename = path.name
old_parts = filename.replace('.csv', '').split('_')
protocol, dnssec_status, keepalive_status, flags = extract_from_new_format(filename)
if len(old_parts) >= 6:
protocol = old_parts[0]
try:
dnssec_idx = old_parts.index('dnssec')
keepalive_idx = old_parts.index('keepalive')
server_parts = old_parts[1:dnssec_idx]
server = '_'.join(server_parts)
dnssec_status = old_parts[dnssec_idx + 1] if dnssec_idx + 1 < len(old_parts) else 'off'
keepalive_status = old_parts[keepalive_idx + 1] if keepalive_idx + 1 < len(old_parts) else 'off'
return protocol, server, dnssec_status, keepalive_status
except ValueError:
pass
# Even older format fallback
if len(old_parts) >= 4:
protocol = old_parts[0]
dnssec_status = 'on' if 'dnssec_on' in filename else 'off'
keepalive_status = 'on' if 'keepalive_on' in filename else 'off'
server = '_'.join(old_parts[1:-4]) if len(old_parts) > 4 else old_parts[1]
if protocol:
# Get server from directory structure
parts = path.parts
if len(parts) >= 4:
potential_date = parts[-2]
if len(potential_date) == 10 and potential_date[4] == '-' and potential_date[7] == '-' and potential_date.replace('-', '').isdigit():
server = parts[-3]
return protocol, server, dnssec_status, keepalive_status
# Fallback to DNS server field
server = row.get('dns_server', '')
return protocol, server, dnssec_status, keepalive_status
return None, None, None, None
def get_dnssec_display_name(dnssec_status):
"""Convert DNSSEC status to display name"""
if dnssec_status == 'auth':
return 'DNSSEC (Authoritative)'
elif dnssec_status == 'trust':
return 'DNSSEC (Trust-based)'
else:
return 'No DNSSEC'
def analyze_dns_data(root_directory, output_file):
"""Analyze DNS data and generate metrics"""
@@ -103,8 +135,7 @@ def analyze_dns_data(root_directory, output_file):
for row_num, row in enumerate(reader, 2): # Start at 2 since header is row 1
try:
protocol, server, dnssec_status, keepalive_status = extract_server_info(
file_path, row.get('dns_server', ''))
protocol, server, dnssec_status, keepalive_status = extract_server_info(file_path, row)
if protocol and server:
resolver = map_server_to_resolver(server)
@@ -123,13 +154,15 @@ def analyze_dns_data(root_directory, output_file):
print(f"Error processing file {file_path}: {e}")
continue
# Calculate statistics and group by resolver, dnssec, and keepalive
# Calculate statistics grouped by resolver first, then by configuration
resolver_results = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
for (resolver, protocol, dnssec, keepalive), durations in measurements.items():
if durations:
stats = {
'protocol': protocol.upper(),
'dnssec': dnssec,
'keepalive': keepalive,
'total_queries': len(durations),
'avg_latency_ms': round(statistics.mean(durations), 3),
'median_latency_ms': round(statistics.median(durations), 3),
@@ -139,23 +172,22 @@ def analyze_dns_data(root_directory, output_file):
'p95_latency_ms': round(statistics.quantiles(durations, n=20)[18], 3) if len(durations) >= 20 else round(max(durations), 3),
'p99_latency_ms': round(statistics.quantiles(durations, n=100)[98], 3) if len(durations) >= 100 else round(max(durations), 3)
}
resolver_results[dnssec][keepalive][resolver].append(stats)
# Group by resolver -> dnssec -> keepalive -> protocol
resolver_results[resolver][dnssec][keepalive].append(stats)
# Sort each resolver's results by average latency
for dnssec in resolver_results:
for keepalive in resolver_results[dnssec]:
for resolver in resolver_results[dnssec][keepalive]:
resolver_results[dnssec][keepalive][resolver].sort(key=lambda x: x['avg_latency_ms'])
# Sort each configuration's results by average latency
for resolver in resolver_results:
for dnssec in resolver_results[resolver]:
for keepalive in resolver_results[resolver][dnssec]:
resolver_results[resolver][dnssec][keepalive].sort(key=lambda x: x['avg_latency_ms'])
# Write to CSV with all data
all_results = []
for dnssec in resolver_results:
for keepalive in resolver_results[dnssec]:
for resolver, results in resolver_results[dnssec][keepalive].items():
for result in results:
for resolver in resolver_results:
for dnssec in resolver_results[resolver]:
for keepalive in resolver_results[resolver][dnssec]:
for result in resolver_results[resolver][dnssec][keepalive]:
result['resolver'] = resolver
result['dnssec'] = dnssec
result['keepalive'] = keepalive
all_results.append(result)
with open(output_file, 'w', newline='') as csvfile:
@@ -172,32 +204,83 @@ def analyze_dns_data(root_directory, output_file):
print(f"\nAnalysis complete! Full results written to {output_file}")
print(f"Total measurements: {sum(len(durations) for durations in measurements.values())}")
def print_resolver_table(resolver, results, dnssec_status, keepalive_status):
"""Print a formatted table for a resolver"""
ka_indicator = "PERSISTENT" if keepalive_status == 'on' else "NEW CONNECTION"
print(f"\n{resolver} DNS Resolver (DNSSEC {dnssec_status.upper()}, {ka_indicator})")
print("=" * 100)
print(f"{'Protocol':<12} {'Queries':<8} {'Avg(ms)':<10} {'Median(ms)':<12} {'Min(ms)':<10} {'Max(ms)':<10} {'P95(ms)':<10}")
print("-" * 100)
def print_configuration_table(resolver, dnssec_status, keepalive_status, results):
"""Print a formatted table for a specific configuration"""
ka_indicator = "PERSISTENT" if keepalive_status == 'on' else "NEW CONN"
dnssec_display = get_dnssec_display_name(dnssec_status)
print(f"\n {dnssec_display} - {ka_indicator}")
print(" " + "-" * 90)
print(f" {'Protocol':<12} {'Queries':<8} {'Avg(ms)':<10} {'Median(ms)':<12} {'Min(ms)':<10} {'Max(ms)':<10} {'P95(ms)':<10}")
print(" " + "-" * 90)
for result in results:
print(f"{result['protocol']:<12} {result['total_queries']:<8} "
print(f" {result['protocol']:<12} {result['total_queries']:<8} "
f"{result['avg_latency_ms']:<10} {result['median_latency_ms']:<12} "
f"{result['min_latency_ms']:<10} {result['max_latency_ms']:<10} "
f"{result['p95_latency_ms']:<10}")
# Print tables organized by DNSSEC and KeepAlive status
for dnssec_status in ['off', 'on']:
if dnssec_status in resolver_results:
print(f"\n{'#' * 60}")
print(f"# DNS RESOLVERS - DNSSEC {dnssec_status.upper()}")
print(f"{'#' * 60}")
for keepalive_status in ['off', 'on']:
if keepalive_status in resolver_results[dnssec_status]:
for resolver in sorted(resolver_results[dnssec_status][keepalive_status].keys()):
results = resolver_results[dnssec_status][keepalive_status][resolver]
print_resolver_table(resolver, results, dnssec_status, keepalive_status)
# Print results grouped by resolver first
print(f"\n{'=' * 100}")
print("DNS RESOLVER PERFORMANCE COMPARISON")
print(f"{'=' * 100}")
for resolver in sorted(resolver_results.keys()):
print(f"\n{resolver} DNS Resolver")
print("=" * 100)
# Order configurations logically
config_order = [
('off', 'off'), # No DNSSEC, New connections
('off', 'on'), # No DNSSEC, Persistent
('trust', 'off'), # Trust DNSSEC, New connections
('trust', 'on'), # Trust DNSSEC, Persistent
('auth', 'off'), # Auth DNSSEC, New connections
('auth', 'on'), # Auth DNSSEC, Persistent
]
for dnssec_status, keepalive_status in config_order:
if dnssec_status in resolver_results[resolver] and keepalive_status in resolver_results[resolver][dnssec_status]:
results = resolver_results[resolver][dnssec_status][keepalive_status]
if results: # Only print if there are results
print_configuration_table(resolver, dnssec_status, keepalive_status, results)
# Summary comparison across resolvers
print(f"\n{'=' * 100}")
print("CROSS-RESOLVER PROTOCOL COMPARISON")
print(f"{'=' * 100}")
# Group by protocol and configuration for cross-resolver comparison
protocol_comparison = defaultdict(lambda: defaultdict(list))
for resolver in resolver_results:
for dnssec in resolver_results[resolver]:
for keepalive in resolver_results[resolver][dnssec]:
for result in resolver_results[resolver][dnssec][keepalive]:
config_key = f"{get_dnssec_display_name(dnssec)} - {'PERSISTENT' if keepalive == 'on' else 'NEW CONN'}"
protocol_comparison[result['protocol']][config_key].append({
'resolver': resolver,
'avg_latency_ms': result['avg_latency_ms'],
'total_queries': result['total_queries']
})
for protocol in sorted(protocol_comparison.keys()):
print(f"\n{protocol} Protocol Comparison")
print("-" * 100)
for config in sorted(protocol_comparison[protocol].keys()):
resolvers_data = protocol_comparison[protocol][config]
if resolvers_data:
print(f"\n {config}")
print(" " + "-" * 60)
print(f" {'Resolver':<15} {'Avg Latency (ms)':<20} {'Queries':<10}")
print(" " + "-" * 60)
# Sort by average latency
resolvers_data.sort(key=lambda x: x['avg_latency_ms'])
for data in resolvers_data:
print(f" {data['resolver']:<15} {data['avg_latency_ms']:<20} {data['total_queries']:<10}")
if __name__ == "__main__":
root_dir = "."