Repository navigation
datapath-windows: execute set() for IPv6, SCTP, ARP, ND and MPLS - #53
Conversation
The kernel datapath silently no-op'd OVS_ACTION_ATTR_SET for the
OVS_KEY_ATTR_IPV6/SCTP/ARP/ND/MPLS key types: the action switch had no
case for them, so the packet egressed unmodified while the set still
reported NDIS_STATUS_SUCCESS. Any set(ipv6(...)) -- OVN logical-router
IPv6 next-hop rewrites, DSCP/flow-label/hop-limit changes -- was dropped
on the floor.
Add per-type inline rewrite helpers modelled on the existing IPv4/TCP/UDP
path. OvsUpdateIPv6Header reuses OvsUpdateAddressAndPortForIpv6 for the
128-bit address and L4/ICMPv6 checksum fixup, then writes traffic class,
flow label and hop limit. OvsUpdateSctpPorts recomputes the SCTP CRC32c
over the transport payload via a new table-driven Crc32c.{c,h} that is
safe at DISPATCH_LEVEL. OvsUpdateArpHeader, OvsUpdateNdHeader and
OvsUpdateMplsHeader rewrite their respective fields. A failed rewrite now
propagates instead of reporting false success.
There was a problem hiding this comment.
Pull request overview
This PR fixes a functional gap in the Windows kernel datapath where OVS_ACTION_ATTR_SET silently ignored several key types by adding explicit per-key rewrite implementations (IPv6, SCTP, ARP, ND, MPLS) and wiring them into the set-action switch, including a new CRC32c implementation needed for SCTP checksum updates.
Changes:
- Add CRC32c (Castagnoli) helper (
Crc32c.{c,h}) and include it in the Windows driver project. - Implement inline set-action rewrite helpers for IPv6 / SCTP / ARP / ND / MPLS and invoke them from
OvsExecuteSetAction(). - Update public action helper declarations in
Actions.h.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| datapath-windows/ovsext/ovsext.vcxproj | Adds the new CRC32c source/header to the Windows driver build. |
| datapath-windows/ovsext/Crc32c.h | Declares a DISPATCH_LEVEL-safe CRC32c API for SCTP. |
| datapath-windows/ovsext/Crc32c.c | Implements table-driven CRC32c used by SCTP port rewrite. |
| datapath-windows/ovsext/Actions.h | Exposes new per-key set-action rewrite helper prototypes. |
| datapath-windows/ovsext/Actions.c | Adds IPv6/SCTP/ARP/ND/MPLS set-action execution paths and helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address review of the set() execution paths: - set(mpls) wrote the LSE at l3Offset, but the parser advances l3Offset past the whole label stack, so it corrupted the inner L3 header instead of the label. Write at l3Offset - MPLS_HLEN (the label adjacent to L3, i.e. the topmost for the single-label stacks in scope) and reject a set(mpls) misapplied to a non-MPLS packet. - set(ipv6), set(nd) and set(arp) under-checked the L3 type. layers->isIcmp is set for IPv4 ICMP too, so require isIPv6 before reinterpreting the L3 header as IPv6 and writing the icmp6Key union; guard set(arp) on the ARP ethertype. - Clamp the ICMPv6 checksum span and the SCTP CRC32c span to the captured L4 bytes (the SCTP length derived from the IP header, not the frame), so IPv6 extension headers, a lying payload_len, or L2 frame padding cannot walk past the NBL. - Pull the full packet contiguous for the ICMPv6 set(ipv6) path so the shared checksum recompute stays within the contiguous buffer. - Align Crc32c.c to the Apache license header; clarify the byte-order and isTx comments.
|
Addressed Copilot's review plus an independent cross-model (Sonnet) review in commit Cross-model review caught a bug both the author and Copilot missed:
Copilot findings:
Also from the cross-model review:
Open/deferred: the ND option-walk still stops (rather than fails) on a malformed option — harmless since the target rewrite + recompute remain valid; and there is no kernel-side unit harness to add datapath tests against (the existing |
|
MPLS fix now live-validated (before/after on the kernel datapath). Getting Injected a crafted MPLS frame (label 100, inner
Verified by hot-swapping the fixed driver between the two runs. So |
Second review round: - OvsUpdateAddressAndPortForIpv6 (also the IPv6 NAT path) recomputed the ICMPv6 checksum over ipHdr->payload_len bytes while only pulling the IPv6 fixed header contiguous; payload_len counts extension headers l4Offset skips, so the walk could read past the buffer. Pull the whole packet for the ICMPv6 case and clamp the span to the captured L4 length. - set(nd): only rewrite the target / link-layer options for neighbor solicitation / advertisement with code 0 (mirroring OvsParseIcmpV6); fail on a malformed option list rather than checksum a half-validated packet; derive the ICMPv6 checksum length as payload_len minus extension headers, clamped to the captured bytes. - set(sctp): bail when the IP-derived L4 length is shorter than the SCTP header. - set(ipv6): store ipv6Label in the host-order 20-bit form the flow-key extractor uses, so a re-extract of the rewritten packet matches.
|
Addressed the second review round in
Rebuilt clean |
…extractor - set(ipv6) wrote traffic class and the flow label by casting the start of IPv6Hdr to a 32-bit word and masking. IPv6Hdr is a bitfield layout (priority:4/version:4 + flow_lbl[3]) the extractor reads byte-wise, so the word cast carried an alignment/endianness assumption. Write the fields byte-wise as the inverse of the extractor instead, leaving version intact. - set(arp) keyed the opcode unconditionally and always mirrored the addresses. Match the extractor (Flow.c): key the opcode only when it fits in 8 bits, and the src/dst/sha/tha only for request/reply, zeroing them otherwise, so a re-extract of the rewritten packet matches the key.
|
Addressed the third review round in
Live test of the byte-wise write (isolated OVS, Raw bytes confirm every field: byte0 |
|
All five
The set-field datapath emission is non-obvious (push+set folds into |
- set(mpls) wrote l3Offset - MPLS_HLEN, which is the innermost (BOS) label on a multi-label stack, while the flow key carries the topmost (outermost) label. Locate the topmost label at the end of L2 (derived the same way the parser does, accounting for an in-packet 802.1Q tag) so single- and multi-label stacks both rewrite the correct label. - The IPv6/SCTP L4-length math derived a span by unsigned subtraction of IP length fields; a malformed header could underflow and rely on wraparound + MIN to stay in bounds. Make the subtractions saturating and fail set(sctp) outright on an inconsistent IP length, in OvsUpdateSctpPorts, OvsUpdateNdHeader and OvsUpdateAddressAndPortForIpv6.
|
Round 4 addressed in
|
- The IPv6/IPv4 address-rewrite helpers computed the contiguous span as `l4Offset + isTcp ? sizeof(tcp) : sizeof(udp)`, which parses as `(l4Offset + isTcp) ? ...` and collapses hdrSize to a small constant, so OvsGetHeaderBySize pulls too few bytes (a latent out-of-bounds read on multi-MDL packets). Parenthesize the ternary in OvsUpdateAddressAndPort, OvsUpdateAddressAndPortForIpv6 and OvsUpdateIPv4Header (OvsUpdateIPv6Header was already correct). - set(nd) gated on layers->isIcmp, which OvsExtractLayers sets only when OvsParseIcmpV6 fully succeeds (and which is set for IPv4 ICMP too). Gate on IPv6 + the ICMPv6 L4 proto from the flow key instead; the on-wire ICMPv6 type/code are already validated before the rewrite.
|
Round 5 addressed in
Regression-tested live on the rebuilt driver: |
OvsParseIcmpV6 treats a second source- or target-link-layer-address option as malformed and fails. OvsUpdateNdHeader walked the options without that check, so it would rewrite every occurrence and leave the key reflecting only the last. Track whether each option type has been seen and fail on a duplicate, to match the parser and avoid rewriting a malformed payload.
|
Round 6 addressed in Validated live by injecting two crafted NS frames through the rewrite:
|
The Windows kernel datapath silently ignored
OVS_ACTION_ATTR_SETfor the IPv6, SCTP, ARP, ND and MPLS key types: the action switch had no case for them, so the packet egressed unmodified while the set still reportedNDIS_STATUS_SUCCESS.set(ipv6(...))in particular — OVN logical-router IPv6 next-hop rewrites and DSCP/flow-label/hop-limit changes — was dropped on the floor.This adds per-type inline rewrite helpers modelled on the existing IPv4/TCP/UDP path:
OvsUpdateAddressAndPortForIpv6for the 128-bit address and L4/ICMPv6 checksum fixup, then writes traffic class, flow label and hop limit.Crc32c.{c,h}that is safe atDISPATCH_LEVEL.A failed rewrite now propagates instead of reporting false success.
Validated live on the eryph stack:
set(ipv6)bidirectional address rewrite with correct ICMPv6 checksums (end-to-end ping returns only when both directions are correct), and IPv6 end-to-end through production OVN on the rebuilt driver.