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.