Skip to content

Conversation

@david-crespo
Copy link
Collaborator

@david-crespo david-crespo commented Sep 20, 2024

Followup work for #2437 (comment)

  • Basic parsing-based validation with ip-num
  • Confirm Rust code has the same validation results (https://github.com/oxidecomputer/test-ip-validation)
  • Abandon ip-num because it does not have the same validation results
  • Decide on return type of parser function
  • Figure out how this should be integrated into forms
  • Validate transit IP nets
  • Validate IPs and IP nets in route form
  • Validate IPs and IP nets in firewall rule form
  • Polish up weird behavior on host and target subform on type change, submit success, and clear
  • Write some e2e tests

@vercel
Copy link

vercel bot commented Sep 20, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
console ✅ Ready (Inspect) Visit Preview Oct 1, 2024 7:44pm

@david-crespo
Copy link
Collaborator Author

Ok, here's something interesting. I tried swapping in the ip-num things in our existing validateIp function and a bunch of tests fail, indicating it disagrees significantly with the Rust parsing code.

 export function validateIp(ip: string) {
-  const isv4 = IPV4_REGEX.test(ip)
-  const isv6 = !isv4 && IPV6_REGEX.test(ip)
+  let isv4 = false
+  try {
+    IPv4.fromString(ip)
+    isv4 = true
+  } catch (e) {}
+  let isv6 = false
+  if (!isv4) {
+    try {
+      IPv6.fromString(ip)
+      isv6 = true
+    } catch (e) {}
+  }
   return { isv4, isv6, valid: isv4 || isv6 }
 }
image

I didn't notice these differences before because my list of examples for IP networks was much less thorough than my kickass list for plain addresses. When I add the offending address to the list of IP nets, ip-num considers it valid, as expected.

image

Rust oxnet's Ipv4Net considers it invalid, which is expected because it splits the thing on / and uses the underlying Ipv4Addr to parse the inner IP address (source).

image

So, after all that excitement, I am going to take ip-num back out and use our existing IP address thing to parse an IP network. Not sure why I didn't start with that.

@david-crespo
Copy link
Collaborator Author

david-crespo commented Sep 24, 2024

truly, we are jamming.

2024-09-24-validate-ips-and-nets.mp4

form.setValue('transitIps', [...transitIps, transitIp])
transitIpsForm.reset()
}
})
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without wrapping this in handleSubmit, it doesn't run the validate function on submit. This way the inner function never gets called unless validation passes.

// back to validating on submit instead of change
onTypeChange={() =>
hostForm.reset({ type: hostForm.getValues('type'), value: '' })
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of hate this, but it does pretty elegantly express what I want to happen here, namely that we are basically starting over from scratch whenever we change the type. Very weird combination of feeling both wrong and exactly right.

@david-crespo
Copy link
Collaborator Author

Still need to add another test or two and fix the clear button bug in the firewall rules inputs, but this is mostly ready to review. It's crazy long but most of the length is tests and fresh additions.

@david-crespo david-crespo changed the title Validation utilities for IP networks Validate IPs and IP networks Oct 1, 2024
Copy link
Collaborator Author

@david-crespo david-crespo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some explanations.

name: TName
control: Control<TFieldValues>
onChange?: (value: string | null | undefined) => void
validate?: Validate<FieldPathValue<TFieldValues, TName>, TFieldValues>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this lets us validate names in arbitrary-value comboboxes.


test.each(invalid)('parseIp catches invalid IP: %s', (s) => {
expect(parseIp(s)).toStrictEqual({ type: 'error', message: 'Not a valid IP address' })
})
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From here up, the tests are basically unchanged, I just had to extract the lists of IPs so I could reuse them in the IP net tests.

/^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}$/u

