From 941ba1ba5d27d2c3f76040c03112e028889e3865 Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Fri, 6 Sep 2024 15:18:29 +0200 Subject: [PATCH] NETOBSERV-1833: decompose TCPflags bitfield --- web/locales/en/plugin__netobserv-plugin.json | 1 + .../components/drawer/record/record-field.tsx | 28 +++++++------- web/src/utils/__tests__/tcp-flags.spec.ts | 17 +++++++++ web/src/utils/filter-options.ts | 8 ++-- web/src/utils/tcp-flags.ts | 29 +++++++++++++++ web/src/utils/tcp_flags.ts | 37 ------------------- 6 files changed, 66 insertions(+), 54 deletions(-) create mode 100644 web/src/utils/__tests__/tcp-flags.spec.ts create mode 100644 web/src/utils/tcp-flags.ts delete mode 100644 web/src/utils/tcp_flags.ts diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 1865b5255..65cd67d3d 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -43,6 +43,7 @@ "Show related documentation": "Show related documentation", "Value": "Value", "Examples": "Examples", + "The flow contains packets with various flags: ": "The flow contains packets with various flags: ", "ICMP type provided but protocol is {{proto}}": "ICMP type provided but protocol is {{proto}}", "ICMP code provided but protocol is {{proto}}": "ICMP code provided but protocol is {{proto}}", "Invalid data provided. Check JSON for details.": "Invalid data provided. Check JSON for details.", diff --git a/web/src/components/drawer/record/record-field.tsx b/web/src/components/drawer/record/record-field.tsx index 73ba83f36..aadb3b473 100644 --- a/web/src/components/drawer/record/record-field.tsx +++ b/web/src/components/drawer/record/record-field.tsx @@ -21,11 +21,7 @@ import { import { dropCausesNames, getDropCauseDescription, getDropCauseDocUrl } from '../../../utils/pkt-drop'; import { formatPort } from '../../../utils/port'; import { formatProtocol, getProtocolDocUrl } from '../../../utils/protocol'; -import { - getTCPFlagsDocUrl, - gettcpFlagsServiceClassDescription, - gettcpFlagsServiceClassName -} from '../../../utils/tcp_flags'; +import { decomposeTCPFlagsBitfield, getTCPFlagsDocUrl } from '../../../utils/tcp-flags'; import { Size } from '../../dropdowns/table-display-dropdown'; import './record-field.css'; @@ -478,15 +474,21 @@ export const RecordField: React.FC = ({ case ColumnsId.tcpflags: { let child = emptyText(); if (typeof value === 'number' && !isNaN(value)) { - const serviceClassName = gettcpFlagsServiceClassName(value); - if (serviceClassName && detailed) { - child = clickableContent( - serviceClassName, - `${t('Value')}: ${value} ${t('Examples')}: ${gettcpFlagsServiceClassDescription(value)}`, - getTCPFlagsDocUrl() - ); + const flags = decomposeTCPFlagsBitfield(value); + const name = flags.length > 0 ? flags.map(f => f.name).join(', ') : String(value); + if (detailed) { + let description = `${t('Value')}: ${value}`; + if (flags.length === 1) { + description += '. ' + flags[0].description; + } else if (flags.length > 1) { + description += + '. ' + + t('The flow contains packets with various flags: ') + + flags.map(f => f.name + ' (' + f.description + ')').join('; '); + } + child = clickableContent(name, description, getTCPFlagsDocUrl()); } else { - child = simpleTextWithTooltip(serviceClassName || String(value))!; + child = simpleTextWithTooltip(name)!; } } return singleContainer(child); diff --git a/web/src/utils/__tests__/tcp-flags.spec.ts b/web/src/utils/__tests__/tcp-flags.spec.ts new file mode 100644 index 000000000..f14054e55 --- /dev/null +++ b/web/src/utils/__tests__/tcp-flags.spec.ts @@ -0,0 +1,17 @@ +import { decomposeTCPFlagsBitfield } from '../tcp-flags'; + +describe('TCP flags', () => { + it('should decompose', () => { + const flags528 = decomposeTCPFlagsBitfield(528); + expect(flags528).toHaveLength(2); + expect(flags528.map(f => f.name)).toEqual(['ACK', 'FIN_ACK']); + + const flags256 = decomposeTCPFlagsBitfield(256); + expect(flags256).toHaveLength(1); + expect(flags256.map(f => f.name)).toEqual(['SYN_ACK']); + + const flags666 = decomposeTCPFlagsBitfield(666); + expect(flags666).toHaveLength(5); + expect(flags666.map(f => f.name)).toEqual(['SYN', 'PSH', 'ACK', 'CWR', 'FIN_ACK']); + }); +}); diff --git a/web/src/utils/filter-options.ts b/web/src/utils/filter-options.ts index 0baf28efc..0b74e3a1f 100644 --- a/web/src/utils/filter-options.ts +++ b/web/src/utils/filter-options.ts @@ -10,7 +10,7 @@ import { dnsErrors, dnsRCodes } from './dns'; import { DSCP_VALUES } from './dscp'; import { dropCauses, dropStates } from './pkt-drop'; import { getPort, getService } from './port'; -import { TCPFlags_VALUES } from './tcp_flags'; +import { tcpFlagsList } from './tcp-flags'; export const noOption: (value: string) => Promise = () => Promise.resolve([]); @@ -172,9 +172,9 @@ export const getDSCPOptions = (value: string): Promise => { export const getTCPFlagsOptions = (value: string): Promise => { return Promise.resolve( - TCPFlags_VALUES.filter( - opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase()) - ).map(v => ({ name: v.name, value: String(v.value) })) + tcpFlagsList + .filter(opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase())) + .map(v => ({ name: v.name, value: String(v.value) })) ); }; diff --git a/web/src/utils/tcp-flags.ts b/web/src/utils/tcp-flags.ts new file mode 100644 index 000000000..13e86901e --- /dev/null +++ b/web/src/utils/tcp-flags.ts @@ -0,0 +1,29 @@ +import { ReadOnlyValue, ReadOnlyValues } from './values'; + +export const getTCPFlagsDocUrl = () => { + return 'https://www.rfc-editor.org/rfc/rfc9293'; +}; + +export const tcpFlagsList: ReadOnlyValues = [ + { value: 1, name: 'FIN', description: 'No more data from sender' }, + { value: 2, name: 'SYN', description: 'Synchronize sequence numbers' }, + { value: 4, name: 'RST', description: 'Reset the connection' }, + { value: 8, name: 'PSH', description: 'Push function' }, + { value: 16, name: 'ACK', description: 'Acknowledgement field is significant' }, + { value: 32, name: 'URG', description: 'Urgent pointer field is significant' }, + { value: 64, name: 'ECE', description: 'ECN-Echo' }, + { value: 128, name: 'CWR', description: 'Congestion Window Reduced' }, + { value: 256, name: 'SYN_ACK', description: 'Acknowledgement of SYN (custom flag)' }, + { value: 512, name: 'FIN_ACK', description: 'Acknowledgement of FIN (custom flag)' }, + { value: 1024, name: 'RST_ACK', description: 'Acknowledgement of RST (custom flag)' } +] as const; + +export const decomposeTCPFlagsBitfield = (bitfield: number): ReadOnlyValues => { + const values: ReadOnlyValue[] = []; + tcpFlagsList.forEach(flag => { + if (bitfield & flag.value) { + values.push(flag); + } + }); + return values; +}; diff --git a/web/src/utils/tcp_flags.ts b/web/src/utils/tcp_flags.ts deleted file mode 100644 index 64b2d97a7..000000000 --- a/web/src/utils/tcp_flags.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ReadOnlyValues } from './values'; - -export const getTCPFlagsDocUrl = () => { - return 'https://www.rfc-editor.org/rfc/rfc9293'; -}; - -export const TCPFlags_VALUES: ReadOnlyValues = [ - { value: 1, name: 'FIN', description: 'No more data from sender' }, - { value: 2, name: 'SYN', description: 'Synchronize sequence numbers' }, - { value: 3, name: 'FIN_SYN', description: 'Custom flag indicating both FIN and SYN flags are set' }, - { value: 4, name: 'RST', description: 'Reset the connection' }, - { value: 5, name: 'FIN_RST', description: 'Custom flag indicating both FIN and RST flags are set' }, - { value: 6, name: 'SYN_RST', description: 'Custom flag indicating both SYN and RST flags are set' }, - { value: 7, name: 'FIN_SYN_RST', description: 'Custom flag indicating FIN, SYN and RST flags are set' }, - { value: 8, name: 'PSH', description: 'Push function' }, - { value: 16, name: 'ACK', description: 'Acknowledgement field is significant' }, - { value: 32, name: 'URG', description: 'Urgent pointer field is significant' }, - { value: 64, name: 'ECE', description: 'ECN-Echo' }, - { value: 128, name: 'CWR', description: 'Congestion Window Reduced' }, - { value: 256, name: 'SYN_ACK', description: 'Custom flag indicating both SYN and ACK flags are set' }, - { value: 512, name: 'FIN_ACK', description: 'Custom flag indicating both FIN and ACK flags are set' }, - { value: 1024, name: 'RST_ACK', description: 'Custom flag indicating both RST and ACK flags are set' } -] as const; - -const tcpFlagsNames = TCPFlags_VALUES.map(v => v.name); -export type TCPFLAGS_SERVICE_CLASS_NAMES = typeof tcpFlagsNames[number]; - -export const gettcpFlagsServiceClassName = (flags: number): TCPFLAGS_SERVICE_CLASS_NAMES | undefined => { - return TCPFlags_VALUES.find(v => v.value === flags)?.name; -}; - -const tcpFlagsDescriptions = TCPFlags_VALUES.map(v => v.description); -export type TCPFLAGS_SERVICE_CLASS_DESCRIPTIONS = typeof tcpFlagsDescriptions[number]; - -export const gettcpFlagsServiceClassDescription = (flags: number): TCPFLAGS_SERVICE_CLASS_DESCRIPTIONS | undefined => { - return TCPFlags_VALUES.find(v => v.value === flags)?.description; -};