diff --git a/Makefile b/Makefile index 8ba12dd0d..6e32ce91f 100644 --- a/Makefile +++ b/Makefile @@ -136,10 +136,6 @@ else cd - endif -.PHONY: generate-doc -generate-doc: ## Generate documentation of the flows JSON format - cd web && npm run generate-doc - .PHONY: update-config update-config: ## Update sample config from operator repo ./scripts/update-config.sh diff --git a/web/package.json b/web/package.json index 779401555..51b6c59f0 100644 --- a/web/package.json +++ b/web/package.json @@ -25,8 +25,7 @@ "lint": "./node_modules/.bin/eslint \"./src/**/*.{ts,tsx}\"", "format": "pretty-quick --branch main", "format-all": "prettier --write \"./src/**/*.{ts,tsx}\" && npm run fix-imports", - "fix-imports": "eslint --no-eslintrc --no-inline-config --parser '@typescript-eslint/parser' --plugin 'unused-imports' --plugin 'react-hooks' --rule 'unused-imports/no-unused-imports:error' --fix \"./src/**/*.{ts,tsx}\"", - "generate-doc": "typedoc --sort source-order --categorizeByGroup false --githubPages false --readme none --disableSources --out docs src/api/ipfix.ts --hideBreadcrumbs true --hideInPageTOC true" + "fix-imports": "eslint --no-eslintrc --no-inline-config --parser '@typescript-eslint/parser' --plugin 'unused-imports' --plugin 'react-hooks' --rule 'unused-imports/no-unused-imports:error' --fix \"./src/**/*.{ts,tsx}\"" }, "devDependencies": { "@babel/core": "^7.17.12", diff --git a/web/src/api/ipfix.ts b/web/src/api/ipfix.ts index 0cc57656f..0d18f310f 100644 --- a/web/src/api/ipfix.ts +++ b/web/src/api/ipfix.ts @@ -2,195 +2,117 @@ import { TFunction } from 'i18next'; import { RecordType } from '../model/flow-query'; -// Please keep this file documented: it is used in doc generation -// To regenerate doc, run `make generate-doc` - and also check this page: -// https://github.com/netobserv/network-observability-operator/blob/main/docs/GeneratingAsciidocAPI.md#generate-asciidoc-for-flows-json-format-reference - export interface Record { - labels: Labels; + labels: Flow; key: number; - fields: Fields; + fields: Flow; } -export const getRecordValue = (record: Record, fieldOrLabel: string, defaultValue?: string | number) => { +export const getRecordValue = (record: Record, field: Field, defaultValue?: string | number) => { /* TODO: fix following behavior: * Check if field exists first since /flow endpoint return fields as labels when using filters * This is mandatory to ensure fields types */ - if (record.fields[fieldOrLabel as keyof Fields] !== undefined) { - return record.fields[fieldOrLabel as keyof Fields]; + if (record.fields[field] !== undefined) { + return record.fields[field]; } // check if label exists - if (record.labels[fieldOrLabel as keyof Labels] !== undefined) { - return record.labels[fieldOrLabel as keyof Labels]; + if (record.labels[field] !== undefined) { + return record.labels[field]; } // fallback on default return defaultValue; }; -export interface Labels { - /** Source namespace */ +export type Field = keyof Flow; + +export interface Flow { SrcK8S_Namespace?: string; - /** Destination namespace */ DstK8S_Namespace?: string; - /** Source owner, such as Deployment, StatefulSet, etc. */ - SrcK8S_OwnerName?: string; - /** Destination owner, such as Deployment, StatefulSet, etc. */ - DstK8S_OwnerName?: string; - /** Kind of the source matched Kubernetes object, such as Pod, Service, etc. */ SrcK8S_Type?: string; - /** Kind of the destination matched Kubernetes object, such as Pod name, Service name, etc. */ DstK8S_Type?: string; - /** Flow direction from the node observation point*/ FlowDirection?: FlowDirection; - /** Type of record: 'flowLog' for regular flow logs, or 'allConnections', - * 'newConnection', 'heartbeat', 'endConnection' for conversation tracking */ _RecordType?: RecordType; -} - -export enum FlowDirection { - /** Incoming traffic, from the node observation point */ - Ingress = '0', - /** Outgoing traffic, from the node observation point */ - Egress = '1', - /** Inner traffic, with the same source and destination node */ - Inner = '2', - /** Both traffic (for Interface direction only), flow seen on both Ingress and Egress */ - Both = '3' -} - -export const getDirectionDisplayString = (value: FlowDirection, t: TFunction) => { - return value === FlowDirection.Ingress - ? t('Ingress') - : value === FlowDirection.Egress - ? t('Egress') - : value === FlowDirection.Inner - ? t('Inner') - : value === FlowDirection.Both - ? t('Both') - : t('n/a'); -}; - -export enum IfDirection { - /** Incoming traffic, from the network interface observation point */ - Ingress = '0', - /** Outgoing traffic, from the network interface observation point */ - Egress = '1' -} - -export interface Fields { - /** Source IP address (ipv4 or ipv6) */ SrcAddr?: string; - /** Destination IP address (ipv4 or ipv6) */ DstAddr?: string; - /** Source MAC address */ SrcMac?: string; - /** Destination MAC address */ DstMac?: string; - /** Name of the source matched Kubernetes object, such as Pod name, Service name, etc. */ SrcK8S_Name?: string; - /** Name of the destination matched Kubernetes object, such as Pod name, Service name, etc. */ DstK8S_Name?: string; - /** Source port */ SrcPort?: number; - /** Destination port */ DstPort?: number; - /** Kind of the source Kubernetes owner, such as Deployment, StatefulSet, etc. */ + SrcK8S_OwnerName?: string; + DstK8S_OwnerName?: string; SrcK8S_OwnerType?: string; - /** Kind of the destination Kubernetes owner, such as Deployment, StatefulSet, etc. */ DstK8S_OwnerType?: string; - /** Source node IP */ SrcK8S_HostIP?: string; - /** Destination node IP */ DstK8S_HostIP?: string; - /** Source node name */ SrcK8S_HostName?: string; - /** Destination node name */ DstK8S_HostName?: string; - /** Source zone */ SrcK8S_Zone?: string; - /** Destination zone */ DstK8S_Zone?: string; - /** Source network name (e.g. secondary networks or UDN) */ SrcK8S_NetworkName?: string; - /** Destination network name (e.g. secondary networks or UDN) */ DstK8S_NetworkName?: string; - /** Cluster name */ K8S_ClusterName?: string; - /** L4 protocol */ Proto?: number; - /** Network interface array */ Interfaces?: string[]; - /** Flow direction array from the network interface observation point */ IfDirections?: IfDirection[]; - /** UDNs labels array */ Udns?: string[]; - /** Network Events */ NetworkEvents?: string[]; - /** Logical OR combination of unique TCP flags comprised in the flow, as per RFC-9293, with additional custom flags to represent the following per-packet combinations: SYN+ACK (0x100), FIN+ACK (0x200) and RST+ACK (0x400). */ Flags?: string[]; - /** Number of packets */ Packets?: number; - /** In conversation tracking, A to B packets counter per conversation */ Packets_AB?: number; - /** In conversation tracking, B to A packets counter per conversation */ Packets_BA?: number; - /** Number of bytes */ Bytes?: number; - /** In conversation tracking, A to B bytes counter per conversation */ Bytes_AB?: number; - /** In conversation tracking, B to A bytes counter per conversation */ Bytes_BA?: number; - /** Differentiated Services Code Point Value */ Dscp?: number; - /** ICMP type */ IcmpType?: number; - /** ICMP code */ IcmpCode?: number; - /** Pkt TCP state for drops */ PktDropLatestState?: string; - /** Pkt cause for drops */ PktDropLatestDropCause?: string; - /** Pkt TCP flags for drops */ PktDropLatestFlags?: number; - /** Number of packets dropped by the kernel */ PktDropPackets?: number; - /** In conversation tracking, A to B packets dropped counter per conversation */ PktDropPackets_AB?: number; - /** In conversation tracking, B to A packets dropped counter per conversation */ PktDropPackets_BA?: number; - /** Number of bytes dropped by the kernel */ PktDropBytes?: number; - /** In conversation tracking, A to B bytes dropped counter per conversation */ PktDropBytes_AB?: number; - /** In conversation tracking, B to A bytes dropped counter per conversation */ PktDropBytes_BA?: number; - /** DNS record id */ DnsId?: number; - /** DNS flags for DNS record */ DnsFlags?: number; - /** Parsed DNS header RCODEs name */ DnsFlagsResponseCode?: string; - /** Calculated time between response and request, in milliseconds */ DnsLatencyMs?: number; - /** Error number returned from DNS tracker ebpf hook function */ DnsErrno?: number; - /** Start timestamp of this flow, in milliseconds */ TimeFlowStartMs?: number; - /** End timestamp of this flow, in milliseconds */ TimeFlowEndMs?: number; - /** Timestamp when this flow was received and processed by the flow collector, in seconds */ TimeReceived?: number; - /** TCP smoothed Round Trip Time (sRTT) in nanoseconds */ TimeFlowRttNs?: number; - /** In conversation tracking, the conversation identifier */ _HashId?: string; - /** In conversation tracking, a flag identifying the first flow */ _IsFirst?: string; - /** In conversation tracking, a counter of flow logs per conversation */ numFlowLogs?: number; - /** User Defined Network identifier */ UdnId?: string; } -export type Field = keyof Fields | keyof Labels; +export enum FlowDirection { + Ingress = '0', + Egress = '1', + Inner = '2', + Both = '3' +} + +export const getDirectionDisplayString = (value: FlowDirection, t: TFunction) => { + return value === FlowDirection.Ingress + ? t('Ingress') + : value === FlowDirection.Egress + ? t('Egress') + : value === FlowDirection.Inner + ? t('Inner') + : value === FlowDirection.Both + ? t('Both') + : t('n/a'); +}; + +export enum IfDirection { + Ingress = '0', + Egress = '1' +} diff --git a/web/src/api/loki.ts b/web/src/api/loki.ts index b535837f7..421cc627e 100644 --- a/web/src/api/loki.ts +++ b/web/src/api/loki.ts @@ -1,7 +1,7 @@ import { FlowScope, MetricType, StatFunction } from '../model/flow-query'; import { cyrb53 } from '../utils/hash'; import { getFunctionFromId, getRateFunctionFromId } from '../utils/overview-panels'; -import { Field, Fields, Labels, Record } from './ipfix'; +import { Field, Flow, Record } from './ipfix'; export interface AggregatedQueryResponse { resultType: string; @@ -39,16 +39,16 @@ export class GenericMetricsResult { export const parseStream = (raw: StreamResult): Record[] => { return raw.values.map(v => { - const fields = JSON.parse(v[1]) as Fields; + const fields = JSON.parse(v[1]) as Flow; return { - labels: raw.stream as unknown as Labels, + labels: raw.stream as Flow, key: cyrb53(v.join(',')), fields: fields }; }); }; -export interface RawTopologyMetric extends Fields, Labels {} +export type RawTopologyMetric = Flow; export interface RawTopologyMetrics { metric: RawTopologyMetric; diff --git a/web/src/utils/__tests__/flows.spec.ts b/web/src/utils/__tests__/flows.spec.ts index 10b9c99e0..642576ad5 100644 --- a/web/src/utils/__tests__/flows.spec.ts +++ b/web/src/utils/__tests__/flows.spec.ts @@ -1,4 +1,4 @@ -import { Fields, FlowDirection, IfDirection, Record } from '../../api/ipfix'; +import { FlowDirection, IfDirection, Record } from '../../api/ipfix'; import { mergeFlowReporters } from '../flows'; describe('mergeFlowReporters', () => { @@ -9,57 +9,57 @@ describe('mergeFlowReporters', () => { const flows: Record[] = [ { key: 1, - fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2' } as Fields, + fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2' }, labels: { FlowDirection: FlowDirection.Ingress } }, { key: 2, - fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2' } as Fields, + fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2' }, labels: { FlowDirection: FlowDirection.Egress } }, { key: 3, - fields: { SrcAddr: '10.0.0.2', DstAddr: '10.0.0.1' } as Fields, + fields: { SrcAddr: '10.0.0.2', DstAddr: '10.0.0.1' }, labels: { FlowDirection: FlowDirection.Ingress } }, { key: 4, - fields: { SrcAddr: '10.0.0.2', DstAddr: '10.0.0.1' } as Fields, + fields: { SrcAddr: '10.0.0.2', DstAddr: '10.0.0.1' }, labels: { FlowDirection: FlowDirection.Egress } }, { key: 5, - fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2' } as Fields, + fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2' }, labels: { FlowDirection: FlowDirection.Ingress } }, { key: 6, - fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2' } as Fields, + fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2' }, labels: { FlowDirection: FlowDirection.Egress } }, { key: 7, - fields: { SrcAddr: '10.0.0.1', DstAddr: '43.75.13.32' } as Fields, + fields: { SrcAddr: '10.0.0.1', DstAddr: '43.75.13.32' }, labels: { FlowDirection: FlowDirection.Egress } }, { key: 8, - fields: { SrcAddr: '43.75.13.32', DstAddr: '10.0.0.1' } as Fields, + fields: { SrcAddr: '43.75.13.32', DstAddr: '10.0.0.1' }, labels: { FlowDirection: FlowDirection.Ingress } }, { key: 9, - fields: { SrcAddr: '10.0.0.1', DstAddr: '43.75.13.32' } as Fields, + fields: { SrcAddr: '10.0.0.1', DstAddr: '43.75.13.32' }, labels: { FlowDirection: FlowDirection.Egress } }, { key: 10, - fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2', SrcPort: 8080 } as Fields, + fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2', SrcPort: 8080 }, labels: { FlowDirection: FlowDirection.Ingress } }, { key: 11, - fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2', SrcPort: 8080 } as Fields, + fields: { SrcAddr: '10.0.0.1', DstAddr: '10.0.0.2', SrcPort: 8080 }, labels: { FlowDirection: FlowDirection.Egress } } ]; @@ -78,7 +78,7 @@ describe('mergeFlowReporters', () => { IfDirections: [IfDirection.Ingress, IfDirection.Egress], Interfaces: ['eth0', 'abcd'], Udns: ['udn1', 'udn2'] - } as Fields, + }, labels: { FlowDirection: FlowDirection.Ingress } }, { @@ -89,7 +89,7 @@ describe('mergeFlowReporters', () => { IfDirections: [IfDirection.Ingress], Interfaces: ['genev'], Udns: ['udn3'] - } as Fields, + }, labels: { FlowDirection: FlowDirection.Egress } } ]; @@ -103,7 +103,7 @@ describe('mergeFlowReporters', () => { IfDirections: [IfDirection.Ingress, IfDirection.Egress, IfDirection.Ingress], Interfaces: ['eth0', 'abcd', 'genev'], Udns: ['udn1', 'udn2', 'udn3'] - } as Fields, + }, labels: { FlowDirection: FlowDirection.Ingress } }); }); diff --git a/web/src/utils/column-parser.ts b/web/src/utils/column-parser.ts index 66ecfed66..7ae9fa9cc 100644 --- a/web/src/utils/column-parser.ts +++ b/web/src/utils/column-parser.ts @@ -1,4 +1,4 @@ -import { getRecordValue, Record } from '../api/ipfix'; +import { Field, getRecordValue, Record } from '../api/ipfix'; import { Column, ColumnConfigDef, ColumnsId, ColValue } from './columns'; import { FieldConfig, FieldType } from './fields'; @@ -19,7 +19,7 @@ const getColumnOrRecordValue = ( } return defaultValue; } - return getRecordValue(record, arg, defaultValue); + return getRecordValue(record, arg as Field, defaultValue); }; const funcs: { [name: string]: (columns: Column[], record: Record, args: string[]) => ColValue } = { diff --git a/web/src/utils/fields.ts b/web/src/utils/fields.ts index fe6abd071..18a5b0ea3 100644 --- a/web/src/utils/fields.ts +++ b/web/src/utils/fields.ts @@ -1,9 +1,11 @@ +import { Field } from '../api/ipfix'; + export type FieldType = 'string' | 'string[]' | 'number' | 'number[]'; export type FieldFormat = 'IP'; export interface FieldConfig { - name: string; + name: Field; type: FieldType; format?: FieldFormat; description: string;