mirror of
https://github.com/rajnandan1/kener.git
synced 2026-08-07 07:14:56 +00:00
feat: enhance grpc, dns monitoring
This commit is contained in:
@@ -152,6 +152,13 @@ function IsValidNameServer(nameServer: string): boolean {
|
||||
const regex = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
|
||||
return regex.test(nameServer);
|
||||
}
|
||||
function IsValidDnsResolver(resolver: string): boolean {
|
||||
const ipType = ValidateIpAddress(resolver);
|
||||
if (ipType === "IP4" || ipType === "IP6") {
|
||||
return true;
|
||||
}
|
||||
return IsValidHost(resolver);
|
||||
}
|
||||
const IsValidURL = function (url: string): boolean {
|
||||
return /^(http|https):\/\/[^ "]+$/.test(url);
|
||||
};
|
||||
@@ -383,6 +390,7 @@ export {
|
||||
ValidateIpAddress,
|
||||
IsValidHost,
|
||||
IsValidNameServer,
|
||||
IsValidDnsResolver,
|
||||
IsValidURL,
|
||||
IsValidPort,
|
||||
CollapseStatusCounts,
|
||||
|
||||
+130
-11
@@ -1,7 +1,8 @@
|
||||
import dns2 from "dns2";
|
||||
import dgram, { type Socket } from "dgram";
|
||||
import tls from "tls";
|
||||
import { Resolver as NodeResolver } from "node:dns/promises";
|
||||
import { AllRecordTypes } from "../clientTools";
|
||||
import { AllRecordTypes, IsValidHost, ValidateIpAddress } from "../clientTools";
|
||||
|
||||
interface DNSAnswer {
|
||||
name: string;
|
||||
@@ -25,13 +26,32 @@ interface DNSRecordResult {
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
export interface DnsQueryOptions {
|
||||
transport?: "UDP" | "TLS";
|
||||
nameserverOverride?: string;
|
||||
tlsPort?: number;
|
||||
tlsServername?: string;
|
||||
allowSelfSignedCert?: boolean;
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
const DEFAULT_DOT_PORT = 853;
|
||||
const DEFAULT_QUERY_TIMEOUT_MS = 3000;
|
||||
|
||||
class DNSResolver {
|
||||
nameserver: string;
|
||||
socket: Socket;
|
||||
socket: Socket | null;
|
||||
|
||||
constructor() {
|
||||
this.nameserver = "8.8.8.8";
|
||||
this.socket = dgram.createSocket("udp4");
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
private getUdpSocket(): Socket {
|
||||
if (!this.socket) {
|
||||
this.socket = dgram.createSocket("udp4");
|
||||
}
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
createQuery(domain: string, type: string): InstanceType<typeof dns2.Packet> {
|
||||
@@ -49,11 +69,24 @@ class DNSResolver {
|
||||
return packet;
|
||||
}
|
||||
|
||||
private resolveTlsServername(nameserver: string, tlsServername?: string): string | undefined {
|
||||
const configuredServername = tlsServername?.trim();
|
||||
if (configuredServername) {
|
||||
return configuredServername;
|
||||
}
|
||||
if (IsValidHost(nameserver)) {
|
||||
return nameserver;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async query(domain: string, recordType: string, nameserverOverride?: string): Promise<DNSResponse> {
|
||||
const socket = this.getUdpSocket();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = this.createQuery(domain, recordType);
|
||||
const buffer = query.toBuffer();
|
||||
const targetNameserver = nameserverOverride || this.nameserver;
|
||||
const timeoutMs = DEFAULT_QUERY_TIMEOUT_MS;
|
||||
|
||||
const onMessage = (message: Buffer) => {
|
||||
clearTimeout(timeoutId);
|
||||
@@ -63,22 +96,87 @@ class DNSResolver {
|
||||
};
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
this.socket.removeListener("message", onMessage);
|
||||
socket.removeListener("message", onMessage);
|
||||
reject(new Error(`DNS query timed out for ${domain} (${recordType}) via ${targetNameserver}`));
|
||||
}, 3000);
|
||||
}, timeoutMs);
|
||||
|
||||
this.socket.once("message", onMessage);
|
||||
socket.once("message", onMessage);
|
||||
|
||||
this.socket.send(buffer, 0, buffer.length, 53, targetNameserver, (err: Error | null) => {
|
||||
socket.send(buffer, 0, buffer.length, 53, targetNameserver, (err: Error | null) => {
|
||||
if (err) {
|
||||
clearTimeout(timeoutId);
|
||||
this.socket.removeListener("message", onMessage);
|
||||
socket.removeListener("message", onMessage);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async queryOverTls(
|
||||
domain: string,
|
||||
recordType: string,
|
||||
nameserver: string,
|
||||
options: Pick<DnsQueryOptions, "tlsPort" | "tlsServername" | "allowSelfSignedCert" | "timeoutMs"> = {},
|
||||
): Promise<DNSResponse> {
|
||||
const query = this.createQuery(domain, recordType);
|
||||
const buffer = query.toBuffer();
|
||||
const lengthPrefix = Buffer.alloc(2);
|
||||
lengthPrefix.writeUInt16BE(buffer.length, 0);
|
||||
const message = Buffer.concat([lengthPrefix, buffer]);
|
||||
const port = options.tlsPort ?? DEFAULT_DOT_PORT;
|
||||
const timeoutMs = options.timeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS;
|
||||
const servername = this.resolveTlsServername(nameserver, options.tlsServername);
|
||||
const ipType = ValidateIpAddress(nameserver);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let responseBuffer = Buffer.alloc(0);
|
||||
let expectedLength: number | null = null;
|
||||
|
||||
const socket = tls.connect({
|
||||
host: nameserver,
|
||||
port,
|
||||
servername,
|
||||
rejectUnauthorized: !options.allowSelfSignedCert,
|
||||
family: ipType === "IP6" ? 6 : ipType === "IP4" ? 4 : undefined,
|
||||
});
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
socket.destroy();
|
||||
reject(new Error(`DNS-over-TLS query timed out for ${domain} (${recordType}) via ${nameserver}:${port}`));
|
||||
}, timeoutMs);
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
|
||||
socket.on("error", (err) => {
|
||||
cleanup();
|
||||
reject(err);
|
||||
});
|
||||
|
||||
socket.on("data", (chunk: Buffer) => {
|
||||
responseBuffer = Buffer.concat([responseBuffer, chunk]);
|
||||
|
||||
if (expectedLength === null && responseBuffer.length >= 2) {
|
||||
expectedLength = responseBuffer.readUInt16BE(0);
|
||||
}
|
||||
|
||||
if (expectedLength !== null && responseBuffer.length >= expectedLength + 2) {
|
||||
cleanup();
|
||||
const responseData = responseBuffer.subarray(2, 2 + expectedLength);
|
||||
// @ts-expect-error dns2 types are incomplete
|
||||
const response = dns2.Packet.parse(responseData) as DNSResponse;
|
||||
socket.end();
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("secureConnect", () => {
|
||||
socket.write(message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getAuthoritativeNameServers(domain: string, resolverNameserver?: string): Promise<string[]> {
|
||||
const resolver = new NodeResolver();
|
||||
resolver.setServers([resolverNameserver || this.nameserver]);
|
||||
@@ -140,12 +238,30 @@ class DNSResolver {
|
||||
async getRecord(
|
||||
domain: string,
|
||||
recordType: string,
|
||||
nameserverOverride?: string,
|
||||
options: DnsQueryOptions = {},
|
||||
): Promise<Record<string, DNSRecordResult[]>> {
|
||||
const results: Record<string, DNSRecordResult[]> = {};
|
||||
const transport = options.transport ?? "UDP";
|
||||
const nameserverOverride = options.nameserverOverride?.trim() || undefined;
|
||||
|
||||
try {
|
||||
const response = await this.queryAuthoritativeRecord(domain, recordType, nameserverOverride);
|
||||
let response: DNSResponse;
|
||||
|
||||
if (transport === "TLS") {
|
||||
if (!nameserverOverride) {
|
||||
throw new Error("Name server is required for DNS-over-TLS queries");
|
||||
}
|
||||
|
||||
response = await this.queryOverTls(domain, recordType, nameserverOverride, {
|
||||
tlsPort: options.tlsPort,
|
||||
tlsServername: options.tlsServername,
|
||||
allowSelfSignedCert: options.allowSelfSignedCert,
|
||||
timeoutMs: options.timeoutMs,
|
||||
});
|
||||
} else {
|
||||
response = await this.queryAuthoritativeRecord(domain, recordType, nameserverOverride);
|
||||
}
|
||||
|
||||
results[recordType] = response.answers.map((answer: DNSAnswer) => ({
|
||||
name: answer.name,
|
||||
type: recordType,
|
||||
@@ -157,7 +273,10 @@ class DNSResolver {
|
||||
console.error("Error querying DNS records:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.socket.close();
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
this.socket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,18 +32,26 @@ class DnsCall {
|
||||
let matchType = this.monitor.type_data.matchType;
|
||||
let values = this.monitor.type_data.values;
|
||||
const configuredNameServer = this.monitor.type_data.nameServer?.trim() || undefined;
|
||||
const transport = this.monitor.type_data.transport ?? "UDP";
|
||||
const queryStartTime = performance.now();
|
||||
|
||||
try {
|
||||
let dnsRes = await dnsResolver.getRecord(host, recordType, configuredNameServer);
|
||||
let dnsRes = await dnsResolver.getRecord(host, recordType, {
|
||||
transport,
|
||||
nameserverOverride: configuredNameServer,
|
||||
tlsPort: this.monitor.type_data.tlsPort,
|
||||
tlsServername: this.monitor.type_data.tlsServername,
|
||||
allowSelfSignedCert: this.monitor.type_data.allowSelfSignedCert,
|
||||
});
|
||||
let latency = Math.round(performance.now() - queryStartTime);
|
||||
const transportLabel = transport === "TLS" ? "DNS-over-TLS" : "DNS";
|
||||
|
||||
if (dnsRes[recordType] === undefined) {
|
||||
return {
|
||||
status: GC.DOWN,
|
||||
latency: latency,
|
||||
type: GC.REALTIME,
|
||||
error_message: `No DNS ${recordType} response found for ${host}`,
|
||||
error_message: `No ${transportLabel} ${recordType} response found for ${host}`,
|
||||
};
|
||||
}
|
||||
let data = dnsRes[recordType];
|
||||
@@ -55,7 +63,7 @@ class DnsCall {
|
||||
status: GC.DOWN,
|
||||
latency: latency,
|
||||
type: GC.REALTIME,
|
||||
error_message: `No DNS ${recordType} records returned for ${host}`,
|
||||
error_message: `No ${transportLabel} ${recordType} records returned for ${host}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,7 +74,7 @@ class DnsCall {
|
||||
status: GC.DOWN,
|
||||
latency: latency,
|
||||
type: GC.REALTIME,
|
||||
error_message: `DNS ${recordType} mismatch for ${host}. Missing: ${missingValues.join(", ")}`,
|
||||
error_message: `${transportLabel} ${recordType} mismatch for ${host}. Missing: ${missingValues.join(", ")}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -88,17 +96,18 @@ class DnsCall {
|
||||
status: GC.DOWN,
|
||||
latency: latency,
|
||||
type: GC.REALTIME,
|
||||
error_message: `DNS ${recordType} mismatch for ${host}. Got: ${dnsData.join(", ")}`,
|
||||
error_message: `${transportLabel} ${recordType} mismatch for ${host}. Got: ${dnsData.join(", ")}`,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const latency = Math.round(performance.now() - queryStartTime);
|
||||
const transportLabel = transport === "TLS" ? "DNS-over-TLS" : "DNS";
|
||||
return {
|
||||
status: GC.DOWN,
|
||||
latency,
|
||||
type: GC.REALTIME,
|
||||
error_message: `DNS query failed for ${host} (${recordType}): ${message}`,
|
||||
error_message: `${transportLabel} query failed for ${host} (${recordType}): ${message}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -61,7 +61,7 @@ class GrpcCall {
|
||||
}
|
||||
|
||||
async execute(): Promise<MonitoringResult> {
|
||||
const { host, port, service, tls, timeout } = this.monitor.type_data;
|
||||
const { host, port, service, tls, insecure, timeout } = this.monitor.type_data;
|
||||
const timeoutMs = timeout || 10000;
|
||||
const target = `${host}:${port}`;
|
||||
|
||||
@@ -72,8 +72,20 @@ class GrpcCall {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const healthService = (proto.grpc as any).health.v1.Health;
|
||||
|
||||
const credentials = tls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
|
||||
const client = new healthService(target, credentials);
|
||||
const credentials =
|
||||
tls && insecure
|
||||
? grpc.credentials.createSsl(null, null, null, { rejectUnauthorized: false })
|
||||
: tls
|
||||
? grpc.credentials.createSsl()
|
||||
: grpc.credentials.createInsecure();
|
||||
const clientOptions: grpc.ChannelOptions = {};
|
||||
|
||||
// Keep channel-level insecure knob for grpc-js compatibility.
|
||||
if (tls && insecure) {
|
||||
clientOptions["grpc-node.tls_reject_unauthorized"] = 0;
|
||||
}
|
||||
|
||||
const client = new healthService(target, credentials, clientOptions);
|
||||
const deadline = new Date(Date.now() + timeoutMs);
|
||||
|
||||
const result = await new Promise<{ status: string }>((resolve, reject) => {
|
||||
|
||||
@@ -34,6 +34,10 @@ export interface DnsMonitorTypeData {
|
||||
lookupRecord: string;
|
||||
matchType: "ALL" | "ANY";
|
||||
values: string[];
|
||||
transport?: "UDP" | "TLS";
|
||||
tlsPort?: number;
|
||||
tlsServername?: string;
|
||||
allowSelfSignedCert?: boolean;
|
||||
}
|
||||
|
||||
export type { PingHost, PingMonitorTypeData };
|
||||
@@ -86,6 +90,7 @@ export interface GrpcMonitorTypeData {
|
||||
port: number;
|
||||
service?: string;
|
||||
tls?: boolean;
|
||||
insecure?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: DNS Monitor
|
||||
description: Validate DNS records against expected values
|
||||
description: Validate DNS records against expected values over UDP or DNS-over-TLS
|
||||
---
|
||||
|
||||
DNS monitors query records for a host and compare returned values to your expected values.
|
||||
@@ -14,17 +14,43 @@ Configure:
|
||||
- `matchType` (`ANY` or `ALL`, default `ANY`)
|
||||
- at least one expected value in `values`
|
||||
|
||||
`nameServer` is optional (leave blank for resolver defaults).
|
||||
For UDP transport, `nameServer` is optional (leave blank for resolver defaults).
|
||||
|
||||
For DNS-over-TLS (`transport: "TLS"`), `nameServer` is required.
|
||||
|
||||
## Configuration fields {#configuration-fields}
|
||||
|
||||
| Field | Type | Default | Notes |
|
||||
| :------------- | :--------- | :------ | :-------------------------- |
|
||||
| `host` | `string` | — | Required |
|
||||
| `nameServer` | `string` | `""` | Optional override |
|
||||
| `lookupRecord` | `string` | `A` | Required |
|
||||
| `matchType` | `ANY\|ALL` | `ANY` | Required |
|
||||
| `values` | `string[]` | `[]` | Required (non-empty values) |
|
||||
| Field | Type | Default | Notes |
|
||||
| :-------------------- | :------------ | :------ | :------------------------------------------------- |
|
||||
| `host` | `string` | — | Required |
|
||||
| `transport` | `UDP\|TLS` | `UDP` | Query transport |
|
||||
| `nameServer` | `string` | `""` | Optional for UDP; required for TLS |
|
||||
| `tlsPort` | `number` | `853` | DoT port when `transport` is `TLS` |
|
||||
| `tlsServername` | `string` | `""` | TLS SNI hostname for IP-based DoT resolvers |
|
||||
| `allowSelfSignedCert` | `boolean` | `false` | Disable TLS verification for private DoT resolvers |
|
||||
| `lookupRecord` | `string` | `A` | Required |
|
||||
| `matchType` | `ANY\|ALL` | `ANY` | Required |
|
||||
| `values` | `string[]` | `[]` | Required (non-empty values) |
|
||||
|
||||
## Transport modes {#transport-modes}
|
||||
|
||||
### UDP (default)
|
||||
|
||||
Standard DNS over UDP port 53. When `nameServer` is blank, Kener walks authoritative nameservers for the zone before falling back to `8.8.8.8`.
|
||||
|
||||
### DNS-over-TLS (DoT)
|
||||
|
||||
Encrypted DNS over TCP port 853 (RFC 7858). Queries go directly to the configured resolver — authoritative lookup is not used.
|
||||
|
||||
Common public resolvers:
|
||||
|
||||
| Resolver | Address | TLS Server Name |
|
||||
| :------- | :-------- | :------------------ |
|
||||
| Google | `8.8.8.8` | `dns.google` |
|
||||
| Cloudflare | `1.1.1.1` | `cloudflare-dns.com` |
|
||||
| Quad9 | `9.9.9.9` | `dns.quad9.net` |
|
||||
|
||||
When connecting to an IP address, set `tlsServername` to the provider's hostname if the resolver requires SNI.
|
||||
|
||||
## Match behavior {#match-behavior}
|
||||
|
||||
@@ -39,7 +65,9 @@ Before comparison, values are normalized by runtime logic:
|
||||
- trailing `.` removed
|
||||
- trimmed whitespace
|
||||
|
||||
## Example {#example}
|
||||
## Examples {#examples}
|
||||
|
||||
### UDP
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -53,8 +81,27 @@ Before comparison, values are normalized by runtime logic:
|
||||
}
|
||||
```
|
||||
|
||||
### DNS-over-TLS
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "DNS",
|
||||
"type_data": {
|
||||
"host": "example.com",
|
||||
"transport": "TLS",
|
||||
"nameServer": "1.1.1.1",
|
||||
"tlsPort": 853,
|
||||
"tlsServername": "cloudflare-dns.com",
|
||||
"lookupRecord": "A",
|
||||
"matchType": "ANY",
|
||||
"values": ["93.184.216.34"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting {#troubleshooting}
|
||||
|
||||
- **Unexpected DOWN**: copy exact record output (after normalization rules)
|
||||
- **No response**: check `lookupRecord` type and resolver reachability
|
||||
- **Partial mismatches**: use `ANY` for multi-value dynamic DNS setups
|
||||
- **DoT TLS errors**: set `tlsServername` when using an IP resolver; enable `allowSelfSignedCert` only for trusted private resolvers
|
||||
|
||||
@@ -31,8 +31,9 @@ Connection errors and timeouts return **DOWN**.
|
||||
| `host` | `string` | — | Required |
|
||||
| `port` | `number` | `50051` | Required |
|
||||
| `service` | `string` | `""` | Fully qualified service name; empty = overall |
|
||||
| `tls` | `boolean` | `false` | Use TLS credentials |
|
||||
| `timeout` | `number` | `10000` | Request deadline in ms |
|
||||
| `tls` | `boolean` | `false` | Use TLS credentials |
|
||||
| `insecure` | `boolean` | `false` | Skip TLS certificate verification (TLS only) |
|
||||
| `timeout` | `number` | `10000` | Request deadline in ms |
|
||||
|
||||
## Example {#example}
|
||||
|
||||
@@ -44,6 +45,7 @@ Connection errors and timeouts return **DOWN**.
|
||||
"port": 50051,
|
||||
"service": "my.package.MyService",
|
||||
"tls": true,
|
||||
"insecure": true,
|
||||
"timeout": 5000
|
||||
}
|
||||
}
|
||||
@@ -54,4 +56,5 @@ Connection errors and timeouts return **DOWN**.
|
||||
- **Immediate DOWN**: wrong host/port, service not running, or firewall blocking the connection
|
||||
- **DEGRADED**: server returned `UNKNOWN` or `SERVICE_UNKNOWN` — verify the service name is registered
|
||||
- **Timeout**: increase `timeout` or check network latency to the gRPC server
|
||||
- **TLS errors**: ensure the server has a valid certificate, or check if TLS should be disabled
|
||||
- **TLS errors**: ensure the server has a valid certificate, disable `insecure`, or check if TLS should be disabled
|
||||
- **Self-signed certificate** (messages mentioning `--use-system-ca`): set `tls: true` and `insecure: true` on the monitor (same as `grpcurl -insecure`). Alternatively, trust the issuer: run Node with `NODE_OPTIONS='--use-system-ca'` so the OS CA store is used (Node 22+ defaults to bundled CA roots only)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import type { GroupMonitorTypeData, MonitoringResult } from "$lib/server/types/monitor.js";
|
||||
import { MONITOR_TYPES, type MonitorType } from "$lib/types/monitor.js";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { ValidateIpAddress, IsValidHost, IsValidNameServer, IsValidURL, IsValidPort } from "$lib/clientTools";
|
||||
import { ValidateIpAddress, IsValidHost, IsValidNameServer, IsValidDnsResolver, IsValidURL, IsValidPort } from "$lib/clientTools";
|
||||
import { GAMEDIG_SOCKET_TIMEOUT } from "$lib/anywhere";
|
||||
import { resolve } from "$app/paths";
|
||||
import clientResolver from "$lib/client/resolver.js";
|
||||
@@ -153,7 +153,16 @@
|
||||
const data = typeData as any;
|
||||
if (!data.host || !IsValidHost(data.host)) return false;
|
||||
const nameServer = (data.nameServer || "").trim();
|
||||
if (nameServer && !IsValidNameServer(nameServer)) return false;
|
||||
const transport = data.transport || "UDP";
|
||||
if (transport === "TLS") {
|
||||
if (!nameServer || !IsValidDnsResolver(nameServer)) return false;
|
||||
const tlsPort = Number(data.tlsPort ?? 853);
|
||||
if (!IsValidPort(String(tlsPort))) return false;
|
||||
const tlsServername = (data.tlsServername || "").trim();
|
||||
if (tlsServername && !IsValidHost(tlsServername)) return false;
|
||||
} else if (nameServer && !IsValidNameServer(nameServer)) {
|
||||
return false;
|
||||
}
|
||||
if (!data.lookupRecord) return false;
|
||||
if (!data.values || !Array.isArray(data.values) || data.values.length === 0) return false;
|
||||
const hasNonEmptyValue = data.values.some((val: string) => val && val.trim() !== "");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Input } from "$lib/components/ui/input/index.js";
|
||||
import { Label } from "$lib/components/ui/label/index.js";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import { Switch } from "$lib/components/ui/switch/index.js";
|
||||
import * as Select from "$lib/components/ui/select/index.js";
|
||||
import * as InputGroup from "$lib/components/ui/input-group/index.js";
|
||||
import Plus from "@lucide/svelte/icons/plus";
|
||||
@@ -16,9 +17,14 @@
|
||||
if (!data.nameServer) data.nameServer = "";
|
||||
if (!data.lookupRecord) data.lookupRecord = "A";
|
||||
if (!data.matchType) data.matchType = "ANY";
|
||||
if (!data.transport) data.transport = "UDP";
|
||||
if (!data.tlsPort) data.tlsPort = 853;
|
||||
if (!data.tlsServername) data.tlsServername = "";
|
||||
if (data.allowSelfSignedCert === undefined) data.allowSelfSignedCert = false;
|
||||
if (!data.values) data.values = [""];
|
||||
|
||||
const recordTypes = Object.keys(AllRecordTypes);
|
||||
const usesTls = $derived(data.transport === "TLS");
|
||||
|
||||
function addValue() {
|
||||
data.values = [...data.values, ""];
|
||||
@@ -31,17 +37,75 @@
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="dns-transport">Transport</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={data.transport}
|
||||
onValueChange={(v) => {
|
||||
if (v) data.transport = v;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="dns-transport" class="w-full">
|
||||
{data.transport === "TLS" ? "DNS-over-TLS" : "UDP"}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="UDP">UDP - Standard DNS (port 53)</Select.Item>
|
||||
<Select.Item value="TLS">TLS - DNS-over-TLS (port 853)</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="dns-host">Host <span class="text-destructive">*</span></Label>
|
||||
<Input id="dns-host" bind:value={data.host} placeholder="example.com" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="dns-nameserver">Name Server (optional)</Label>
|
||||
<Input id="dns-nameserver" bind:value={data.nameServer} placeholder="Leave empty for authoritative lookup" />
|
||||
<p class="text-muted-foreground text-xs">Leave blank to use authoritative DNS nameservers automatically.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="dns-nameserver">
|
||||
Name Server
|
||||
{#if usesTls}
|
||||
<span class="text-destructive">*</span>
|
||||
{:else}
|
||||
(optional)
|
||||
{/if}
|
||||
</Label>
|
||||
<Input
|
||||
id="dns-nameserver"
|
||||
bind:value={data.nameServer}
|
||||
placeholder={usesTls ? "1.1.1.1 or dns.example.com" : "Leave empty for authoritative lookup"}
|
||||
/>
|
||||
{#if usesTls}
|
||||
<p class="text-muted-foreground text-xs">
|
||||
DoT resolver address. For IP resolvers, set TLS Server Name when required (e.g. 8.8.8.8 → dns.google).
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-muted-foreground text-xs">Leave blank to use authoritative DNS nameservers automatically.</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if usesTls}
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="dns-tls-port">TLS Port</Label>
|
||||
<Input id="dns-tls-port" type="number" min="1" max="65535" bind:value={data.tlsPort} placeholder="853" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if usesTls}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="dns-tls-servername">TLS Server Name (optional)</Label>
|
||||
<Input id="dns-tls-servername" bind:value={data.tlsServername} placeholder="dns.google" />
|
||||
<p class="text-muted-foreground text-xs">SNI hostname for TLS. Required for many public resolvers when using an IP address.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 pt-6">
|
||||
<Switch id="dns-self-signed" bind:checked={data.allowSelfSignedCert} />
|
||||
<Label for="dns-self-signed">Allow self-signed TLS certificates</Label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="dns-record">Lookup Record</Label>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
if (!data.service) data.service = "";
|
||||
if (!data.timeout) data.timeout = 10000;
|
||||
if (data.tls === undefined) data.tls = false;
|
||||
if (data.insecure === undefined) data.insecure = false;
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -43,4 +44,11 @@
|
||||
<Switch id="grpc-tls" bind:checked={data.tls} />
|
||||
<Label for="grpc-tls">Use TLS</Label>
|
||||
</div>
|
||||
|
||||
{#if data.tls}
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="grpc-insecure" bind:checked={data.insecure} />
|
||||
<Label for="grpc-insecure">Allow Insecure TLS (skip certificate verification)</Label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user