# Inject real tunnel qnames (Mendeley DTQBC, CC BY 4.0) at various volumes into real benign DNS (CTU-Normal-20 dns.log # regenerated with Zeek 9.0.1); compute per-(host, base) features. 300 seeds per (class, U). import sys, os, csv, random, statistics, math, importlib.util here = os.path.dirname(os.path.abspath(__file__)) sp = importlib.util.spec_from_file_location('p3lib', os.path.join(here, 'p3lib.py')); L = importlib.util.module_from_spec(sp); sp.loader.exec_module(L) def read(path): fields = None for line in open(path, errors='replace'): line = line.rstrip('\n') if line.startswith('#fields'): fields = line.split('\t')[1:]; continue if line.startswith('#') or not line: continue yield dict(zip(fields, line.split('\t'))) benign_logs = sys.argv[1].split(',') mend = sys.argv[2]; seeds = int(sys.argv[3]) rows = list(csv.reader(open(mend, encoding='utf-8', errors='replace')))[1:] cls = {c: [r[1] for r in rows if r[0] == c] for c in '1234'} names = {'1': 'dns2tcp', '2': 'dnscapy', '3': 'iodine', '4': 'tuns'} def info_bits(f): return f['H'] * f['L'] * f['U'] for bl in benign_logs: base_pairs = [(r['id.orig_h'], r['query']) for r in read(bl) if r.get('query') not in (None, '-', '')] fb = L.features(L.groups_from(base_pairs)) ib = sorted((info_bits(v), k, v) for k, v in fb.items()) print(f"== benign {bl}: groups={len(fb)}; top info_bits:") for i, k, v in ib[-4:]: print(f" {k} U={v['U']} H={v['H']:.2f} meanlen={v['L']:.1f} JSD={v['JSD']:.3f} info_bits={i:.0f}") for c in '124': for U in (30, 60, 100, 300): I, J, Hs, Ls = [], [], [], [] for s in range(1, seeds + 1): r = random.Random(s * 1000 + U) pool = list(dict.fromkeys(cls[c])); sample = r.sample(pool, min(U, len(pool))) pairs = base_pairs + [('10.66.66.66', q) for q in sample] f = L.features(L.groups_from(pairs)) tun = [v for k, v in f.items() if k[0] == '10.66.66.66'] v = max(tun, key=lambda x: x['U']) I.append(info_bits(v)); J.append(v['JSD']); Hs.append(v['H']); Ls.append(v['L']) q = lambda xs: f"min={min(xs):.3g} med={statistics.median(xs):.3g} max={max(xs):.3g}" print(f" {names[c]:8s} U={U:4d} info_bits {q(I)} | JSD {q(J)} | H {q(Hs)} | meanlen {q(Ls)}")