|
|
|
|
@ -0,0 +1,67 @@
|
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
Извлекает уникальные QNAME из текстового вывода tcpdump (UDP/TCP DNS).
|
|
|
|
|
Ответы и соответствие IP не учитываются — только запрошенные имена.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import re
|
|
|
|
|
import sys
|
|
|
|
|
from typing import Set, TextIO
|
|
|
|
|
|
|
|
|
|
# Запрос: <txid>+ <TYPE>? <qname>. (<остаток длины>)
|
|
|
|
|
# Примеры: "47028+ A? talk-pilsner.kakao.com. (40)"
|
|
|
|
|
# TCP: "... length 48 31523+ A? api.example.com. (46)"
|
|
|
|
|
_QUERY = re.compile(
|
|
|
|
|
r"""
|
|
|
|
|
(?<!\d) # не середина числа
|
|
|
|
|
(\d+)\+ # txid и флаг запроса
|
|
|
|
|
\s+
|
|
|
|
|
(\S+?) # тип (A, AAAA, PTR, …)
|
|
|
|
|
\?
|
|
|
|
|
\s+
|
|
|
|
|
([^\s()]+) # QNAME (с завершающей точкой в дампе)
|
|
|
|
|
\s+
|
|
|
|
|
\(\d+\) # длина хвоста пакета
|
|
|
|
|
""",
|
|
|
|
|
re.VERBOSE,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description="Список уникальных доменов из текстового вывода tcpdump (DNS-запросы)."
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"file",
|
|
|
|
|
nargs="?",
|
|
|
|
|
default="-",
|
|
|
|
|
help="Файл с дампом; по умолчанию или «-» — читать stdin",
|
|
|
|
|
)
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
inp: TextIO
|
|
|
|
|
if args.file == "-":
|
|
|
|
|
inp = sys.stdin
|
|
|
|
|
else:
|
|
|
|
|
inp = open(args.file, encoding="utf-8", errors="replace")
|
|
|
|
|
|
|
|
|
|
seen: Set[str] = set()
|
|
|
|
|
try:
|
|
|
|
|
for line in inp:
|
|
|
|
|
for m in _QUERY.finditer(line):
|
|
|
|
|
name = m.group(3).rstrip(".")
|
|
|
|
|
if name and name not in seen:
|
|
|
|
|
seen.add(name)
|
|
|
|
|
print(name)
|
|
|
|
|
finally:
|
|
|
|
|
if inp is not sys.stdin:
|
|
|
|
|
inp.close()
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
raise SystemExit(main())
|