Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions APIReference/Service/DataTypeDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,19 @@ const dataTypeDetails: Dictionary<DataTypePageData> = {
},
],
},
{
name: "dnsMonitor",
type: "MonitorStepDnsMonitor",
required: false,
description:
"Configuration for DNS monitoring. Required for DNS monitor type. Defines query name (domain), record type, optional DNS server, port, timeout, and retry settings. See MonitorStepDnsMonitor.",
typeLinks: [
{
label: "MonitorStepDnsMonitor",
path: "monitor-step-dns-monitor",
},
],
},
],
values: [],
jsonExample: JSON.stringify(
Expand Down Expand Up @@ -2560,6 +2573,31 @@ const dataTypeDetails: Dictionary<DataTypePageData> = {
description:
"Whether the SNMP device is reachable. Use with 'True' or 'False'. Applies to: SNMP monitors.",
},
{
value: "DNS Response Time (in ms)",
description:
"The DNS query response time in milliseconds. Use with numeric FilterTypes. Applies to: DNS monitors.",
},
{
value: "DNS Is Online",
description:
"Whether the DNS resolution succeeded. Use with 'True' or 'False'. Applies to: DNS monitors.",
},
{
value: "DNS Record Value",
description:
"The value of a DNS record returned by the query. Use with string FilterTypes (Contains, EqualTo, etc.). Applies to: DNS monitors.",
},
{
value: "DNSSEC Is Valid",
description:
"Whether DNSSEC validation passed (AD flag present). Use with 'True' or 'False'. Applies to: DNS monitors.",
},
{
value: "DNS Record Exists",
description:
"Whether any DNS records were returned for the query. Use with 'True' or 'False'. Applies to: DNS monitors.",
},
{
value: "JavaScript Expression",
description:
Expand Down Expand Up @@ -3112,6 +3150,93 @@ const dataTypeDetails: Dictionary<DataTypePageData> = {
2,
),
},
"monitor-step-dns-monitor": {
title: "MonitorStepDnsMonitor",
description:
"Configuration for a DNS monitor step. Defines the domain to query, record type, optional custom DNS server, and timeout settings. Used as the 'dnsMonitor' property on a MonitorStep when the monitor type is 'DNS'. The criteria filters can then use 'DNS Is Online', 'DNS Response Time (in ms)', 'DNS Record Value', 'DNSSEC Is Valid', and 'DNS Record Exists' as CheckOn values.",
isEnum: false,
relatedTypes: [
{
name: "MonitorStep",
path: "monitor-step",
relationship: "Parent that holds this as dnsMonitor property",
},
{
name: "CheckOn",
path: "check-on",
relationship: "Use DNS-specific CheckOn values with DNS monitors",
},
],
properties: [
{
name: "queryName",
type: "string",
required: true,
description: "The domain name to query (e.g., 'example.com').",
},
{
name: "recordType",
type: "string (enum)",
required: true,
description:
"The DNS record type to query. Possible values: 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA', 'PTR', 'SRV', 'CAA'.",
},
{
name: "hostname",
type: "string",
required: false,
description:
"Custom DNS server to use for the query (e.g., '8.8.8.8'). Leave empty to use system default DNS resolver.",
},
{
name: "port",
type: "number",
required: false,
description: "DNS port. Default is 53.",
},
{
name: "timeout",
type: "number",
required: false,
description:
"Timeout for DNS queries in milliseconds. Default is 5000 (5 seconds).",
},
{
name: "retries",
type: "number",
required: false,
description: "Number of retries for failed DNS queries. Default is 3.",
},
],
values: [],
jsonExample: JSON.stringify(
{
"// Example 1: Basic A record lookup": {
queryName: "example.com",
recordType: "A",
timeout: 5000,
retries: 3,
},
"// Example 2: MX record with custom DNS server": {
queryName: "example.com",
recordType: "MX",
hostname: "8.8.8.8",
port: 53,
timeout: 5000,
retries: 3,
},
"// Example 3: TXT record for SPF verification": {
queryName: "example.com",
recordType: "TXT",
hostname: "1.1.1.1",
timeout: 5000,
retries: 3,
},
},
null,
2,
),
},
};

export default class ServiceHandler {
Expand Down
8 changes: 8 additions & 0 deletions Common/Server/Services/MonitorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ export class Service extends DatabaseService<Model> {
monitorDestination = `${monitorDestination}:${port}`;
}
}

// For DNS monitors, use the queryName from dnsMonitor config
if (monitorType === MonitorType.DNS && firstStep?.data?.dnsMonitor) {
monitorDestination = firstStep.data.dnsMonitor.queryName || "";
if (firstStep.data.dnsMonitor.hostname) {
monitorDestination = `${monitorDestination} @${firstStep.data.dnsMonitor.hostname}`;
}
}
}
}

Expand Down
183 changes: 183 additions & 0 deletions Common/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import DataToProcess from "../DataToProcess";
import CompareCriteria from "./CompareCriteria";
import {
CheckOn,
CriteriaFilter,
FilterType,
} from "../../../../Types/Monitor/CriteriaFilter";
import DnsMonitorResponse from "../../../../Types/Monitor/DnsMonitor/DnsMonitorResponse";
import ProbeMonitorResponse from "../../../../Types/Probe/ProbeMonitorResponse";
import EvaluateOverTime from "./EvaluateOverTime";
import CaptureSpan from "../../Telemetry/CaptureSpan";
import logger from "../../Logger";

