Internet-Draft IPv6 Firewall Metadata November 2026
Bonica, et al. Expires 6 May 2027 [Page]
Workgroup:
IPv6 Maintenance Working Group
Internet-Draft:
draft-bonica-macneil-singh-ipv6-firewall-metadata-00
Published:
Intended Status:
Experimental
Expires:
Authors:
R. Bonica
Hewlett Packard Enterprise
E. MacNeil
Hewlett Packard Enterprise
P. Singh
Hewlett Packard Enterprise

Verifiable Firewall and Security-Enforcement Metadata in IPv6

Abstract

This document defines a way to carry a verifiable record of security enforcement inside IPv6 packets. Each firewall (or other security enforcement point) that handles a packet writes a small, authenticated note saying which rule it matched and what action it took. The notes are protected by a chained Integrity Check Value (ICV), reusing the construction defined for In-situ OAM (IOAM) data integrity. A checker can then prove two separate things: that the notes are authentic (not forged or changed on the way), and that the recorded actions match the intended policy. Because the two checks are independent, a firewall that honestly diverges from policy -- for example because of a stale rule or an implementation bug -- still produces an authentic note, and the divergence is reported rather than hidden. Operators thus gain precise per-hop evidence that every enforcement point was traversed and applied the intended policy, so an EP that was skipped or did not enforce is revealed.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 6 May 2027.

▲

Table of Contents

1. Introduction

Operators want proof of how a packet was treated as it crossed a chain of firewalls and other security enforcement points (EPs). They want to answer simple questions:

This document lets each EP add a small note to the packet. The note says which rule matched and what action the EP took. Each EP also folds its note into a running Integrity Check Value (ICV) using its own secret key. The ICV is the same chained construction used for IOAM data integrity [I-D.ietf-ippm-ioam-data-integrity]; this document does not invent new cryptography.

The design separates two checks that are easy to mix up:

  1. Is the note authentic? (Integrity.) The checker recomputes the ICV chain; if it matches, the notes were written by the claimed EPs and were not changed on the path.

  2. Does the action match policy? (Conformance.) The checker compares each authentic note to the policy that was expected.

The two checks are independent: a note can be authentic yet still diverge from policy. An EP may honestly apply the wrong action -- a stale rule, a rollout still in flight, or the same policy behaving differently across vendors, software versions, or hardware -- and because the note stays authentic, that silent divergence is reported instead of hidden.

Current practice can steer a packet through a set of firewalls, and proof-of-transit schemes can show it traversed them, but neither proves what each firewall actually enforced -- the rule that matched and the action taken. This mechanism supplies that per-hop enforcement evidence, and from it a firewall that was skipped or did not enforce is detected.

This mechanism is intended for use within a single limited domain ([RFC8799]).

2. Conventions and Definitions

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

Enforcement Point (EP):

A firewall or other on-path device that evaluates policy against a packet and records the result.

Policy ID:

An identifier for the policy (or access list) every EP uses.

Policy Version:

The generation of that policy. It changes each time the rule set is edited.

Filter ID:

An identifier for the exact rule (also called a rule ID) that matched, within a given Policy ID and Policy Version. A policy is a set of rules, each with a match and an action; the Filter ID names the one that fired.

ICV:

Integrity Check Value, computed with the chained AES-GMAC construction of [I-D.ietf-ippm-ioam-data-integrity].

Validator:

Any entity that checks the notes. It may be an EP on the path or the final decapsulating node.

3. How It Works

A packet carries a small block of firewall metadata. As the packet crosses each EP, that EP writes one record (its note) and updates the ICV with its own key. At the end, a validator checks the ICV and compares each record to policy.

   packet carrying the firewall-metadata option
       |           |           |                |
       v           v           v                v
   +-------+   +-------+   +-------+        +-----------+
   | EP 1  |   | EP 2  |   | EP 3  |  ...   |  Decap /  |
   | (fw)  |   | (fw)  |   | (fw)  |        | validator |
   +-------+   +-------+   +-------+        +-----------+
   write rec   write rec   write rec       check ICV
   + update    + update    + update        + compare to
     ICV         ICV         ICV             policy

Each EP needs only its own key to write its record. Only a validator needs the keys of the EPs whose records it checks. The diagram shows validation at the end for simplicity, but validation is a role, not a fixed place: a validator can be any EP that holds the upstream keys (early, in-path checking) or just the final node (one check at the end).

4. What the Identifiers Mean

Policy ID, Policy Version, and Filter ID are compact identifiers that stand for things that already exist in a firewall or access-list configuration. A controller assigns the identifiers and keeps the human-readable mapping. The EP stamps them; a validator maps them back to named rules. Only the identifiers travel in the packet; rule text and match fields never do.

Firewall rules may be expressed in any syntax; what matters is that they are semantically identical at every EP, so a given Filter ID maps to the same rule everywhere on the path.

An authenticated {Filter ID, Action} pair then proves which rule an EP matched and what action it took, relative to the named Policy ID and Policy Version.

5. Encoding

