@@ -52,7 +52,12 @@ import { KEYS } from '~/ui/util/keys'
5252import { ALL_ISH } from '~/util/consts'
5353import { validateIp , validateIpNet } from '~/util/ip'
5454import { links } from '~/util/links'
55- import { getProtocolDisplayName , getProtocolKey , ICMP_TYPES } from '~/util/protocol'
55+ import {
56+ getProtocolDisplayName ,
57+ getProtocolKey ,
58+ ICMP_TYPES ,
59+ PROTOCOL_LABELS ,
60+ } from '~/util/protocol'
5661import { capitalize , normalizeDashes } from '~/util/str'
5762
5863import { type FirewallRuleValues } from './firewall-rules-util'
@@ -289,22 +294,27 @@ const directionItems: Array<{ value: VpcFirewallRuleDirection; label: string }>
289294 { value : 'outbound' , label : 'Outbound' } ,
290295]
291296
292- const protocolTypeItems : Array < { value : VpcFirewallRuleProtocol [ 'type' ] ; label : string } > =
293- [
294- { value : 'tcp ' , label : 'TCP ' } ,
295- { value : 'udp ' , label : 'UDP ' } ,
296- { value : 'icmp ' , label : 'ICMP ' } ,
297- ]
297+ const protocolTypeItems = [
298+ { value : 'tcp' , label : 'TCP' } ,
299+ { value : 'udp ' , label : 'UDP ' } ,
300+ { value : 'icmp ' , label : 'ICMPv4 ' } ,
301+ { value : 'icmp6 ' , label : 'ICMPv6 ' } ,
302+ ] satisfies Array < { value : VpcFirewallRuleProtocol [ 'type' ] ; label : string } >
298303
299- const icmpTypeItems = [
304+ const buildIcmpTypeItems = ( types : Record < number , string > ) => [
300305 { value : '' , label : 'All types' , selectedLabel : 'All types' } ,
301- ...Object . entries ( ICMP_TYPES ) . map ( ( [ type , name ] ) => ( {
306+ ...Object . entries ( types ) . map ( ( [ type , name ] ) => ( {
302307 value : type ,
303308 label : `${ type } - ${ name } ` ,
304309 selectedLabel : type ,
305310 } ) ) ,
306311]
307312
313+ const icmpTypeItems = {
314+ icmp : buildIcmpTypeItems ( ICMP_TYPES . icmp ) ,
315+ icmp6 : buildIcmpTypeItems ( ICMP_TYPES . icmp6 ) ,
316+ }
317+
308318const targetAndHostTableColumns = [
309319 {
310320 header : 'Type' ,
@@ -343,13 +353,13 @@ const isDuplicateProtocol = (
343353 return existingProtocols . some ( ( p ) => p . type === newProtocol . type )
344354 }
345355
346- if ( newProtocol . type === 'icmp' ) {
356+ if ( newProtocol . type === 'icmp' || newProtocol . type === 'icmp6' ) {
347357 if ( newProtocol . value === null ) {
348- return existingProtocols . some ( ( p ) => p . type === 'icmp' && p . value === null )
358+ return existingProtocols . some ( ( p ) => p . type === newProtocol . type && p . value === null )
349359 }
350360 return existingProtocols . some (
351361 ( p ) =>
352- p . type === 'icmp' &&
362+ p . type === newProtocol . type &&
353363 p . value ?. icmpType === newProtocol . value ?. icmpType &&
354364 p . value ?. code === newProtocol . value ?. code
355365 )
@@ -361,9 +371,15 @@ const isDuplicateProtocol = (
361371type ParseResult < T > = { success : true ; data : T } | { success : false ; message : string }
362372
363373const parseIcmpType = ( value : string | undefined ) : ParseResult < number | undefined > => {
364- if ( value === undefined || value === '' ) return { success : true , data : undefined }
365- const parsed = parseInt ( value , 10 )
366- if ( isNaN ( parsed ) || parsed < 0 || parsed > 255 ) {
374+ const trimmedValue = value ?. trim ( )
375+ if ( trimmedValue === undefined || trimmedValue === '' ) {
376+ return { success : true , data : undefined }
377+ }
378+ if ( ! / ^ \d + $ / . test ( trimmedValue ) ) {
379+ return { success : false , message : `ICMP type must be a number between 0 and 255` }
380+ }
381+ const parsed = parseInt ( trimmedValue , 10 )
382+ if ( parsed < 0 || parsed > 255 ) {
367383 return { success : false , message : `ICMP type must be a number between 0 and 255` }
368384 }
369385 return { success : true , data : parsed }
@@ -423,7 +439,7 @@ const ProtocolFilters = ({ control }: { control: Control<FirewallRuleValues> })
423439 const submitProtocol = protocolForm . handleSubmit ( ( values ) => {
424440 if ( values . protocolType === 'tcp' || values . protocolType === 'udp' ) {
425441 addProtocolIfUnique ( { type : values . protocolType } )
426- } else if ( values . protocolType === 'icmp' ) {
442+ } else if ( values . protocolType === 'icmp' || values . protocolType === 'icmp6' ) {
427443 // this parse should never fail because we've already validated, but doing
428444 // it this way keeps the just-in-case early return logic consistent
429445 const parseResult = parseIcmpType ( values . icmpType )
@@ -432,14 +448,15 @@ const ProtocolFilters = ({ control }: { control: Control<FirewallRuleValues> })
432448 const icmpType = parseResult . data
433449 if ( icmpType === undefined ) {
434450 // All ICMP types
435- addProtocolIfUnique ( { type : 'icmp' , value : null } )
451+ addProtocolIfUnique ( { type : values . protocolType , value : null } )
436452 } else {
437453 // Specific ICMP type
438454 const icmpValue : VpcFirewallIcmpFilter = { icmpType }
439- if ( values . icmpCode ) {
440- icmpValue . code = values . icmpCode
455+ const icmpCode = values . icmpCode ?. trim ( )
456+ if ( icmpCode ) {
457+ icmpValue . code = icmpCode
441458 }
442- addProtocolIfUnique ( { type : 'icmp' , value : icmpValue } )
459+ addProtocolIfUnique ( { type : values . protocolType , value : icmpValue } )
443460 }
444461 }
445462 protocolForm . reset ( )
@@ -461,19 +478,28 @@ const ProtocolFilters = ({ control }: { control: Control<FirewallRuleValues> })
461478 control = { protocolForm . control }
462479 placeholder = ""
463480 items = { protocolTypeItems }
481+ // ICMPv4 and ICMPv6 type numbers mean different things, so clear the
482+ // selected ICMP type/code when switching protocol. Also clear errors:
483+ // setValue doesn't revalidate, so a stale type error would otherwise
484+ // linger on the now-empty field.
485+ onChange = { ( ) => {
486+ protocolForm . setValue ( 'icmpType' , '' )
487+ protocolForm . setValue ( 'icmpCode' , '' )
488+ protocolForm . clearErrors ( [ 'icmpType' , 'icmpCode' ] )
489+ } }
464490 />
465491
466- { selectedProtocolType === 'icmp' && (
492+ { ( selectedProtocolType === 'icmp' || selectedProtocolType === 'icmp6' ) && (
467493 < >
468494 < ComboboxField
469- label = "ICMP type"
495+ label = { ` ${ PROTOCOL_LABELS [ selectedProtocolType ] } type` }
470496 name = "icmpType"
471497 control = { protocolForm . control }
472498 description = "Leave blank to match any type"
473499 placeholder = ""
474500 allowArbitraryValues
475501 onInputChange = { ( value ) => protocolForm . setValue ( 'icmpType' , value ) }
476- items = { icmpTypeItems }
502+ items = { icmpTypeItems [ selectedProtocolType ] }
477503 validate = { ( value ) => {
478504 const result = parseIcmpType ( value )
479505 if ( ! result . success ) return result . message
@@ -482,7 +508,7 @@ const ProtocolFilters = ({ control }: { control: Control<FirewallRuleValues> })
482508
483509 { selectedIcmpType !== undefined && selectedIcmpType !== '' && (
484510 < TextField
485- label = "ICMP code"
511+ label = { ` ${ PROTOCOL_LABELS [ selectedProtocolType ] } code` }
486512 name = "icmpCode"
487513 control = { protocolForm . control }
488514 description = {
0 commit comments