export default class DnsMonitorCriteria {
@CaptureSpan()
public static async isMonitorInstanceCriteriaFilterMet(input: {
dataToProcess: DataToProcess;
criteriaFilter: CriteriaFilter;
}): Promise<string | null> {
let threshold: number | string | undefined | null =
input.criteriaFilter.value;

const dataToProcess: ProbeMonitorResponse =
input.dataToProcess as ProbeMonitorResponse;

const dnsResponse: DnsMonitorResponse | undefined =
dataToProcess.dnsResponse;

let overTimeValue: Array<number | boolean> | number | boolean | undefined =
undefined;

if (
input.criteriaFilter.evaluateOverTime &&
input.criteriaFilter.evaluateOverTimeOptions
) {
try {
overTimeValue = await EvaluateOverTime.getValueOverTime({
projectId: (input.dataToProcess as ProbeMonitorResponse).projectId,
monitorId: input.dataToProcess.monitorId!,
evaluateOverTimeOptions: input.criteriaFilter.evaluateOverTimeOptions,
metricType: input.criteriaFilter.checkOn,
});

if (Array.isArray(overTimeValue) && overTimeValue.length === 0) {
overTimeValue = undefined;
}
} catch (err) {
logger.error(
`Error in getting over time value for ${input.criteriaFilter.checkOn}`,
);
logger.error(err);
overTimeValue = undefined;
}
}

// Check if DNS is online
if (input.criteriaFilter.checkOn === CheckOn.DnsIsOnline) {
const currentIsOnline: boolean | Array<boolean> =
(overTimeValue as Array<boolean>) ||
(input.dataToProcess as ProbeMonitorResponse).isOnline;

return CompareCriteria.compareCriteriaBoolean({
value: currentIsOnline,
criteriaFilter: input.criteriaFilter,
});
}

// Check DNS response time
if (input.criteriaFilter.checkOn === CheckOn.DnsResponseTime) {
threshold = CompareCriteria.convertToNumber(threshold);

if (threshold === null || threshold === undefined) {
return null;
}

const currentResponseTime: number | Array<number> =
(overTimeValue as Array<number>) ||
dnsResponse?.responseTimeInMs ||
(input.dataToProcess as ProbeMonitorResponse).responseTimeInMs;

if (currentResponseTime === null || currentResponseTime === undefined) {
return null;
}

return CompareCriteria.compareCriteriaNumbers({
value: currentResponseTime,
threshold: threshold as number,
criteriaFilter: input.criteriaFilter,
});
}

// Check if DNS record exists
if (input.criteriaFilter.checkOn === CheckOn.DnsRecordExists) {
const exists: boolean = Boolean(
dnsResponse?.records && dnsResponse.records.length > 0,
);

const isTrue: boolean =
input.criteriaFilter.filterType === FilterType.True;
const isFalse: boolean =
input.criteriaFilter.filterType === FilterType.False;

if (exists && isTrue) {
return `DNS records exist for the query.`;
}

if (!exists && isFalse) {
return `No DNS records found for the query.`;
}

return null;
}

// Check DNSSEC validity
if (input.criteriaFilter.checkOn === CheckOn.DnssecIsValid) {
const isTrue: boolean =
input.criteriaFilter.filterType === FilterType.True;
const isFalse: boolean =
input.criteriaFilter.filterType === FilterType.False;

if (dnsResponse?.isDnssecValid === undefined) {
return null;
}

if (dnsResponse.isDnssecValid && isTrue) {
return `DNSSEC is valid.`;
}

if (!dnsResponse.isDnssecValid && isFalse) {
return `DNSSEC is not valid.`;
}

return null;
}

// Check DNS record value
if (input.criteriaFilter.checkOn === CheckOn.DnsRecordValue) {
if (!dnsResponse?.records || dnsResponse.records.length === 0) {
return null;
}

// Check if any record value matches the criteria
for (const record of dnsResponse.records) {
const recordValue: string = record.value;

// Try numeric comparison first
if (
typeof threshold === "number" ||
(typeof threshold === "string" && !isNaN(Number(threshold)))
) {
const numericThreshold: number | null =
CompareCriteria.convertToNumber(threshold);

if (numericThreshold !== null && !isNaN(Number(recordValue))) {
const result: string | null =
CompareCriteria.compareCriteriaNumbers({
value: Number(recordValue),
threshold: numericThreshold,
criteriaFilter: input.criteriaFilter,
});

if (result) {
return `DNS record (${record.type}): ${result}`;
}
}
}

// String comparison
const result: string | null = CompareCriteria.compareCriteriaStrings({
value: recordValue,
threshold: String(threshold),
criteriaFilter: input.criteriaFilter,
});

if (result) {
return `DNS record (${record.type}): ${result}`;
}
}
}

return null;
}
}
13 changes: 13 additions & 0 deletions Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import MetricMonitorCriteria from "./Criteria/MetricMonitorCriteria";
import TraceMonitorCriteria from "./Criteria/TraceMonitorCriteria";
import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
import SnmpMonitorCriteria from "./Criteria/SnmpMonitorCriteria";
import DnsMonitorCriteria from "./Criteria/DnsMonitorCriteria";
import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
Expand Down Expand Up @@ -493,6 +494,18 @@ ${contextBlock}
}
}

if (input.monitor.monitorType === MonitorType.DNS) {
const dnsMonitorResult: string | null =
await DnsMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
dataToProcess: input.dataToProcess,
criteriaFilter: input.criteriaFilter,
});

if (dnsMonitorResult) {
return dnsMonitorResult;
}
}

return null;
}

Expand Down
Loading
Loading