- Posted on
- • Artificial Intelligence
Artificial Intelligence DNS Analytics
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
AI + Bash: Detect DNS Threats on the Command Line
If DNS is the internet’s address book, attackers are constantly scribbling in the margins. DNS tunneling, DGAs (algorithmic domains), and misconfigurations all leave subtle traces in query patterns. The good news: with a few Linux command-line tools and a pinch of AI, you can turn raw DNS traffic into high-signal detections—without buying another appliance.
This guide shows how to capture DNS, engineer features in Bash, and run an unsupervised anomaly detector to surface suspicious queries in minutes.
Why AI for DNS analytics?
DNS is everywhere. Every device and app talks DNS; adversaries do too.
It’s noisy and high-volume. Traditional rules are brittle; attackers evade by morphing payloads and domains.
Unsupervised AI shines in “unknown unknowns.” Techniques like Isolation Forest can flag outliers (e.g., high-entropy subdomains, abnormal label counts) without labeled data.
It’s deployable on a laptop or a jump box. You can start small on a tap/mirror port or on the DNS server itself.
What you’ll build:
A rotating DNS packet capture
A Bash+awk pipeline to extract features (length, label count, digit ratio, entropy)
A Python (scikit-learn) unsupervised detector for anomalies
Simple alerting via syslog and JSON outputs you can feed into a SIEM
Note: You’ll see plaintext DNS traffic unless you capture on the resolver side. DNS over HTTPS/TLS (DoH/DoT) won’t be visible on the wire unless you terminate it.
Prerequisites: Install tools
Pick your distro and install the following packages.
Apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y tcpdump tshark jq python3 python3-sklearn
DNF (Fedora/RHEL/CentOS Stream)
sudo dnf install -y tcpdump wireshark-cli jq python3 python3-scikit-learn
Zypper (openSUSE/SLES)
sudo zypper refresh
sudo zypper install -y tcpdump wireshark-cli jq python3 python3-scikit-learn
Notes:
tshark may prompt for capture permissions. Running with sudo is simplest to start.
If you want non-root captures, add your user to the wireshark group and set dumpcap capabilities per your distro guidance.
Step 1: Capture DNS traffic (rotating PCAPs)
Start a ring buffer of pcap files so you don’t fill the disk. Replace eth0 with your interface or use any to start broadly.
sudo mkdir -p /var/log/dns
sudo tcpdump -i eth0 -w /var/log/dns/dns.pcap -C 50 -W 20 'udp port 53 or tcp port 53'
-C 50 creates 50 MB chunks
-W 20 keeps 20 files in rotation (about 1 GB total)
Adjust to your retention and volume
Let this run in a tmux/screen session or as a systemd service.
Step 2: Parse to CSV with tshark
Convert a pcap into a compact CSV with fields we’ll feature-engineer.
tshark -r /var/log/dns/dns.pcap \
-Y 'dns' \
-T fields \
-e frame.time_epoch -e ip.src -e ip.dst \
-e dns.qry.name -e dns.qry.type \
-e dns.flags.response -e dns.flags.rcode \
-E header=y -E separator=, -E quote=d > /tmp/dns_raw.csv
Columns: time, src, dst, qname, qtype, response, rcode
Step 3: Feature-engineer in Bash (length, labels, digits, entropy)
Turn texty DNS fields into numbers our model can learn from.
awk -F, 'BEGIN{OFS=","}
NR==1{
print "time","src","dst","qname","qtype","response","rcode","qname_len","label_count","digit_ratio","entropy"
next
}
function entropy(s, i,c,n,p,H) {
n=length(s); if (n==0) return 0
for (i=1;i<=n;i++){ c=substr(s,i,1); freq[c]++ }
for (c in freq){ p=freq[c]/n; H+= -p*log(p)/log(2) }
delete freq
return H
}
function count_digits(s, i,c,n,k){ n=length(s); for(i=1;i<=n;i++){ c=substr(s,i,1); if (c ~ /[0-9]/) k++ } return k }
{
qname=$4
qlen=length(qname)
nlabels=(qname==""?0:split(qname,parts,"."))
dr=count_digits(qname); dr=(qlen>0?dr/qlen:0)
ent=entropy(qname)
print $1,$2,$3,qname,$5,$6,$7,qlen,nlabels,dr,ent
}' /tmp/dns_raw.csv > /tmp/dns_features.csv
Why these features?
qname_len: tunneling often pads long subdomains
label_count: nested labels can encode data
digit_ratio and entropy: DGAs and tunnels look “random”
rcode (later) helps link NXDOMAIN storms to DGAs
Step 4: Run an unsupervised detector (Isolation Forest)
We’ll use scikit-learn’s IsolationForest to flag outliers. It needs only unlabeled examples.
Save this as ai_dns_anomaly.py or run inline:
python3 - <<'PY'
import csv, json, sys
from sklearn.ensemble import IsolationForest
infile = "/tmp/dns_features.csv"
rows = []
with open(infile, newline="") as f:
r = csv.DictReader(f)
for row in r:
rows.append(row)
if not rows:
sys.exit(0)
def f(x):
try: return float(x)
except: return 0.0
X = []
for r in rows:
# Basic numeric features (+ rcode==3 as NXDOMAIN flag)
X.append([
f(r.get("qname_len",0)),
f(r.get("label_count",0)),
f(r.get("digit_ratio",0)),
f(r.get("entropy",0)),
1.0 if r.get("rcode","") == "3" else 0.0
])
clf = IsolationForest(n_estimators=200, contamination=0.01, random_state=42)
clf.fit(X)
pred = clf.predict(X) # -1 = anomaly, 1 = normal
score = clf.decision_function(X) # lower = more anomalous
with open("/tmp/dns_anomalies.jsonl","w") as out:
for r,p,s in zip(rows,pred,score):
if p == -1:
out.write(json.dumps({
"time": r["time"],
"src": r["src"],
"dst": r["dst"],
"qname": r["qname"],
"qtype": r["qtype"],
"rcode": r["rcode"],
"anomaly_score": float(-s) # bigger is more anomalous here
}) + "\n")
PY
Explore the output:
jq -s 'sort_by(.anomaly_score)|reverse|.[0:10]' /tmp/dns_anomalies.jsonl
Send high-confidence alerts to syslog:
jq 'select(.anomaly_score>0.3)' /tmp/dns_anomalies.jsonl | \
while read -r line; do
logger -t dns-ai "$line"
done
Tuning tips:
contamination (0.01) ≈ expected outlier fraction; increase to be more sensitive.
You can fit on a “known good” baseline (e.g., previous day) and score new data to reduce drift.
Try adding more features: QTYPE one-hots, source port randomness, inter-arrival times per source.
Step 5: Automate the pipeline
Here’s a minimal 15-minute batch job you can schedule via cron. Replace IFACE as needed.
sudo install -d -m 755 /var/lib/dns-ai
sudo tee /usr/local/bin/dns_ai_pipeline.sh >/dev/null <<'SH'
#!/usr/bin/env bash
set -euo pipefail
IFACE="${IFACE:-any}"
WORK="/var/lib/dns-ai"
TS="$(date -u +%Y%m%d%H%M%S)"
PCAP="$WORK/$TS.pcap"
RAW="$WORK/$TS.raw.csv"
FEAT="$WORK/$TS.features.csv"
ANOM="$WORK/$TS.anomalies.jsonl"
# 1) Capture 15 minutes
sudo timeout 900 tcpdump -i "$IFACE" -w "$PCAP" 'udp port 53 or tcp port 53' >/dev/null 2>&1
# 2) Parse
tshark -r "$PCAP" -Y 'dns' -T fields \
-e frame.time_epoch -e ip.src -e ip.dst \
-e dns.qry.name -e dns.qry.type \
-e dns.flags.response -e dns.flags.rcode \
-E header=y -E separator=, -E quote=d > "$RAW"
# 3) Features
awk -F, 'BEGIN{OFS=","}
NR==1{print "time","src","dst","qname","qtype","response","rcode","qname_len","label_count","digit_ratio","entropy"; next}
function entropy(s, i,c,n,p,H){n=length(s); if(n==0)return 0; for(i=1;i<=n;i++){c=substr(s,i,1); f[c]++} for(c in f){p=f[c]/n; H+= -p*log(p)/log(2)} delete f; return H}
function dcount(s, i,c,n,k){n=length(s); for(i=1;i<=n;i++){c=substr(s,i,1); if(c ~ /[0-9]/) k++} return k}
{q=$4; ql=length(q); lc=(q==""?0:split(q,a,".")); dr=dcount(q); dr=(ql>0?dr/ql:0); print $1,$2,$3,q,$5,$6,$7,ql,lc,dr,entropy(q)}' "$RAW" > "$FEAT"
# 4) AI detection
python3 - <<PY
import csv, json
from sklearn.ensemble import IsolationForest
rows=[]
with open("$FEAT", newline="") as f:
r=csv.DictReader(f)
for row in r: rows.append(row)
if not rows: raise SystemExit
def f(x):
try: return float(x)
except: return 0.0
X=[]
for r in rows:
X.append([f(r["qname_len"]), f(r["label_count"]), f(r["digit_ratio"]), f(r["entropy"]), 1.0 if r.get("rcode","")=="3" else 0.0])
clf=IsolationForest(n_estimators=200, contamination=0.01, random_state=42).fit(X)
pred=clf.predict(X); score=clf.decision_function(X)
with open("$ANOM","w") as out:
for r,p,s in zip(rows,pred,score):
if p==-1:
out.write(json.dumps({"time":r["time"],"src":r["src"],"dst":r["dst"],"qname":r["qname"],"qtype":r["qtype"],"rcode":r["rcode"],"anomaly_score":float(-s)})+"\n")
PY
# 5) Alert top anomalies
jq 'select(.anomaly_score>0.3)' "$ANOM" | while read -r line; do logger -t dns-ai "$line"; done
SH
sudo chmod +x /usr/local/bin/dns_ai_pipeline.sh
Schedule it:
(crontab -l 2>/dev/null; echo '*/15 * * * * IFACE=eth0 /usr/local/bin/dns_ai_pipeline.sh >> /var/log/dns_ai.log 2>&1') | crontab -
What does this catch? Real-world examples
DNS tunneling and exfiltration
- Signals: very long qnames, high label_count, high entropy, many TXT queries
- Expect: few internal hosts with sustained outliers
DGA-driven beaconing or malware C2
- Signals: high entropy + NXDOMAIN storms (rcode==3), uniform query cadence
- Expect: a host generating many unique failed lookups for random-like domains
Misconfigurations and loops
- Signals: spikes from a single src, repetitive queries to internal domains, abnormal label depth
- Expect: noisy but low-entropy patterns that you can whitelist quickly
Hardening and next steps
Whitelist known noisy patterns (e.g., CDNs with long subdomains) before modeling.
Split models per subnet or role (servers vs. endpoints) for cleaner baselines.
Enrich with resolver logs for response sizes, TTLs, and DOH/DTLS termination points.
Consider Zeek for richer DNS metadata; parse its logs with the same feature pipeline.
Feed anomalies to your SIEM and pivot on src IP, qname, and time window.
Conclusion and Call to Action
You don’t need a data lake to get value from DNS. With tcpdump, tshark, a few lines of Bash+awk, and an unsupervised model, you can automatically surface the weird and worthy in your DNS stream.
Your next step: 1) Install the packages for your distro. 2) Start the rotating capture. 3) Run the feature and AI pipeline on a sample hour of traffic. 4) Tune contamination and thresholds until the top 10 anomalies look interesting. 5) Put it on a cron and watch for new signals.
If this helped, try adding new features (e.g., per-host query rate, unique qname cardinality) and compare detection lift. Share what you learn—DNS is a team sport.