mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
refactor(pcap): centralize DNS packet facts
This commit is contained in:
1 parent
5aa23c3d17
commit
98e47e37a2
3 files changed
+53
-24
No files matched your search
@@ -1,5 +1,6 @@
|
||||
type Fields = (name: string) => readonly string[];
|
||||
interface Attribution {
|
||||
interface DnsPacketFacts {
|
||||
readonly questions: readonly string[];
|
||||
readonly incomplete: boolean;
|
||||
readonly assigned: readonly (readonly [string, string])[];
|
||||
readonly unattributed: readonly string[];
|
||||
@@ -25,11 +26,16 @@ const nameKey = (name: string): string =>
|
||||
* ambiguous flat inventories instead of reconstructing missing hierarchy.
|
||||
* Validate that correspondence before joining any owner to an address.
|
||||
*/
|
||||
export const attributeDnsAddresses = (field: Fields): Attribution => {
|
||||
export const parseDnsPacketFields = (field: Fields): DnsPacketFacts => {
|
||||
const questions = field("dns.qry.name");
|
||||
const retainedQuestions = [
|
||||
...new Set(questions.filter((value) => value !== "")),
|
||||
];
|
||||
const ipv4 = field("dns.a");
|
||||
const ipv6 = field("dns.aaaa");
|
||||
const addresses = [...ipv4, ...ipv6].filter((value) => value !== "");
|
||||
const unknown = (): Attribution => ({
|
||||
const unknown = (): DnsPacketFacts => ({
|
||||
questions: retainedQuestions,
|
||||
assigned: [],
|
||||
unattributed: addresses,
|
||||
incomplete:
|
||||
@@ -50,8 +56,9 @@ export const attributeDnsAddresses = (field: Fields): Attribution => {
|
||||
return Number.isInteger(value) && value <= 65535 ? value : null;
|
||||
};
|
||||
const response = field("dns.flags.response");
|
||||
const questions = field("dns.qry.name");
|
||||
const questionTypes = field("dns.qry.type");
|
||||
const questionType =
|
||||
questionTypes.length === 1 ? dnsType(questionTypes[0]) : null;
|
||||
const answers = count("dns.count.answers");
|
||||
const authority = count("dns.count.auth_rr");
|
||||
const additional = count("dns.count.add_rr");
|
||||
@@ -74,7 +81,12 @@ export const attributeDnsAddresses = (field: Fields): Attribution => {
|
||||
)
|
||||
return unknown();
|
||||
if (addresses.length === 0)
|
||||
return { assigned: [], unattributed: [], incomplete: false };
|
||||
return {
|
||||
questions: retainedQuestions,
|
||||
assigned: [],
|
||||
unattributed: [],
|
||||
incomplete: false,
|
||||
};
|
||||
// Only IN establishes Internet address semantics. An ANY-class question
|
||||
// also permits IN answers; other classes retain the producer values without
|
||||
// interpreting their class-specific RDATA as a resolved Internet address.
|
||||
@@ -90,7 +102,7 @@ export const attributeDnsAddresses = (field: Fields): Attribution => {
|
||||
questions.length !== 1 ||
|
||||
questions[0] === "" ||
|
||||
questionTypes.length !== 1 ||
|
||||
![1, 28, 255].includes(dnsType(questionTypes[0]) ?? -1)
|
||||
![1, 28, 255].includes(questionType ?? -1)
|
||||
)
|
||||
return unknown();
|
||||
let a = 0,
|
||||
@@ -140,12 +152,16 @@ export const attributeDnsAddresses = (field: Fields): Attribution => {
|
||||
record.dnsClass === 1 &&
|
||||
record.owner !== "" &&
|
||||
reachable.has(record.owner) &&
|
||||
(dnsType(questionTypes[0]) === 255 ||
|
||||
dnsType(questionTypes[0]) === record.type) &&
|
||||
(questionType === 255 || questionType === record.type) &&
|
||||
!links.has(record.owner)
|
||||
)
|
||||
assigned.push([questions[0]!, record.value!]);
|
||||
else unattributed.push(record.value!);
|
||||
}
|
||||
return { assigned, unattributed, incomplete: unattributed.length > 0 };
|
||||
return {
|
||||
questions: retainedQuestions,
|
||||
assigned,
|
||||
unattributed,
|
||||
incomplete: unattributed.length > 0,
|
||||
};
|
||||
};
|
||||
@@ -114,6 +114,25 @@ it.each([
|
||||
expect(result.partial).toBe(true);
|
||||
});
|
||||
|
||||
it("retains exact nonempty DNS questions when response ownership is unresolved", async () => {
|
||||
const result = await parseTsharkJsonOutput(
|
||||
capture([
|
||||
{
|
||||
"dns.flags.response": ["1"],
|
||||
"dns.qry.name": ["", "WWW.Example", "www.example", "WWW.Example"],
|
||||
"dns.a": ["192.0.2.10"],
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(result.dnsQueries).toEqual([
|
||||
{ query: "WWW.Example", response_addresses: [], response_address_count: 0 },
|
||||
{ query: "www.example", response_addresses: [], response_address_count: 0 },
|
||||
]);
|
||||
expect(result.dnsQueryCount).toBe(2);
|
||||
expect(result.unattributedDnsAddresses).toEqual(["192.0.2.10"]);
|
||||
expect(result.partial).toBe(true);
|
||||
});
|
||||
|
||||
it("reports unknown RR coverage when a DNS response omits all count and address fields", async () => {
|
||||
const result = await parseTsharkJsonOutput(
|
||||
capture([
|
||||
|
||||
+9
-15
@@ -6,7 +6,7 @@ import {
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { parseRecordedTransportUrl } from "../domain/recordedTransportUrl.js";
|
||||
import { fixedAllowanceOptions } from "../domain/fixedAllowanceRemediation.js";
|
||||
import { attributeDnsAddresses } from "./PcapDnsOutput.js";
|
||||
import { parseDnsPacketFields } from "./PcapDnsOutput.js";
|
||||
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
|
||||
|
||||
const OPERATION = "inspect_pcap_capture";
|
||||
@@ -298,24 +298,18 @@ export const parseTsharkJsonOutput = async (
|
||||
if (!endpoints.has(address)) charge(address);
|
||||
endpoints.set(address, (endpoints.get(address) ?? 0) + 1);
|
||||
}
|
||||
const questions = new Set(
|
||||
field("dns.qry.name").filter((value) => value !== ""),
|
||||
);
|
||||
for (const question of questions)
|
||||
const dnsFacts = parseDnsPacketFields(field);
|
||||
for (const question of dnsFacts.questions)
|
||||
if (!dns.has(question)) {
|
||||
charge(question);
|
||||
dns.set(question, new Set());
|
||||
}
|
||||
{
|
||||
const attribution = attributeDnsAddresses(field);
|
||||
unresolvedDnsOwnership ||= attribution.incomplete;
|
||||
for (const [question, address] of attribution.assigned)
|
||||
add(dns.get(question)!, address);
|
||||
if (attribution.unattributed.length > 0) {
|
||||
unattributedPackets += 1;
|
||||
for (const address of attribution.unattributed)
|
||||
add(unattributed, address);
|
||||
}
|
||||
unresolvedDnsOwnership ||= dnsFacts.incomplete;
|
||||
for (const [question, address] of dnsFacts.assigned)
|
||||
add(dns.get(question)!, address);
|
||||
if (dnsFacts.unattributed.length > 0) {
|
||||
unattributedPackets += 1;
|
||||
for (const address of dnsFacts.unattributed) add(unattributed, address);
|
||||
}
|
||||
for (const value of field("tls.handshake.extensions_server_name"))
|
||||
if (value !== "") add(sni, value);
|
||||
|
||||
Reference in new issue
Block a user