From 0e07b54c20a9e34fb7d0a72d23bbe3c35cce43a8 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 1 May 2026 20:24:01 -0500 Subject: [PATCH 1/3] ICMPv4 and ICMPv6 --- app/components/ProtocolBadge.tsx | 7 ++-- app/forms/firewall-rules-common.tsx | 45 +++++++++++++------- app/table/cells/ProtocolCell.tsx | 22 +++++++--- app/util/protocol.ts | 53 ++++++++++++++++------- mock-api/vpc.ts | 6 ++- test/e2e/firewall-rules.e2e.ts | 65 +++++++++++++++++++++++------ 6 files changed, 149 insertions(+), 49 deletions(-) diff --git a/app/components/ProtocolBadge.tsx b/app/components/ProtocolBadge.tsx index 6ddbfa726a..199cadb996 100644 --- a/app/components/ProtocolBadge.tsx +++ b/app/components/ProtocolBadge.tsx @@ -19,14 +19,15 @@ export const ProtocolBadge = ({ protocol }: ProtocolBadgeProps) => { return {protocol.type.toUpperCase()} } + const label = protocol.type === 'icmp' ? 'ICMPv4' : 'ICMPv6' + if (protocol.value === null) { - // All ICMP types - return ICMP + return {label} } return (
- ICMP + {label} type {protocol.value.icmpType} diff --git a/app/forms/firewall-rules-common.tsx b/app/forms/firewall-rules-common.tsx index 56340d04f9..8a6f56a1f9 100644 --- a/app/forms/firewall-rules-common.tsx +++ b/app/forms/firewall-rules-common.tsx @@ -52,7 +52,12 @@ import { KEYS } from '~/ui/util/keys' import { ALL_ISH } from '~/util/consts' import { validateIp, validateIpNet } from '~/util/ip' import { links } from '~/util/links' -import { getProtocolDisplayName, getProtocolKey, ICMP_TYPES } from '~/util/protocol' +import { + getProtocolDisplayName, + getProtocolKey, + ICMPV4_TYPES, + ICMPV6_TYPES, +} from '~/util/protocol' import { capitalize, normalizeDashes } from '~/util/str' import { type FirewallRuleValues } from './firewall-rules-util' @@ -293,18 +298,22 @@ const protocolTypeItems: Array<{ value: VpcFirewallRuleProtocol['type']; label: [ { value: 'tcp', label: 'TCP' }, { value: 'udp', label: 'UDP' }, - { value: 'icmp', label: 'ICMP' }, + { value: 'icmp', label: 'ICMPv4' }, + { value: 'icmp6', label: 'ICMPv6' }, ] -const icmpTypeItems = [ +const buildIcmpTypeItems = (types: Record) => [ { value: '', label: 'All types', selectedLabel: 'All types' }, - ...Object.entries(ICMP_TYPES).map(([type, name]) => ({ + ...Object.entries(types).map(([type, name]) => ({ value: type, label: `${type} - ${name}`, selectedLabel: type, })), ] +const icmpV4TypeItems = buildIcmpTypeItems(ICMPV4_TYPES) +const icmpV6TypeItems = buildIcmpTypeItems(ICMPV6_TYPES) + const targetAndHostTableColumns = [ { header: 'Type', @@ -343,13 +352,15 @@ const isDuplicateProtocol = ( return existingProtocols.some((p) => p.type === newProtocol.type) } - if (newProtocol.type === 'icmp') { + if (newProtocol.type === 'icmp' || newProtocol.type === 'icmp6') { if (newProtocol.value === null) { - return existingProtocols.some((p) => p.type === 'icmp' && p.value === null) + return existingProtocols.some( + (p) => p.type === newProtocol.type && p.value === null + ) } return existingProtocols.some( (p) => - p.type === 'icmp' && + p.type === newProtocol.type && p.value?.icmpType === newProtocol.value?.icmpType && p.value?.code === newProtocol.value?.code ) @@ -423,7 +434,7 @@ const ProtocolFilters = ({ control }: { control: Control }) const submitProtocol = protocolForm.handleSubmit((values) => { if (values.protocolType === 'tcp' || values.protocolType === 'udp') { addProtocolIfUnique({ type: values.protocolType }) - } else if (values.protocolType === 'icmp') { + } else if (values.protocolType === 'icmp' || values.protocolType === 'icmp6') { // this parse should never fail because we've already validated, but doing // it this way keeps the just-in-case early return logic consistent const parseResult = parseIcmpType(values.icmpType) @@ -432,14 +443,14 @@ const ProtocolFilters = ({ control }: { control: Control }) const icmpType = parseResult.data if (icmpType === undefined) { // All ICMP types - addProtocolIfUnique({ type: 'icmp', value: null }) + addProtocolIfUnique({ type: values.protocolType, value: null }) } else { // Specific ICMP type const icmpValue: VpcFirewallIcmpFilter = { icmpType } if (values.icmpCode) { icmpValue.code = values.icmpCode } - addProtocolIfUnique({ type: 'icmp', value: icmpValue }) + addProtocolIfUnique({ type: values.protocolType, value: icmpValue }) } } protocolForm.reset() @@ -461,19 +472,25 @@ const ProtocolFilters = ({ control }: { control: Control }) control={protocolForm.control} placeholder="" items={protocolTypeItems} + // ICMPv4 and ICMPv6 type numbers mean different things, so clear the + // selected ICMP type/code when switching protocol + onChange={() => { + protocolForm.setValue('icmpType', '') + protocolForm.setValue('icmpCode', '') + }} /> - {selectedProtocolType === 'icmp' && ( + {(selectedProtocolType === 'icmp' || selectedProtocolType === 'icmp6') && ( <> protocolForm.setValue('icmpType', value)} - items={icmpTypeItems} + items={selectedProtocolType === 'icmp' ? icmpV4TypeItems : icmpV6TypeItems} validate={(value) => { const result = parseIcmpType(value) if (!result.success) return result.message @@ -482,7 +499,7 @@ const ProtocolFilters = ({ control }: { control: Control }) {selectedIcmpType !== undefined && selectedIcmpType !== '' && ( { + if (protocol.type === 'icmp') return 'ICMPv4' + if (protocol.type === 'icmp6') return 'ICMPv6' + return protocol.type.toUpperCase() +} + +const isIcmp = ( + protocol: VpcFirewallRuleProtocol +): protocol is Extract => + protocol.type === 'icmp' || protocol.type === 'icmp6' + export const ProtocolCell = ({ protocol }: { protocol: VpcFirewallRuleProtocol }) => ( - {protocol.type.toUpperCase()} + {protocolLabel(protocol)} ) /** Generate tooltip content for empty protocol cells in the mini table */ const protocolEmptyCellTooltipContent = (protocol: VpcFirewallRuleProtocol): string => { if (protocol.type === 'tcp') return 'This firewall rule will match all TCP traffic' if (protocol.type === 'udp') return 'This firewall rule will match all UDP traffic' + const label = protocolLabel(protocol) // in this case, the user could be looking at the type column or the code column, but both get the same tooltip if (protocol.value === null) { - return 'This firewall rule will match all ICMP traffic' + return `This firewall rule will match all ${label} traffic` } // in this case, there's an icmpType but no code, which means the user is looking at the code column - return `This firewall rule will match all ICMP traffic of type ${protocol.value.icmpType}` + return `This firewall rule will match all ${label} traffic of type ${protocol.value.icmpType}` } export const ProtocolEmptyCell = ({ protocol }: { protocol: VpcFirewallRuleProtocol }) => ( @@ -39,7 +51,7 @@ export const ProtocolEmptyCell = ({ protocol }: { protocol: VpcFirewallRuleProto export const ProtocolTypeCell = ({ protocol }: { protocol: VpcFirewallRuleProtocol }) => // icmpType could be zero, so we check for `not undefined` - protocol.type === 'icmp' && protocol.value?.icmpType !== undefined ? ( + isIcmp(protocol) && protocol.value?.icmpType !== undefined ? ( protocol.value.icmpType ) : ( @@ -47,7 +59,7 @@ export const ProtocolTypeCell = ({ protocol }: { protocol: VpcFirewallRuleProtoc export const ProtocolCodeCell = ({ protocol }: { protocol: VpcFirewallRuleProtocol }) => // code could be zero, so we check for `not undefined` - protocol.type === 'icmp' && protocol.value?.code !== undefined ? ( + isIcmp(protocol) && protocol.value?.code !== undefined ? ( protocol.value.code ) : ( diff --git a/app/util/protocol.ts b/app/util/protocol.ts index a41ec3e727..c3b1840464 100644 --- a/app/util/protocol.ts +++ b/app/util/protocol.ts @@ -8,7 +8,7 @@ import type { VpcFirewallRuleProtocol } from '~/api' -export const ICMP_TYPES: Record = { +export const ICMPV4_TYPES: Record = { 0: 'Echo Reply', 3: 'Destination Unreachable', 5: 'Redirect Message', @@ -21,26 +21,51 @@ export const ICMP_TYPES: Record = { 14: 'Timestamp Reply', } +// ICMPv6 type assignments per RFC 4443 and related RFCs. +export const ICMPV6_TYPES: Record = { + 1: 'Destination Unreachable', + 2: 'Packet Too Big', + 3: 'Time Exceeded', + 4: 'Parameter Problem', + 128: 'Echo Request', + 129: 'Echo Reply', + 133: 'Router Solicitation', + 134: 'Router Advertisement', + 135: 'Neighbor Solicitation', + 136: 'Neighbor Advertisement', + 137: 'Redirect Message', +} + +export type IcmpVariant = 'icmp' | 'icmp6' + +const typesFor = (variant: IcmpVariant) => + variant === 'icmp' ? ICMPV4_TYPES : ICMPV6_TYPES + +const labelFor = (variant: IcmpVariant) => (variant === 'icmp' ? 'ICMPv4' : 'ICMPv6') + /** * Get the human-readable name for an ICMP type */ -export const getIcmpTypeName = (type: number): string | undefined => ICMP_TYPES[type] +export const getIcmpTypeName = ( + variant: IcmpVariant, + type: number +): string | undefined => typesFor(variant)[type] /** * Get a display name for a protocol, including ICMP types and codes */ export const getProtocolDisplayName = (protocol: VpcFirewallRuleProtocol): string => { - if (protocol.type === 'icmp') { - if (protocol.value === null) { - return 'ICMP (All types)' - } else { - const typeName = - ICMP_TYPES[protocol.value.icmpType] || `Type ${protocol.value.icmpType}` - const codePart = protocol.value.code ? ` | Code ${protocol.value.code}` : '' - return `ICMP ${protocol.value.icmpType} - ${typeName}${codePart}` - } + if (protocol.type === 'tcp' || protocol.type === 'udp') { + return protocol.type.toUpperCase() + } + const label = labelFor(protocol.type) + if (protocol.value === null) { + return `${label} (All types)` } - return protocol.type.toUpperCase() + const typeName = + typesFor(protocol.type)[protocol.value.icmpType] || `Type ${protocol.value.icmpType}` + const codePart = protocol.value.code ? ` | Code ${protocol.value.code}` : '' + return `${label} ${protocol.value.icmpType} - ${typeName}${codePart}` } /** @@ -52,6 +77,6 @@ export const getProtocolKey = (protocol: VpcFirewallRuleProtocol): string => { return protocol.type } return protocol.value === null - ? 'icmp|all' - : `icmp|${protocol.value.icmpType}|${protocol.value.code || 'all'}` + ? `${protocol.type}|all` + : `${protocol.type}|${protocol.value.icmpType}|${protocol.value.code || 'all'}` } diff --git a/mock-api/vpc.ts b/mock-api/vpc.ts index 80861df500..68a3fc65f1 100644 --- a/mock-api/vpc.ts +++ b/mock-api/vpc.ts @@ -321,7 +321,11 @@ export const firewallRules: Json = [ description: 'we just want to test with lots of filters', filters: { ports: ['3389', '45-89'], - protocols: [{ type: 'tcp' }, { type: 'icmp', value: { icmp_type: 5, code: '1-3' } }], + protocols: [ + { type: 'tcp' }, + { type: 'icmp', value: { icmp_type: 5, code: '1-3' } }, + { type: 'icmp6', value: { icmp_type: 128 } }, + ], hosts: [ { type: 'instance', value: 'hello-friend' }, { type: 'subnet', value: 'my-subnet' }, diff --git a/test/e2e/firewall-rules.e2e.ts b/test/e2e/firewall-rules.e2e.ts index c073940efe..052dbf6049 100644 --- a/test/e2e/firewall-rules.e2e.ts +++ b/test/e2e/firewall-rules.e2e.ts @@ -141,15 +141,15 @@ test('firewall rule targets and filters overflow', async ({ page }) => { ).toBeVisible() await expect( - page.getByRole('cell', { name: 'instance hello-friend +6', exact: true }) + page.getByRole('cell', { name: 'instance hello-friend +7', exact: true }) ).toBeVisible() // scroll table sideways past the filters cell await page.getByText('Enabled').first().scrollIntoViewIfNeeded() - await page.getByText('+6').hover() + await page.getByText('+7').hover() const tooltip = page.getByRole('tooltip', { - name: 'Other filters subnet my-subnet ip 148.38.89.5 TCP ICMP type 5 code 1-3 Port 3389 Port 45-89', + name: 'Other filters subnet my-subnet ip 148.38.89.5 TCP ICMPv4 type 5 code 1-3 ICMPv6 type 128 Port 3389 Port 45-89', exact: true, }) await expect(tooltip).toBeVisible() @@ -434,16 +434,16 @@ test('can update firewall rule', async ({ page }) => { // protocol is populated in the table const protocolTable = page.getByRole('table', { name: 'Protocol filters' }) - await expect(protocolTable.getByText('ICMP')).toBeVisible() + await expect(protocolTable.getByText('ICMPv4')).toBeVisible() // remove the existing ICMP protocol filter await protocolTable.getByRole('button', { name: 'remove' }).click() // add a new ICMP protocol filter with type 3 and code 0 - await selectOption(page, 'Protocol filters', 'ICMP') - const icmpTypeField = page.getByRole('combobox', { name: 'ICMP Type' }) + await selectOption(page, 'Protocol filters', 'ICMPv4') + const icmpTypeField = page.getByRole('combobox', { name: 'ICMPv4 type' }) await fillAndSelect(icmpTypeField, page, '3', '3 - Destination Unreachable') - await page.getByRole('textbox', { name: 'ICMP Code' }).fill('0') + await page.getByRole('textbox', { name: 'ICMPv4 code' }).fill('0') await page.getByRole('button', { name: 'Add protocol' }).click() // update name @@ -477,7 +477,7 @@ test('can update firewall rule', async ({ page }) => { await expect(rows).toHaveCount(3) // new host filter shows up in filters cell, along with the new ICMP protocol - await expect(page.locator('text=subnetedit-filter-subnetICMP')).toBeVisible() + await expect(page.locator('text=subnetedit-filter-subnetICMPv4')).toBeVisible() // scroll table sideways past the filters cell to see the full content await page.getByText('Enabled').first().scrollIntoViewIfNeeded() @@ -509,7 +509,7 @@ test('create from existing rule', async ({ page }) => { // protocol is populated in the table const protocolTable = modal.getByRole('table', { name: 'Protocol filters' }) - await expect(protocolTable.getByText('ICMP')).toBeVisible() + await expect(protocolTable.getByText('ICMPv4')).toBeVisible() await expect(protocolTable.getByText('TCP')).toBeHidden() await expect(protocolTable.getByText('UDP')).toBeHidden() @@ -537,7 +537,7 @@ test('create from existing rule', async ({ page }) => { // protocol is populated in the table await expect(protocolTable.getByText('TCP')).toBeVisible() await expect(protocolTable.getByText('UDP')).toBeHidden() - await expect(protocolTable.getByText('ICMP')).toBeHidden() + await expect(protocolTable.getByText('ICMPv4')).toBeHidden() await expect(targets.getByRole('row', { name: 'vpc default' })).toBeVisible() }) @@ -657,8 +657,8 @@ test('arbitrary values combobox', async ({ page }) => { // triggering a validation error for bad input. without onInputChange binding // the input value to the form value, this does not trigger an error because // the form thinks the input is empyt. - await selectOption(page, 'Protocol filters', 'ICMP') - await page.getByRole('combobox', { name: 'ICMP type' }).pressSequentially('abc') + await selectOption(page, 'Protocol filters', 'ICMPv4') + await page.getByRole('combobox', { name: 'ICMPv4 type' }).pressSequentially('abc') const error = page .getByRole('dialog') .getByText('ICMP type must be a number between 0 and 255') @@ -667,6 +667,47 @@ test('arbitrary values combobox', async ({ page }) => { await expect(error).toBeVisible() }) +test('can add ICMPv4 and ICMPv6 protocol filters', async ({ page }) => { + await page.goto('/projects/mock-project/vpcs/mock-vpc/firewall-rules-new') + + const protocolTable = page.getByRole('table', { name: 'Protocol filters' }) + await expect(protocolTable).toBeHidden() + + // add an ICMPv4 filter with a specific type + const protocolListbox = page.getByRole('button', { name: 'Protocol filters' }) + await protocolListbox.click() + await page.getByRole('option', { name: 'ICMPv4', exact: true }).click() + await fillAndSelect( + page.getByRole('combobox', { name: 'ICMPv4 type' }), + page, + '8', + '8 - Echo Request' + ) + await page.getByRole('button', { name: 'Add protocol filter' }).click() + await expectRowVisible(protocolTable, { Protocol: 'ICMPv4', Type: '8' }) + + // add an ICMPv6 filter with a different type number; v4 type 8 is Echo + // Request, v6 type 128 is Echo Request — different numbers, same intent + await protocolListbox.click() + await page.getByRole('option', { name: 'ICMPv6', exact: true }).click() + await fillAndSelect( + page.getByRole('combobox', { name: 'ICMPv6 type' }), + page, + '128', + '128 - Echo Request' + ) + await page.getByRole('button', { name: 'Add protocol filter' }).click() + await expectRowVisible(protocolTable, { Protocol: 'ICMPv6', Type: '128' }) + + // both rows are present + await expect(protocolTable.getByRole('row')).toHaveCount(3) // header + 2 + + // switching protocol type clears the previously selected ICMP type + await protocolListbox.click() + await page.getByRole('option', { name: 'ICMPv4', exact: true }).click() + await expect(page.getByRole('combobox', { name: 'ICMPv4 type' })).toHaveValue('') +}) + // Regression test: when typing a partial match in an arbitrary-values combobox, // arrowing to a dropdown option, and pressing Enter, the selected option's value // should end up in the field — not the raw typed query. From 3b75326ad5751db6b4edc511e779aa35abf4647a Mon Sep 17 00:00:00 2001 From: David Crespo Date: Sat, 2 May 2026 18:04:03 -0500 Subject: [PATCH 2/3] use ts-pattern because we love ts-pattern --- app/components/ProtocolBadge.tsx | 3 ++- app/forms/firewall-rules-common.tsx | 15 +++++++++------ app/table/cells/ProtocolCell.tsx | 14 +++++++++----- app/util/protocol.ts | 21 ++++++++++++++------- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/app/components/ProtocolBadge.tsx b/app/components/ProtocolBadge.tsx index 199cadb996..9667b99d1e 100644 --- a/app/components/ProtocolBadge.tsx +++ b/app/components/ProtocolBadge.tsx @@ -9,6 +9,7 @@ import { Badge } from '@oxide/design-system/ui' import type { VpcFirewallRuleProtocol } from '~/api' +import { getIcmpLabel } from '~/util/protocol' type ProtocolBadgeProps = { protocol: VpcFirewallRuleProtocol @@ -19,7 +20,7 @@ export const ProtocolBadge = ({ protocol }: ProtocolBadgeProps) => { return {protocol.type.toUpperCase()} } - const label = protocol.type === 'icmp' ? 'ICMPv4' : 'ICMPv6' + const label = getIcmpLabel(protocol.type) if (protocol.value === null) { return {label} diff --git a/app/forms/firewall-rules-common.tsx b/app/forms/firewall-rules-common.tsx index 8a6f56a1f9..f761fc2212 100644 --- a/app/forms/firewall-rules-common.tsx +++ b/app/forms/firewall-rules-common.tsx @@ -8,6 +8,7 @@ import { useEffect, type ReactNode } from 'react' import { useController, useForm, type Control } from 'react-hook-form' +import { match } from 'ts-pattern' import { Badge } from '@oxide/design-system/ui' @@ -53,6 +54,7 @@ import { ALL_ISH } from '~/util/consts' import { validateIp, validateIpNet } from '~/util/ip' import { links } from '~/util/links' import { + getIcmpLabel, getProtocolDisplayName, getProtocolKey, ICMPV4_TYPES, @@ -354,9 +356,7 @@ const isDuplicateProtocol = ( if (newProtocol.type === 'icmp' || newProtocol.type === 'icmp6') { if (newProtocol.value === null) { - return existingProtocols.some( - (p) => p.type === newProtocol.type && p.value === null - ) + return existingProtocols.some((p) => p.type === newProtocol.type && p.value === null) } return existingProtocols.some( (p) => @@ -483,14 +483,17 @@ const ProtocolFilters = ({ control }: { control: Control }) {(selectedProtocolType === 'icmp' || selectedProtocolType === 'icmp6') && ( <> protocolForm.setValue('icmpType', value)} - items={selectedProtocolType === 'icmp' ? icmpV4TypeItems : icmpV6TypeItems} + items={match(selectedProtocolType) + .with('icmp', () => icmpV4TypeItems) + .with('icmp6', () => icmpV6TypeItems) + .exhaustive()} validate={(value) => { const result = parseIcmpType(value) if (!result.success) return result.message @@ -499,7 +502,7 @@ const ProtocolFilters = ({ control }: { control: Control }) {selectedIcmpType !== undefined && selectedIcmpType !== '' && ( { - if (protocol.type === 'icmp') return 'ICMPv4' - if (protocol.type === 'icmp6') return 'ICMPv6' - return protocol.type.toUpperCase() -} +const protocolLabel = (protocol: VpcFirewallRuleProtocol) => + match(protocol.type) + .with('tcp', () => 'TCP') + .with('udp', () => 'UDP') + .with('icmp', () => 'ICMPv4') + .with('icmp6', () => 'ICMPv6') + .exhaustive() const isIcmp = ( protocol: VpcFirewallRuleProtocol diff --git a/app/util/protocol.ts b/app/util/protocol.ts index c3b1840464..eb59a8529e 100644 --- a/app/util/protocol.ts +++ b/app/util/protocol.ts @@ -6,6 +6,8 @@ * Copyright Oxide Computer Company */ +import { match } from 'ts-pattern' + import type { VpcFirewallRuleProtocol } from '~/api' export const ICMPV4_TYPES: Record = { @@ -39,17 +41,22 @@ export const ICMPV6_TYPES: Record = { export type IcmpVariant = 'icmp' | 'icmp6' const typesFor = (variant: IcmpVariant) => - variant === 'icmp' ? ICMPV4_TYPES : ICMPV6_TYPES + match(variant) + .with('icmp', () => ICMPV4_TYPES) + .with('icmp6', () => ICMPV6_TYPES) + .exhaustive() -const labelFor = (variant: IcmpVariant) => (variant === 'icmp' ? 'ICMPv4' : 'ICMPv6') +export const getIcmpLabel = (variant: IcmpVariant) => + match(variant) + .with('icmp', () => 'ICMPv4' as const) + .with('icmp6', () => 'ICMPv6' as const) + .exhaustive() /** * Get the human-readable name for an ICMP type */ -export const getIcmpTypeName = ( - variant: IcmpVariant, - type: number -): string | undefined => typesFor(variant)[type] +export const getIcmpTypeName = (variant: IcmpVariant, type: number): string | undefined => + typesFor(variant)[type] /** * Get a display name for a protocol, including ICMP types and codes @@ -58,7 +65,7 @@ export const getProtocolDisplayName = (protocol: VpcFirewallRuleProtocol): strin if (protocol.type === 'tcp' || protocol.type === 'udp') { return protocol.type.toUpperCase() } - const label = labelFor(protocol.type) + const label = getIcmpLabel(protocol.type) if (protocol.value === null) { return `${label} (All types)` } From 443bea05d93384e62f2607f7a1484db96878a71d Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 4 May 2026 18:50:10 -0500 Subject: [PATCH 3/3] check ICMP type list and add citations links in code --- app/util/protocol.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/util/protocol.ts b/app/util/protocol.ts index eb59a8529e..da479b0a13 100644 --- a/app/util/protocol.ts +++ b/app/util/protocol.ts @@ -10,6 +10,8 @@ import { match } from 'ts-pattern' import type { VpcFirewallRuleProtocol } from '~/api' +// IANA ICMP Type Numbers registry: +// https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-types export const ICMPV4_TYPES: Record = { 0: 'Echo Reply', 3: 'Destination Unreachable', @@ -23,7 +25,8 @@ export const ICMPV4_TYPES: Record = { 14: 'Timestamp Reply', } -// ICMPv6 type assignments per RFC 4443 and related RFCs. +// IANA ICMPv6 Type Numbers registry: +// https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-2 export const ICMPV6_TYPES: Record = { 1: 'Destination Unreachable', 2: 'Packet Too Big', @@ -31,6 +34,9 @@ export const ICMPV6_TYPES: Record = { 4: 'Parameter Problem', 128: 'Echo Request', 129: 'Echo Reply', + 130: 'Multicast Listener Query', + 131: 'Multicast Listener Report', + 132: 'Multicast Listener Done', 133: 'Router Solicitation', 134: 'Router Advertisement', 135: 'Neighbor Solicitation',