diff --git a/config/sample-config.yaml b/config/sample-config.yaml index 635a67f8c..74fd05d19 100644 --- a/config/sample-config.yaml +++ b/config/sample-config.yaml @@ -441,6 +441,14 @@ frontend: filter: dns_flag_response_code default: true width: 5 + - id: DNSErrNo + group: DNS + name: DNS Error + tooltip: DNS error number returned by bpf_skb_load_bytes function. + field: DnsErrno + filter: dns_errno + default: false + width: 5 filters: - id: src_namespace name: Namespace @@ -760,6 +768,10 @@ frontend: - A IANA RCODE number like 0, 3, 9 - A IANA RCODE name like NoError, NXDomain, NotAuth docUrl: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 + - id: dns_errno + name: DNS Error + component: autocomplete + hint: Specify a single DNS error number. - id: time_flow_rtt name: Flow RTT component: number diff --git a/web/src/components/netflow-traffic.tsx b/web/src/components/netflow-traffic.tsx index 5089e6d38..e64de4062 100644 --- a/web/src/components/netflow-traffic.tsx +++ b/web/src/components/netflow-traffic.tsx @@ -298,7 +298,8 @@ export const NetflowTraffic: React.FC<{ col => (!isSidePanel || !col.isCommon) && (isConnectionTracking() || ![ColumnsId.recordtype, ColumnsId.hashid].includes(col.id)) && - (isDNSTracking() || ![ColumnsId.dnsid, ColumnsId.dnslatency, ColumnsId.dnsresponsecode].includes(col.id)) && + (isDNSTracking() || + ![ColumnsId.dnsid, ColumnsId.dnslatency, ColumnsId.dnsresponsecode, ColumnsId.dnserrno].includes(col.id)) && (isFlowRTT() || ![ColumnsId.rttTime].includes(col.id)) ); }, diff --git a/web/src/model/filters.ts b/web/src/model/filters.ts index 25e8b0744..aceda9134 100644 --- a/web/src/model/filters.ts +++ b/web/src/model/filters.ts @@ -33,6 +33,7 @@ export type FilterId = | 'dns_id' | 'dns_latency' | 'dns_flag_response_code' + | 'dns_errno' | 'time_flow_rtt'; export interface FilterConfigDef { diff --git a/web/src/utils/columns.ts b/web/src/utils/columns.ts index c6428cbc5..ba56ccc6a 100644 --- a/web/src/utils/columns.ts +++ b/web/src/utils/columns.ts @@ -52,6 +52,7 @@ export enum ColumnsId { dnsid = 'DNSId', dnslatency = 'DNSLatency', dnsresponsecode = 'DNSResponseCode', + dnserrno = 'DNSErrNo', hostaddr = 'K8S_HostIP', srchostaddr = 'SrcK8S_HostIP', dsthostaddr = 'DstK8S_HostIP', diff --git a/web/src/utils/dns.ts b/web/src/utils/dns.ts index 12e9bd753..02194ae34 100644 --- a/web/src/utils/dns.ts +++ b/web/src/utils/dns.ts @@ -35,3 +35,51 @@ export type DNS_CODE_NAMES = typeof dnsRcodesNames[number]; export const getDNSRcodeDescription = (name: DNS_CODE_NAMES): string => { return DNS_RCODES.find(v => v.name === name)?.description || 'Unassigned'; }; + +// https://elixir.bootlin.com/linux/v4.7/source/include/uapi/asm-generic/errno-base.h +export const DNS_ERRORS: ReadOnlyValues = [ + { value: 1, name: 'EPERM', description: 'Operation not permitted' }, + { value: 2, name: 'ENOENT', description: 'No such file or directory' }, + { value: 3, name: 'ESRCH', description: 'No such process' }, + { value: 4, name: 'EINTR', description: 'Interrupted system call' }, + { value: 5, name: 'EIO', description: 'I/O error' }, + { value: 6, name: 'ENXIO', description: 'No such device or address' }, + { value: 7, name: 'E2BIG', description: 'Argument list too long' }, + { value: 8, name: 'ENOEXEC', description: 'Exec format error' }, + { value: 9, name: 'EBADF', description: 'Bad file number' }, + { value: 10, name: 'ECHILD', description: 'No child processes' }, + { value: 11, name: 'EAGAIN', description: 'Try again' }, + { value: 12, name: 'ENOMEM', description: 'Out of memory' }, + { value: 13, name: 'EACCES', description: 'Permission denied' }, + { value: 14, name: 'EFAULT', description: 'Bad address' }, + { value: 15, name: 'ENOTBLK', description: 'Block device required' }, + { value: 16, name: 'EBUSY', description: 'Device or resource busy' }, + { value: 17, name: 'EEXIST', description: 'File exists' }, + { value: 18, name: 'EXDEV', description: 'Cross-device link' }, + { value: 19, name: 'ENODEV', description: 'No such device' }, + { value: 20, name: 'ENOTDIR', description: 'Not a directory' }, + { value: 21, name: 'EISDIR', description: 'Is a directory' }, + { value: 22, name: 'EINVAL', description: 'Invalid argument' }, + { value: 23, name: 'ENFILE', description: 'File table overflow' }, + { value: 24, name: 'EMFILE', description: 'Too many open files' }, + { value: 25, name: 'ENOTTY', description: 'Not a typewriter' }, + { value: 26, name: 'ETXBSY', description: 'Text file busy' }, + { value: 27, name: 'EFBIG', description: 'File too large' }, + { value: 28, name: 'ENOSPC', description: 'No space left on device' }, + { value: 29, name: 'ESPIPE', description: 'Illegal seek' }, + { value: 30, name: 'EROFS', description: 'Read-only file system' }, + { value: 31, name: 'EMLINK', description: 'Too many links' }, + { value: 32, name: 'EPIPE', description: 'Broken pipe' }, + { value: 33, name: 'EDOM', description: 'Math argument out of domain of func' }, + { value: 34, name: 'ERANGE', description: 'Math result not representable' } +] as const; + +const dnsErrorsValues = DNS_ERRORS.map(v => v.value); +export type DNS_ERRORS_VALUES = typeof dnsErrorsValues[number]; + +const dnsErrorsNames = DNS_ERRORS.map(v => v.name); +export type DNS_ERRORS_NAMES = typeof dnsErrorsNames[number]; + +export const getDNSErrorDescription = (value: DNS_ERRORS_VALUES): string => { + return DNS_ERRORS.find(v => v.value === value)?.description || ''; +}; diff --git a/web/src/utils/filter-definitions.ts b/web/src/utils/filter-definitions.ts index ac493166e..5debeda59 100644 --- a/web/src/utils/filter-definitions.ts +++ b/web/src/utils/filter-definitions.ts @@ -28,7 +28,8 @@ import { getDropStateOptions, getDropCauseOptions, getDirectionOptionsAsync, - findDirectionOption + findDirectionOption, + getDnsErrorCodeOptions } from './filter-options'; import { ColumnConfigDef } from './columns'; @@ -287,6 +288,8 @@ export const getFilterDefinitions = ( getOptions = cap10(getDropCauseOptions); } else if (d.id.includes('dns_flag_response_code')) { getOptions = cap10(getDnsResponseCodeOptions); + } else if (d.id.includes('dns_errno')) { + getOptions = cap10(getDnsErrorCodeOptions); } return { getOptions, validate, encoder, checkCompletion }; }; diff --git a/web/src/utils/filter-options.ts b/web/src/utils/filter-options.ts index c6e5cb604..e568a4048 100644 --- a/web/src/utils/filter-options.ts +++ b/web/src/utils/filter-options.ts @@ -5,7 +5,7 @@ import { FlowDirection } from '../api/ipfix'; import { FilterOption } from '../model/filters'; import { splitResource, SplitStage } from '../model/resource'; import { autoCompleteCache } from './autocomplete-cache'; -import { DNS_RCODES } from './dns'; +import { DNS_ERRORS, DNS_RCODES } from './dns'; import { getPort, getService } from './port'; import { DROP_CAUSES, DROP_STATES } from './pkt-drop'; import { TFunction } from 'i18next'; @@ -125,6 +125,14 @@ export const getDnsResponseCodeOptions = (value: string): Promise => { + return Promise.resolve( + DNS_ERRORS.filter( + opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase()) + ).map(v => ({ name: v.name, value: String(v.value) })) + ); +}; + export const findProtocolOption = (nameOrVal: string) => { return protocolOptions.find(p => p.name.toLowerCase() === nameOrVal.toLowerCase() || p.value === nameOrVal); };