The metadata is carried in an IPv6 Destination Options header, reusing the IPv6 option carriage defined for IOAM in [RFC9486]. We would like intermediate nodes to have a chance to examine this information, so one would typically place it in a Hop-by-Hop Options header. That is avoided here because of the security implications of Hop-by-Hop extension headers. Instead it is placed in a Destination Option. Strictly, a Destination Option should be examined only by the destination node, but the intermediate nodes that care about this information are firewalls, which routinely break that rule and snoop deep into the packet. The metadata is therefore placed in a Destination Option on the assumption that it will be snooped.

The three models differ in which nodes are destinations that process the option:

  (a) SRv6 steering -- every EP is a segment endpoint (destination):

        ingress ==> EP1 ==> EP2 ==> EP3 ==> decap / validator
                    |       |       |          |
                    writes  writes  writes     checks ICV + policy
                    Rec1    Rec2    Rec3

  (b) Encapsulation -- only tunnel ends are destinations:

        ingress ========= tunnel ========> egress (EP + validator)
        adds outer IPv6                          one EP's record
        + option

  (c) Transparent inspection -- firewalls inspect/rewrite in path,
      not as the IPv6 destination (limited domain only; outside
      RFC 8200's destination-only processing):

        ... --> [fw EP1] --> [fw EP2] --> [fw EP3] --> ...
                 rewrite      rewrite      rewrite

6. Packet Format

The header and record layouts follow IOAM conventions ([RFC9197]) -- fixed format fields, 4-octet-friendly alignment, and a namespace identifier -- so the format is familiar to IOAM implementers; the same field semantics have an IOAM-native encoding in the companion [I-D.singh-macneil-ioam-firewall-metadata]. All multi-byte fields are in network byte order (big-endian). The values in the figures match the reference run in Section 9.

The option, in order, is an IPv6 Destination Options header followed by these fields:

+----------------------+----------------------------------------+
| IPv6 Destination     | Next Header, Hdr Ext Len,              |
| Options header       | Option Type (TBD), Opt Data Len        |
+----------------------+----------------------------------------+
| Initial Header       | 8 octets  (see {{inithdr}})             |
+----------------------+----------------------------------------+
| Integrity Header     | 32 octets (MethodID, NonceLen, Rsvd,   |
|                      |            Nonce(12), ICV(16))         |
+----------------------+----------------------------------------+
| Per-Node Records     | N x 8 octets (see {{record}})          |
+----------------------+----------------------------------------+
| PadN                 | align option to a multiple of 8 octets |
+----------------------+----------------------------------------+

6.1. Initial Header

The Initial Header is 8 octets:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Namespace ID         |          Policy ID            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Policy Version|   Capacity    | Flags|NextIdx |   Reserved    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Namespace ID (16 bits):

Identifies the security-metadata namespace, mirroring the IOAM Namespace-ID ([RFC9197], Section 4.3). A namespace is a context that fixes how the metadata is interpreted: it disambiguates otherwise non-unique Node IDs and Policy IDs across overlapping domains, and it selects the record format. It is also the extensibility mechanism: instead of adding optional fields to one format, an operator can define a new namespace whose fixed record format carries additional fields (for example a timestamp), and every EP and validator keys its parsing off the Namespace ID. For the same reason there is no separate format-version field. Placed first, as in IOAM. Set by the ingress; immutable and covered by the ICV. The default value 0 means no specific namespace.

Policy ID (16 bits):

Names the policy every EP uses. One value per path, set by the ingress; this document assumes all EPs on the path evaluate the same policy (see Section 8.1). Immutable; covered by the ICV.

Policy Version (8 bits):

Generation (epoch) of that policy, bumped by the control plane on every edit, set once by the ingress. It binds the trace to a specific rule-set generation and MUST be a domain-wide, monotonically increasing value per policy. This is the ingress's single reference generation; if an EP ran a different generation during a rollout (version skew), the per-record PV flag marks it, so the mismatch is treated as expected skew rather than a true divergence (see Section 8.1). Carrying each EP's actual generation is a namespace extension. Immutable; covered by the ICV.

Capacity (8 bits):

The number of record slots the ingress pre-allocated for this option, counted in records (the Namespace ID fixes the record size, 8 octets by default). Set once by the ingress, sized so the option fits the domain PMTU ([RFC8200]). Immutable; covered by the ICV. Capacity lets a validator tell a legitimately full buffer (records present equal to Capacity) from a bypassed or stripped record (fewer records present than Capacity) using only authenticated data. Whether Capacity merits its own octet is left open for working-group review: completeness is already enforced by the authenticated Node IDs against the expected path (Section 8), so if that authenticated capacity and overflow signal is judged not worth the space, this octet (byte 5) can be reclaimed as Reserved.

Flags (3 bits):

Mutable control bits, excluded from the ICV so EPs may update them. One bit is the Overflow (O) indicator: an EP with no free slot sets O to mark the metadata incomplete. This indicator MUST be in the header, not in a per-node record, because an EP that cannot write a record also cannot set a bit inside it. O is only a hint; incomplete metadata is detected regardless by the completeness check (Section 8). The remaining bits are reserved (MUST be zero).

NextIndex (5 bits):

The index of the next free record slot, equal to the count of records already written. An EP writes its record at NextIndex and increments it; when NextIndex equals Capacity there is no free slot and the EP sets O instead of writing. A write-path cursor only: mutable and excluded from the ICV, so a validator relies on the authenticated records and Capacity, never on this field.

Reserved (8 bits):

Set to zero on send, ignored on receipt. Excluded from the ICV. (In the extensible record variant of Section 6.3, this octet instead carries the Optional Data Present bitmap, which is then covered by the ICV.)

6.2. Per-Node Record

Each EP appends one 8-octet record:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            Node ID            |         Filter ID (20)        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|(cont) |Action |State|Severity |             Flags             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  bytes 0-1 : Node ID      (16 bits)
  bytes 2-4 : Filter ID    (20 bits) || Action (4 bits)
  byte  5   : Policer Data -- State (3 bits) || Severity (5 bits)
  bytes 6-7 : Flags        (16 bits)
Node ID (16 bits):

Identifies the EP that wrote the record. Selects the per-node key.

Filter ID (20 bits):

The firewall rule that matched, scoped to Policy ID and Policy Version. It is the compact handle the controller assigns to that rule and maps back; it need not equal the ACL sequence-id or priority, and its meaning is scoped to one policy within a single domain -- the same value in another domain is unrelated.

Action (4 bits):

The action applied: 0 = ACCEPT, 1 = DROP, 2 = POLICE. Other values reserved.

Policer State (3 bits):

Policer state when Action = POLICE. This document uses 0 = GREEN (in profile, or conformant -- within the configured committed rate and burst) and 2 = RED (out of profile, or non-conformant -- exceeding them), the green/red marking of [RFC2697]; value 1 (YELLOW) and the rest are reserved. The value is the policer's state as it handled the packet: RED when the measured rate was over the configured rate, GREEN when within it. It is stamped on a packet the policer forwards (allows), so the rate condition travels onward with the packet. A RED record therefore marks an allowed, forwarded packet that the policer handled while over its configured rate -- not a dropped one (see Section 9, Case 5). If the policer instead drops the excess traffic, those packets take the DROP action and carry no metadata downstream -- so they never appear as RED records.

Severity (5 bits):

A carry-forward count of out-of-profile hops: a GREEN hop adds 0, a RED hop adds 1. The count travels with the packet, so no EP needs per-flow state, and the final value is how many hops policed this packet out of profile. Because it accumulates across hops and arrives authenticated, an egress or collector can use it as a distributed rate-limit / DDoS signal -- shedding or correlating packets that were policed at many hops, catching distributed overload that no single hop saw enough of to act on alone (see Section 10). A future version MAY add Policer values and per-color weights so Severity becomes a weighted intensity instead of a plain count.

Flags (16 bits):

Per-node status, covered by the ICV (so each bit is an authenticated per-node statement). Two bits signal that the EP's policy context differs from the Initial Header:

  • Bit 0 -- PV (Policy Version Differs): the EP evaluated a different Policy Version than the header (version skew, typically a transient rollout).

  • Bit 1 -- PI (Policy ID Differs): the EP evaluated a different Policy ID than the header (policy layering, typically intentional and structural).

When either is set, a validator treats an Action or Filter mismatch at that EP as an expected context difference to review (see Section 8.1), not a true execution divergence, so legitimate skew or layering does not raise a false alarm. Keeping them separate lets a validator handle them differently (for example, auto-suppress PV skew under a convergence timer while always expecting PI layering). These bits say only that the context differs, not which Policy ID or Version the EP used; the actual values are a namespace extension. Remaining bits reserved (MUST be zero).

Security-critical status (anything a validator acts on) MUST be an immutable per-node field so that it is covered by the ICV. Mutable header fields (Flags, NextIndex, Reserved) are not authenticated and MUST NOT carry security decisions.

6.3. Record Extensibility: Fixed vs. Optional Data Present

This document specifies the fixed 8-octet record above as the primary format, and records an extensible variant as a co-equal alternative. The choice is left open for working-group discussion.

Fixed (primary):

Every record is exactly 8 octets with fixed field offsets. It is the simplest to implement (fixed offsets suit hardware), adds no per-packet length computation, needs no extra covered field, and still supports format evolution because a different Namespace ID can select a different fixed record format -- the IOAM approach. Its limitation is that an optional field (for example a timestamp or session identifier) cannot be added to the same namespace in place; it requires a new namespace/format.

Optional Data Present (alternative):

The Reserved octet of the Initial Header instead carries an 8-bit Optional Data Present bitmap, set once by the ingress. Each set bit signals an optional field appended to every record (for example a 64-bit timestamp or session identifier), making the record self-describing on one namespace. Because the bitmap defines the record layout it is security-relevant, so it is immutable and covered by the ICV. Its costs are variable record length (every EP and validator computes record size from the bitmap), an added covered field, an extra IANA bit registry, and more implementation complexity.

For an initial specification the fixed format is RECOMMENDED; the Optional Data Present variant is preferable only where optional per-record fields are expected frequently on a single namespace.

6.4. Complete Option on the Wire

The figure below assembles every field into one concrete option. The bytes are the real values from Case 2 of Section 9 (three EPs, all in sync): a 64-octet option carrying the 8-octet Initial Header, the 32-octet Integrity Header, and three 8-octet records. All values are big-endian; offsets are in hexadecimal.

Outer framing -- the option sits in an IPv6 Dest Options header:

 +--------------+--------------+--------------+----------------+
 | Next Header  | Hdr Ext Len  | Opt Type=TBD | Opt Data Len   |
 +--------------+--------------+--------------+----------------+
 |         Option Data (64 octets, detailed below)   | PadN     |
 +---------------------------------------------------+----------+

Option Data -- 64 octets (Case 2: 3 EPs, every EP ACCEPT / 0x1001):

 off  octets                     field
 ---  -----------------------    -----------------------------------
 Initial Header (8):
 00   00 00                      Namespace ID   = 0x0000   (covered)
 02   00 a1                      Policy ID      = 0x00a1   (covered)
 04   01                         Policy Version = 1        (covered)
 05   03                         Capacity       = 3        (covered)
 06   03                         Flags=0, NextIndex=3      (mutable)
 07   00                         Reserved       = 0        (mutable)

 Integrity Header (32):
 08   00                         Method ID      = 0
 09   0c                         Nonce Length   = 12
 0a   00 00                      Reserved
 0c   01 00 00 01 00 00 00 00    Nonce: KeyID=01 Encap=000001
 14   00 00 00 02                       Counter=...0000002
 18   45 cb cc fc 11 8d e8 cd    ICV (16) = carried ICV_3
 20   57 37 44 44 d4 cc 09 bc      45cbccfc118de8cd57374444d4cc09bc

 Per-Node Records (3 x 8):
 28   00 01 01 00 10 00 00 00    EP1: id=0001 flt=0x1001 ACPT GRN s0
 30   00 02 01 00 10 00 00 00    EP2: id=0002 flt=0x1001 ACPT GRN s0
 38   00 03 01 00 10 00 00 00    EP3: id=0003 flt=0x1001 ACPT GRN s0

Each record packs Node ID (2 octets), then Filter ID (20 bits) and Action (4 bits) as three octets 01 00 10 = (0x01001 << 4) | 0x0, then Policer Data (00 = GREEN, Severity 0), then the 16-bit record Flags (00 00). The masked Initial Header (mutable octets 06, 07 zeroed; octet 05 Capacity covered) is folded in only by the first EP, in ICV_1; each later EP folds its record over the previous ICV, so the value carried at offset 0x18 is the last link, ICV_3. The construction is defined next.

The ICV reuses the chained AES-GMAC construction of [I-D.ietf-ippm-ioam-data-integrity] (Method ID 0). Each EP i has a unique key Key_i. Let MASK(H) be the Initial Header with its mutable bits cleared (Section 7), Record_i the record written by EP i, and Nonce the 12-octet nonce (KeyID(1) || EncapNodeID(3) || Counter(8)). For a path of N EPs:

  ICV_1 = GMAC(Key_1, Nonce, MASK(H) || Record_1)        (first EP)
  ICV_i = GMAC(Key_i, Nonce, ICV_{i-1} || Record_i)      for i = 2..N

The first EP computes ICV_1 over the masked header and its own record. Each later EP i folds its record over the ICV it received (ICV_{i-1}) and overwrites the field, so only the latest value, ICV_N, is carried -- that is the "carried ICV". The Integrity Header carries Method ID (1), Nonce Length (1), Reserved (2), the Nonce (12), and ICV_N (16). A validator holds the per-node keys, replays the chain over the masked header and the carried records to recompute ICV_N, and compares it to the carried value. These are the encapsulating-, transit-, and validator-node procedures of [I-D.ietf-ippm-ioam-data-integrity] (Sections 5.3, 5.4, and 5.6; that document indexes the first node as 0).

The chain, drawn as each EP folding its record into the single ICV field:

   MASK(H)+Rec1        ICV1+Rec2        ICV2+Rec3
        |                  |                |
        v                  v                v
   GMAC(Key1) --ICV1--> GMAC(Key2) --ICV2--> GMAC(Key3) --> ICV3
                                                           (carried)

   Only ICV3 travels on the wire. A validator holding Key1..KeyN
   replays ICV1 -> ICV2 -> ICV3 over the carried records and checks
   it equals ICV3.

7. ICV Coverage Mask

Only immutable fields are covered. Fields that legitimately change en route are masked out (ANDed to zero) before the ICV is computed, exactly as in Section 5.2 of [I-D.ietf-ippm-ioam-data-integrity].

Table 1
Initial Header field Covered by ICV
Namespace ID yes
Policy ID yes
Policy Version yes
Capacity yes
Flags no
NextIndex no
Reserved no

All per-node record fields are covered by the ICV.

The AND-mask applied to the 8-octet Initial Header (fixed variant) is:

  FF FF  FF FF  FF  FF  00  00
  ^^^^^  ^^^^^  ^^  ^^  ^^  ^^
  |      |      |   |   |   Reserved           -> 00   (excluded)
  |      |      |   |   Flags || NextIndex  -> 00   (excluded)
  |      |      |   Capacity                -> FF   (covered)
  |      |      Policy Version             -> FF   (covered)
  |      Policy ID                         -> FFFF (covered)
  Namespace ID                            -> FFFF (covered)

(In the Optional Data Present variant of Section 6.3, the final octet is covered: the mask becomes FF FF FF FF FF FF 00 FF.)

Mask check. ICV_1 over the masked header plus the first record does not change when only mutable bits change, and does change when a covered bit changes:

  base                ICV_1 = a48fd4ba...
  flip mutable bits   ICV_1 = a48fd4ba...  (SAME -> excluded)
  flip Policy ID      ICV_1 = a2e4c797...  (DIFFERS -> covered)

8. Checking the Metadata

A validator does the following checks.

   carried records + carried ICV
             |
             v
   Step 1  recompute the chain, compare to the carried ICV
             |
        FAIL | PASS (authentic)
       +-----+-----------------------+
       v                             v
   tampered:                 Step 2  compare each record's
   admin response                    Action/Filter to the
                                     expected policy
                                     |
                                MATCH | MISMATCH
                               +------+-------------------+
                               v                          v
                          conformant              divergent: if PV/PI
                          (done)                  set, an expected
                                                  context difference
                                                  to review; else
                                                  admin response

Step 1 -- Integrity. Recompute the ICV chain over MASK(H) and the carried records, then compare the final recomputed ICV to the carried ICV. If they are equal, the records are authentic and unchanged; otherwise the metadata was tampered with. This is the Validator procedure of [I-D.ietf-ippm-ioam-data-integrity] (Section 5.6).

Step 2 -- Conformance. For each real record, compare the Action and Filter ID to the policy expected for the carried Policy ID and Policy Version. Any mismatch flags honest divergence at that EP.

Step 3 -- Completeness. Confirm that a record is present for every EP the validator expected on the path. Because a chain built over a subset of EPs is itself a valid chain, Step 1 cannot by itself reveal an EP that never stamped a record. The validator compares the authenticated Node IDs against the expected path, and their count against the immutable Capacity: a count equal to Capacity is a legitimately full buffer, whereas a count below Capacity with an expected EP absent is a bypassed or stripped record. Capacity is covered by the ICV while the mutable NextIndex and the Overflow hint are not, so this uses only authenticated data. A missing record is a positive detection: that EP was bypassed or failed to enforce -- a security event in its own right (for example, traffic that skipped a firewall), not merely a gap. Removal of the entire option is outside this check and is left to the encapsulating protocol (see Security Considerations).

A record may pass Step 1 (authentic) and fail Step 2 (divergent).

8.1. One Policy and Generation per Path

The Initial Header carries a single Policy ID and Policy Version, so this specification assumes every EP on the path evaluates the same policy and generation. Conformance (Step 2) compares each record's Action and Filter ID to that one policy.

Two situations break that assumption. EPs may legitimately apply different policies (policy layering -- for example a perimeter firewall policy at the domain edge and an internal segmentation policy deeper in, or a tenant policy at one hop and a corporate policy at another), and during a rollout EPs may briefly run different generations of the same policy (version skew). A single header value cannot express either case.

Each record therefore carries two authenticated hint flags (Section 6.2): PV (Policy Version Differs) and PI (Policy ID Differs). An EP sets PV when it ran a different generation than the header (rollout skew) and PI when it ran a different policy (layering). They let a validator treat that EP's Action or Filter mismatch as an expected context difference to review, rather than a true divergence, so an operator is not alarmed by legitimate skew or layering; keeping them separate allows different handling (for example, auto-suppressing PV skew under a convergence timer). The flags say only that the context differs, not which Policy ID or generation the EP used; carrying those actual values costs scarce record bits and is therefore a namespace extension (a record format, selected by Namespace ID, that adds per-EP Policy ID and Policy Version). The header Policy Version remains the ingress's reference generation.

8.2. Validation Is a Role

Validation is a role, not a fixed node. Any EP that holds the keys of the upstream EPs 0..i can recompute the ICV prefix and check the records written so far. The choice is a key-distribution policy:

  • End-only validation: only the decap holds all keys. Simplest distribution; a single validation at the end that still runs all three checks -- integrity (the chain), conformance (per-record action and filter), and completeness (expected Node IDs).

  • Distributed validation: capable EPs hold the upstream keys they need and check inline. Earlier detection, at the cost of a wider key footprint.

An EP can play both roles in a single pass: it writes its own record and, if it holds the upstream keys, validates the records before it (an inline-validating EP). This is the node that inline Severity reactions (Section 10) and Case 4 rely on.

Whatever the key distribution, the decapsulating or egress node MUST validate the metadata of packets that reach it; inline validation at earlier EPs is optional. A packet dropped before egress is validated from the dropping EP's export instead.

8.3. Response to a Detected Problem

The protocol produces a trustworthy verdict; the response is set by the operator and is local to the detecting entity. Typical actions, which MAY be combined:

  • log or raise an alarm;

  • report the event to the management plane;

  • collect additional diagnostics (for example the EP's firewall state or policer counters) and attach them to the report;

  • mirror or redirect the packet and its metadata to a collector for deeper analysis;

  • drop the packet, if so configured.

This applies equally to an integrity failure and a conformance mismatch: the mechanism produces the verdict, and which action follows is administratively configured. There is no on-wire "verdict" field.

An EP always enforces its own policy independently; validating the metadata is an additional capability, never a substitute for an EP's own inspection, and an EP's own decision MUST NOT be delayed pending validation. When an EP that expects metadata cannot verify it (missing, stripped, or failing the integrity check), local policy selects the behaviour:

  • Verify-or-Drop (strict): unverifiable or missing expected metadata is treated as absent, raising an alarm and, optionally, dropping the packet. Suited to high-assurance zones.

  • Permissive: the EP logs the event, enforces its own policy, and forwards. Suited to migration, key rotation, or mixed domains.

  • Non-participant: an EP never provisioned with the relevant keys simply forwards the packet with the metadata intact.

When the response is DROP (or an EP's own policy drops the packet), the packet does not continue, so the in-band note cannot be delivered downstream. The dropping EP finalizes the partial chain and MAY export it out-of-band, rate-limited, so the evidence is preserved for a key-holding collector to validate; the dropping EP need not -- and, as a non-validator, may be unable to -- validate the upstream records itself. Responses that let the packet continue (ACCEPT, POLICE) keep the note in-band to the decap.

9. Worked Examples

These examples come from a runnable reference. Policy ID is 0x00a1, Policy Version is 1, and the expected decision at every EP is ACCEPT / Filter ID 0x1001. ICVs are 16 octets; the "..." form shows the first bytes.

9.1. Case 1: Tampering Is Caught

The packet holds a single ICV field -- the "carried ICV". The chain keeps only the latest value: EP1 computes it first, and each later EP folds its own record in and overwrites it, so the carried ICV is the value written by the last EP (EP3). Three EPs each ACCEPT with Filter ID 0x1001.

  BUILD -- Rec_i = EP i's record, each folded into one ICV field:
  EP1: ICV_1 = GMAC(K1, Nonce, MASK(header) || Rec_1) -> b23daca9...
  EP2: ICV_2 = GMAC(K2, Nonce, ICV_1 || Rec_2) -> 04eb105e...
  EP3: ICV_3 = GMAC(K3, Nonce, ICV_2 || Rec_3) -> a01d8d8f...

An attacker flips EP2's recorded action ACCEPT -> DROP but cannot update the carried ICV, because it lacks EP2's key.

  CHECK (validator replays the chain over the carried records):
  ICV_1 = GMAC(K1, Nonce, MASK(header) || Rec_1) -> b23daca9...
  ICV_2 = GMAC(K2, Nonce, ICV_1 || Rec_2*) -> 31e121c7... (altered)
  ICV_3 = GMAC(K3, Nonce, ICV_2 || Rec_3) -> c3355628...
  recomp ICV_3 = c3355628 != carried = a01d8d8f -> TAMPERING CAUGHT

Integrity detects that the chain was altered, but a single carried ICV does not by itself say where. Two things localize it: Step 2 conformance independently flags EP2's record (DROP != expected ACCEPT); and a validator that knows the expected records can replay a clean chain and compare step by step, where the first mismatch is EP2. An ICV failure is the validator's verdict, not a value any node writes -- there is no per-record "ICV failed" bit -- and the action on failure (alarm, drop, collect, export) is administratively configured (see Response to a Detected Problem).

9.2. Case 2: Everything in Sync

Every EP applies the expected ACCEPT / 0x1001. Each EP stamps one record and folds it into the ICV; here every record is {Node ID, Filter 0x1001, Action ACCEPT, Policer GREEN, Severity 0}.

  Initial Header: Namespace=0000 PolicyID=00a1 PolicyVer=1
  record EP1: ID=0001 Filter=0x1001 Action=ACCEPT Policer=GREEN Sev=0
  record EP2: ID=0002 Filter=0x1001 Action=ACCEPT Policer=GREEN Sev=0
  record EP3: ID=0003 Filter=0x1001 Action=ACCEPT Policer=GREEN Sev=0
  carried ICV = 45cbccfc...
  Step 1 Integrity   -> PASS (authentic, untampered)
  Step 2 Conformance -> MATCH (every EP applied ACCEPT/0x1001)

9.3. Case 3: Action Mismatch -> DROP Ends the Path

EP 2 honestly records DROP with the expected Filter ID 0x1001 -- a pure action mismatch. A DROP stops the packet, so there is no EP 3 and the in-band chain never reaches the egress.

  build:  EP1 {Filter 0x1001, ACCEPT} -> EP2 {Filter 0x1001, DROP}
          carried ICV (2 records) = dbbc39ae...
  EP2 drops per its own policy and MAY export the 2-record chain
  out-of-band (rate-limited) to a collector that holds the keys; EP2
  itself need not validate it.
  Collector:
    Step 1 Integrity   -> PASS (authentic)
    Step 2 Conformance -> MISMATCH at EP2: Action DROP vs exp ACCEPT
      (Filter 0x1001 matches; only the action diverges)

A plain dropping EP cannot run Step 1 -- it lacks the upstream keys -- so it forwards the evidence to a key-holding collector. An inline-validating EP could run both checks itself before exporting.

9.4. Case 4: Filter Mismatch -> Caught Inline

EP 2 takes the right action (ACCEPT) but matched a different rule, Filter ID 0x1005. The action is ACCEPT, so the packet continues. EP 3 acts as an inline validator.

  build:  EP1 {Filter 0x1001, ACCEPT} -> EP2 {Filter 0x1005, ACCEPT}
          -> EP3 {Filter 0x1001, ACCEPT}    carried ICV = a018ba1e...
  EP3 inline validator (holds keys for EP1, EP2):
     recompute after EP1 = 7ac31d52...
     recompute after EP2 = 5daf39da...  (so-far matches -> PASS)
     conformance -> MISMATCH at EP2: Filter 0x1005 vs expected 0x1001
       (Action ACCEPT matches; only the rule that fired diverges)
     configured response = export to collector; keep forwarding
  Decap re-validates the full 3-record chain independently.

The outcome (ACCEPT) matched policy, but a different rule fired. This signature -- right action, wrong rule -- is exactly what an allow/deny result alone would hide. It typically indicates one of: a stale or drifted rule set on that EP, Policy Version skew during a rollout, or an implementation bug that matched the wrong rule. The PV/PI record flags (Section 6.2) let an EP distinguish an expected context difference (version skew or policy layering) from a true bug: when neither flag is set, a Filter mismatch is an unexpected divergence to investigate. Reporting it exposes drift or a bug that would otherwise go unnoticed.

9.5. Case 5: Policer Color and Severity

Action is POLICE; the Policer State is GREEN (within the configured rate) or RED (exceeding it -- "out of profile"). Severity counts the RED hops along the path; a GREEN hop does not add to it.

  EP1 RED   (+1)  carried severity 1   ICV = f52d43f8...
  EP2 GREEN (+0)  carried severity 1   ICV = f9bd54a3...
  EP3 RED   (+1)  carried severity 2   ICV = 9e19a585... (carried)
  Step 1 Integrity -> PASS
  path severity (final carried value) = 2  (two out-of-profile hops)

An egress MAY use the Severity count or a RED marking to treat the packet more strictly (see Section 10).

9.6. Scaling to More EPs

Path length changes only the record array, never the ICV: the ICV is always 16 octets, and each EP adds one 8-octet record, so the option grows linearly. For five EPs (all ACCEPT/0x1001) the size is:

  option content =  8  (Initial Header)
                 + 32  (Integrity Header: 16-octet ICV + 16 octets of
                        Method ID, Nonce Length, Reserved, and Nonce)
                 + 5 x 8  (per-node records)
                 = 80 octets

  full Dest-Opt header = 80 + 4 (framing) + 4 (PadN) = 88 octets
  carried ICV (EP5) = efea617e...

10. Inline Use and Threshold-Triggered Export

Inline, an EP or the egress MAY use the Policer color (RED) or the Severity count to treat a packet more strictly -- deprioritizing it or policing it tighter when giving out resources. A node that acts on carried Policer State or Severity MUST first validate the upstream chain (that is, act as a validator for those records); otherwise it is relying on unauthenticated data an on-path attacker could have altered. Carried severity is thus trustworthy only at a validating node, which in the common case is the egress.

When Severity crosses a configured threshold, the packet's metadata MAY be exported to a collector. Because each record is authenticated and ordered, the collector reads the exact path and the RED-marking EPs directly from the packet, instead of reconstructing the path from independent per-device records. By correlating Filter IDs across exported packets, the collector can see where related flows entered on other EPs and build an attack graph.

Flows do not merge in band: two flows are two packets, each carrying its own chain. A convergence point MAY independently mark RED when the aggregate of several separately-policed flows exceeds its own limit. In the reference run, two flows both cross EP3 (RED):

  Flow A: EP1(RED) -> EP3(RED)   carried ICV = b76d76c1...
  Flow B: EP2(RED) -> EP3(RED)   carried ICV = 0f4058f2...

The correlation itself is conventional collector and telemetry work (for example [RFC8955] and [RFC8811]); what is new is the authenticated, deterministic, per-hop enforcement evidence it consumes -- something flow-record correlation and probabilistic packet marking cannot provide. Thresholds SHOULD account for path length, since Severity is bounded by the number of hops.

11. Option Sizing

The option content is Initial Header (8) + Integrity Header (32) + N records (8 each). The full Destination Options header adds a 2-octet option header plus 2-octet option type/length and PadN to a multiple of 8 octets.

Table 2
Records (N) Option content Full Dest-Opt header
3 64 octets 72 octets
5 80 octets 88 octets
8 104 octets 112 octets

The per-hop record is deliberately compact (8 octets) to keep the option small and bounded by path length, targeting the common firewall and DDoS use cases while limiting growth of the packet. Because a Destination Options header is not carried in IPv6 fragments and the option is added by the ingress (which knows the PMTU, [RFC8200]), the ingress MUST size the option -- choosing the number of pre-allocated records, carried as Capacity (Section 6.1) -- so that the resulting packet stays within the PMTU of the domain and does not cause fragmentation. If an EP still has no free slot (NextIndex equals Capacity), it sets the Overflow (O) indicator in the Initial Header Flags (Section 6.1) to mark the metadata incomplete.

12. Security Considerations

The ICV provides origin authentication and integrity for the per-node records and the immutable header fields, inheriting the properties and key-management assumptions of [I-D.ietf-ippm-ioam-data-integrity]. Points specific to this document:

This mechanism is intended for a single limited domain [RFC8799].

13. IANA Considerations

This document requests an IPv6 Destination Option Type for firewall metadata (value TBD), with the act bits set so that unknown receivers skip the option and the change-en-route bit set. It also requests registries for Action values and Policer State values, and (for the Optional Data Present variant of Section 6.3) a registry for Optional Data Present bits. Namespace IDs MAY reuse the IOAM Namespace-ID registry of [RFC9197].

14. References

14.1. Normative References

[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/rfc/rfc8174>.
[RFC8200]
Deering, S. and R. Hinden, "Internet Protocol, Version 6 (IPv6) Specification", STD 86, RFC 8200, DOI 10.17487/RFC8200, , <https://www.rfc-editor.org/rfc/rfc8200>.

14.2. Informative References

[I-D.ietf-ippm-ioam-data-integrity]
Brockners, F., Bhandari, S., Mizrahi, T., and J. Iurman, "Integrity Protection of In Situ Operations, Administration, and Maintenance (IOAM) Data Fields", Work in Progress, Internet-Draft, draft-ietf-ippm-ioam-data-integrity-20, , <https://datatracker.ietf.org/doc/html/draft-ietf-ippm-ioam-data-integrity-20>.
[I-D.singh-macneil-ioam-firewall-metadata]
Prashant, S. and E. MacNeil, "IOAM Data Fields for Firewall and Security Metadata", Work in Progress, Internet-Draft, draft-singh-macneil-ioam-firewall-metadata-00, , <https://datatracker.ietf.org/doc/html/draft-singh-macneil-ioam-firewall-metadata-00>.
[RFC2697]
Heinanen, J. and R. Guerin, "A Single Rate Three Color Marker", RFC 2697, DOI 10.17487/RFC2697, , <https://www.rfc-editor.org/rfc/rfc2697>.
[RFC8799]
Carpenter, B. and B. Liu, "Limited Domains and Internet Protocols", RFC 8799, DOI 10.17487/RFC8799, , <https://www.rfc-editor.org/rfc/rfc8799>.
[RFC8811]
Mortensen, A., Ed., Reddy.K, T., Ed., Andreasen, F., Teague, N., and R. Compton, "DDoS Open Threat Signaling (DOTS) Architecture", RFC 8811, DOI 10.17487/RFC8811, , <https://www.rfc-editor.org/rfc/rfc8811>.
[RFC8955]
Loibl, C., Hares, S., Raszuk, R., McPherson, D., and M. Bacher, "Dissemination of Flow Specification Rules", RFC 8955, DOI 10.17487/RFC8955, , <https://www.rfc-editor.org/rfc/rfc8955>.
[RFC9197]
Brockners, F., Ed., Bhandari, S., Ed., and T. Mizrahi, Ed., "Data Fields for In Situ Operations, Administration, and Maintenance (IOAM)", RFC 9197, DOI 10.17487/RFC9197, , <https://www.rfc-editor.org/rfc/rfc9197>.
[RFC9486]
Bhandari, S., Ed. and F. Brockners, Ed., "IPv6 Options for In Situ Operations, Administration, and Maintenance (IOAM)", RFC 9486, DOI 10.17487/RFC9486, , <https://www.rfc-editor.org/rfc/rfc9486>.

Relationship to IOAM

The integrity construction is taken directly, by reference, from IOAM data integrity [I-D.ietf-ippm-ioam-data-integrity]: the chained AES-GMAC ICV, Method ID 0, the 12-octet nonce (Key ID, Encapsulating Node ID, counter), the AND-mask of immutable fields, and all key and nonce management (per-node keys, Key ID rotation, the counter limit, and avoidance of key-nonce reuse) are those of that document and are not restated or redefined here. The IPv6 carriage follows the model of [RFC9486] and the IOAM data fields of [RFC9197]; the Namespace ID mirrors the IOAM Namespace-ID. This document defines no new cryptography.

This document is the native-IPv6 carriage of the same security-enforcement metadata defined for IOAM in [I-D.singh-macneil-ioam-firewall-metadata]. The two share field semantics (Policy ID, Policy Version, Filter ID, Action, Policer State, Severity) and differ only in encoding: the companion document carries the metadata as IOAM-Data-Fields within an Integrity-Protected Trace Option-Type, while this document carries a compact, fixed-layout equivalent in a native IPv6 Destination Option for deployments that do not run IOAM. On-wire field widths are compacted here to limit packet growth (see Option Sizing); the semantics are intended to stay aligned with the companion document.

Acknowledgments

The authors thank the authors and contributors of the IOAM data integrity work, on which the ICV here is based.

Authors' Addresses

Ron Bonica
Hewlett Packard Enterprise
Erin MacNeil
Hewlett Packard Enterprise
Prashant Singh
Hewlett Packard Enterprise