From 866485e9e46cdc3f5d1332db75d15d2cb98ffd2e Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Tue, 5 Nov 2024 17:16:34 +0100 Subject: [PATCH 1/4] NETOBSERV-1890: read TCP flags as strings --- pkg/model/fields/fields.go | 3 +- web/src/api/ipfix.ts | 2 +- .../components/drawer/record/record-field.tsx | 32 ++++++++-------- web/src/utils/__tests__/tcp-flags.spec.ts | 17 --------- web/src/utils/filter-options.ts | 4 +- web/src/utils/tcp-flags.ts | 37 ++++++++----------- 6 files changed, 34 insertions(+), 61 deletions(-) delete mode 100644 web/src/utils/__tests__/tcp-flags.spec.ts diff --git a/pkg/model/fields/fields.go b/pkg/model/fields/fields.go index 46576652b..297b7032c 100644 --- a/pkg/model/fields/fields.go +++ b/pkg/model/fields/fields.go @@ -69,8 +69,7 @@ func IsNumeric(v string) bool { Packets, Proto, Bytes, - DSCP, - TCPFlags: + DSCP: return true default: return false diff --git a/web/src/api/ipfix.ts b/web/src/api/ipfix.ts index b34f668ff..747fbaa55 100644 --- a/web/src/api/ipfix.ts +++ b/web/src/api/ipfix.ts @@ -118,7 +118,7 @@ export interface Fields { /** 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?: number; + Flags?: string; /** Number of packets */ Packets?: number; /** In conversation tracking, A to B packets counter per conversation */ diff --git a/web/src/components/drawer/record/record-field.tsx b/web/src/components/drawer/record/record-field.tsx index aadb3b473..23e4f7eaf 100644 --- a/web/src/components/drawer/record/record-field.tsx +++ b/web/src/components/drawer/record/record-field.tsx @@ -21,7 +21,7 @@ import { import { dropCausesNames, getDropCauseDescription, getDropCauseDocUrl } from '../../../utils/pkt-drop'; import { formatPort } from '../../../utils/port'; import { formatProtocol, getProtocolDocUrl } from '../../../utils/protocol'; -import { decomposeTCPFlagsBitfield, getTCPFlagsDocUrl } from '../../../utils/tcp-flags'; +import { getFlagsList, getTCPFlagsDocUrl } from '../../../utils/tcp-flags'; import { Size } from '../../dropdowns/table-display-dropdown'; import './record-field.css'; @@ -473,23 +473,21 @@ export const RecordField: React.FC = ({ } case ColumnsId.tcpflags: { let child = emptyText(); - if (typeof value === 'number' && !isNaN(value)) { - 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(name)!; + const sVal = String(value); + const flags = getFlagsList(sVal); + 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(sVal, description, getTCPFlagsDocUrl()); + } else { + child = simpleTextWithTooltip(sVal)!; } return singleContainer(child); } diff --git a/web/src/utils/__tests__/tcp-flags.spec.ts b/web/src/utils/__tests__/tcp-flags.spec.ts deleted file mode 100644 index f14054e55..000000000 --- a/web/src/utils/__tests__/tcp-flags.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -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 9dc15601c..7f245f1a3 100644 --- a/web/src/utils/filter-options.ts +++ b/web/src/utils/filter-options.ts @@ -174,8 +174,8 @@ export const getDSCPOptions = (value: string): Promise => { export const getTCPFlagsOptions = (value: string): Promise => { return Promise.resolve( tcpFlagsList - .filter(opt => String(opt.value).includes(value) || opt.name.toLowerCase().includes(value.toLowerCase())) - .map(v => ({ name: v.name, value: String(v.value) })) + .filter(opt => opt.name.toLowerCase().includes(value.toLowerCase())) + .map(v => ({ name: v.name, value: v.name })) ); }; diff --git a/web/src/utils/tcp-flags.ts b/web/src/utils/tcp-flags.ts index 13e86901e..9771f4f14 100644 --- a/web/src/utils/tcp-flags.ts +++ b/web/src/utils/tcp-flags.ts @@ -1,29 +1,22 @@ -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)' } +export const tcpFlagsList = [ + { name: 'FIN', description: 'No more data from sender' }, + { name: 'SYN', description: 'Synchronize sequence numbers' }, + { name: 'RST', description: 'Reset the connection' }, + { name: 'PSH', description: 'Push function' }, + { name: 'ACK', description: 'Acknowledgement field is significant' }, + { name: 'URG', description: 'Urgent pointer field is significant' }, + { name: 'ECE', description: 'ECN-Echo' }, + { name: 'CWR', description: 'Congestion Window Reduced' }, + { name: 'SYN_ACK', description: 'Acknowledgement of SYN (custom flag)' }, + { name: 'FIN_ACK', description: 'Acknowledgement of FIN (custom flag)' }, + { 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; +export const getFlagsList = (joined: string): { name: string, description: string}[] => { + const names = joined.split(','); + return tcpFlagsList.filter(f => names.includes(f.name)); }; From 9407e7d543fca78b3d2b48573fe8cc26ac67cd28 Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Thu, 14 Nov 2024 11:05:03 +0100 Subject: [PATCH 2/4] Use flags list --- web/src/api/ipfix.ts | 2 +- .../components/drawer/record/record-field.tsx | 30 ++++++++++--------- web/src/utils/tcp-flags.ts | 3 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/web/src/api/ipfix.ts b/web/src/api/ipfix.ts index 747fbaa55..2072d946b 100644 --- a/web/src/api/ipfix.ts +++ b/web/src/api/ipfix.ts @@ -118,7 +118,7 @@ export interface Fields { /** 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; + Flags?: string[]; /** Number of packets */ Packets?: number; /** In conversation tracking, A to B packets counter per conversation */ diff --git a/web/src/components/drawer/record/record-field.tsx b/web/src/components/drawer/record/record-field.tsx index 23e4f7eaf..947494e12 100644 --- a/web/src/components/drawer/record/record-field.tsx +++ b/web/src/components/drawer/record/record-field.tsx @@ -473,21 +473,23 @@ export const RecordField: React.FC = ({ } case ColumnsId.tcpflags: { let child = emptyText(); - const sVal = String(value); - const flags = getFlagsList(sVal); - 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('; '); + if (Array.isArray(value) && value.length > 0) { + const flags = getFlagsList(value as string[]); + const joined = value.join(', '); + 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(joined, description, getTCPFlagsDocUrl()); + } else { + child = simpleTextWithTooltip(joined)!; } - child = clickableContent(sVal, description, getTCPFlagsDocUrl()); - } else { - child = simpleTextWithTooltip(sVal)!; } return singleContainer(child); } diff --git a/web/src/utils/tcp-flags.ts b/web/src/utils/tcp-flags.ts index 9771f4f14..b04a4ef9d 100644 --- a/web/src/utils/tcp-flags.ts +++ b/web/src/utils/tcp-flags.ts @@ -16,7 +16,6 @@ export const tcpFlagsList = [ { name: 'RST_ACK', description: 'Acknowledgement of RST (custom flag)' } ] as const; -export const getFlagsList = (joined: string): { name: string, description: string}[] => { - const names = joined.split(','); +export const getFlagsList = (names: string[]): { name: string, description: string}[] => { return tcpFlagsList.filter(f => names.includes(f.name)); }; From 8122612c97e03aba2e5a4076a0fe807ff128e506 Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Thu, 14 Nov 2024 11:10:47 +0100 Subject: [PATCH 3/4] Add TCPFlags to list of arrays --- pkg/model/fields/fields.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/model/fields/fields.go b/pkg/model/fields/fields.go index 297b7032c..ba2ebdd87 100644 --- a/pkg/model/fields/fields.go +++ b/pkg/model/fields/fields.go @@ -94,7 +94,8 @@ func IsArray(v string) bool { case IfDirections, Interfaces, - NetworkEvents: + NetworkEvents, + TCPFlags: return true default: return false From ee5449698f2eec3fb79e65b6dc9614f3f9a4250e Mon Sep 17 00:00:00 2001 From: Joel Takvorian Date: Thu, 14 Nov 2024 11:15:35 +0100 Subject: [PATCH 4/4] fix lint --- web/src/utils/tcp-flags.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/utils/tcp-flags.ts b/web/src/utils/tcp-flags.ts index b04a4ef9d..66c410f57 100644 --- a/web/src/utils/tcp-flags.ts +++ b/web/src/utils/tcp-flags.ts @@ -16,6 +16,6 @@ export const tcpFlagsList = [ { name: 'RST_ACK', description: 'Acknowledgement of RST (custom flag)' } ] as const; -export const getFlagsList = (names: string[]): { name: string, description: string}[] => { +export const getFlagsList = (names: string[]): { name: string; description: string }[] => { return tcpFlagsList.filter(f => names.includes(f.name)); };