const IPV6_REGEX =
/^(?:(?:[\da-f]{1,4}:){7}[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,7}:|(?:[\da-f]{1,4}:){1,6}:[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,5}(?::[\da-f]{1,4}){1,2}|(?:[\da-f]{1,4}:){1,4}(?::[\da-f]{1,4}){1,3}|(?:[\da-f]{1,4}:){1,3}(?::[\da-f]{1,4}){1,4}|(?:[\da-f]{1,4}:){1,2}(?::[\da-f]{1,4}){1,5}|[\da-f]{1,4}:(?::[\da-f]{1,4}){1,6}|:(?:(?::[\da-f]{1,4}){1,7}|:)|fe80:(?::[\da-f]{0,4}){0,4}%[\da-z]+|::(?:f{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-f]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/iu
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regexes are unchanged, the point was to distinguish between parse and validate and to add the IP net things

validate={(value) =>
// TODO: is required false correct here? should this function even have that argument?
validateName(value, `${capitalize(sectionType)} name`, false)
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think required: false is correct because we don't want this to show a required error when it's left empty after a bad input was attempted.

if (targets.value.some((t) => t.value === value && t.type === type)) return
targets.onChange([...targets.value, { type, value }])
targetForm.reset()
targetForm.reset(targetAndHostDefaultValues)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by useEffect below.

/>
<MiniTable.ClearAndAddButtons
addButtonCopy="Add host filter"
disableClear={!!hostValue}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WHOOPS

} else if (last.isv6) {
if (last.type === 'error') {
errors.last = { type: 'pattern', message: last.message }
} else if (last.type === 'v6') {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing actually changes in this file, so I didn't change the tests.

if (error) return error

if (transitIps.includes(value)) return 'Transit IP already in list'
}}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this here instead of in the handleSubmit means we get a beautiful validation error inline on attempts to add a dupe.

const targets = useController({ name: 'targets', control }).field
const targetType = targetForm.watch('type')
const targetValue = targetForm.watch('value')
const [targetType, targetValue] = targetForm.watch(['type', 'value'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡

Comment on lines +293 to +296
const { isSubmitSuccessful: targetSubmitSuccessful } = targetForm.formState
useEffect(() => {
if (targetSubmitSuccessful) targetForm.reset(targetAndHostDefaultValues)
}, [targetSubmitSuccessful, targetForm])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a clever workaround. A shame that we need to distinguish between the two flows, but premature validation errors are even worse. Nevertheless, this is clever.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking there is probably a way to wrap this up into some kind of subform helper hook.

Copy link
Contributor

@charliepark charliepark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@david-crespo david-crespo merged commit dec4849 into main Oct 1, 2024
@david-crespo david-crespo deleted the validate-ip-net branch October 1, 2024 21:13
david-crespo added a commit to oxidecomputer/omicron that referenced this pull request Oct 3, 2024
oxidecomputer/console@5561f28...073471c

* [073471c4](oxidecomputer/console@073471c4) mock api: disks are created straight into detached state
* [612ec90c](oxidecomputer/console@612ec90c) oxidecomputer/console#2485
* [614f1bb5](oxidecomputer/console@614f1bb5) oxidecomputer/console#2466
* [df2dc14c](oxidecomputer/console@df2dc14c) bump vite related deps for a weird vuln
* [a030b9e0](oxidecomputer/console@a030b9e0) oxidecomputer/console#2464
* [dec48497](oxidecomputer/console@dec48497) oxidecomputer/console#2461
* [e46216aa](oxidecomputer/console@e46216aa) oxidecomputer/console#2482
* [0d897efe](oxidecomputer/console@0d897efe) oxidecomputer/console#2479
* [f88790db](oxidecomputer/console@f88790db) oxidecomputer/console#2478
* [d634c8f0](oxidecomputer/console@d634c8f0) oxidecomputer/console#2477
* [eeaa14c3](oxidecomputer/console@eeaa14c3) oxidecomputer/console#2475
* [5ece6e18](oxidecomputer/console@5ece6e18) oxidecomputer/console#2467
* [4b699e01](oxidecomputer/console@4b699e01) oxidecomputer/console#2448
* [9c9dc149](oxidecomputer/console@9c9dc149) oxidecomputer/console#2465
* [1aa0fc9b](oxidecomputer/console@1aa0fc9b) oxidecomputer/console#2463
* [57db4054](oxidecomputer/console@57db4054) oxidecomputer/console#2462
* [da7fe328](oxidecomputer/console@da7fe328) oxidecomputer/console#2460
* [e0d52efd](oxidecomputer/console@e0d52efd) oxidecomputer/console#2437
* [1625d02a](oxidecomputer/console@1625d02a) oxidecomputer/console#2458
* [fd82458e](oxidecomputer/console@fd82458e) oxidecomputer/console#2457
* [7daaa337](oxidecomputer/console@7daaa337) oxidecomputer/console#2453
david-crespo added a commit to oxidecomputer/omicron that referenced this pull request Oct 3, 2024
oxidecomputer/console@5561f28...073471c

* [073471c4](oxidecomputer/console@073471c4)
mock api: disks are created straight into detached state
* [612ec90c](oxidecomputer/console@612ec90c)
oxidecomputer/console#2485
* [614f1bb5](oxidecomputer/console@614f1bb5)
oxidecomputer/console#2466
* [df2dc14c](oxidecomputer/console@df2dc14c)
bump vite related deps for a weird vuln
* [a030b9e0](oxidecomputer/console@a030b9e0)
oxidecomputer/console#2464
* [dec48497](oxidecomputer/console@dec48497)
oxidecomputer/console#2461
* [e46216aa](oxidecomputer/console@e46216aa)
oxidecomputer/console#2482
* [0d897efe](oxidecomputer/console@0d897efe)
oxidecomputer/console#2479
* [f88790db](oxidecomputer/console@f88790db)
oxidecomputer/console#2478
* [d634c8f0](oxidecomputer/console@d634c8f0)
oxidecomputer/console#2477
* [eeaa14c3](oxidecomputer/console@eeaa14c3)
oxidecomputer/console#2475
* [5ece6e18](oxidecomputer/console@5ece6e18)
oxidecomputer/console#2467
* [4b699e01](oxidecomputer/console@4b699e01)
oxidecomputer/console#2448
* [9c9dc149](oxidecomputer/console@9c9dc149)
oxidecomputer/console#2465
* [1aa0fc9b](oxidecomputer/console@1aa0fc9b)
oxidecomputer/console#2463
* [57db4054](oxidecomputer/console@57db4054)
oxidecomputer/console#2462
* [da7fe328](oxidecomputer/console@da7fe328)
oxidecomputer/console#2460
* [e0d52efd](oxidecomputer/console@e0d52efd)
oxidecomputer/console#2437
* [1625d02a](oxidecomputer/console@1625d02a)
oxidecomputer/console#2458
* [fd82458e](oxidecomputer/console@fd82458e)
oxidecomputer/console#2457
* [7daaa337](oxidecomputer/console@7daaa337)
oxidecomputer/console#2453
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants