Internet-Draft Resilient Next-Hop Groups October 2026
Xu, et al. Expires 3 May 2027 [Page]
Workgroup:
Individual Submission
Published:
Intended Status:
Standards Track
Expires:
Authors:
B. Xu
China Unicom
C. Liu
China Unicom
J. Ren
China Unicom
C. Wen
China Unicom
W. Cheng
Centec
J. Wang
Centec
M. Wu
Centec
Y. Yang
Centec

A YANG Data Model for Resilient Next-Hop Groups

Abstract

This document defines a YANG data model for configuring and observing resilient next-hop groups. A fixed bucket table separates flow hashing from member selection. The model augments the routing management model with shared groups, static route bindings, weighted bucket targets, restoration policies, and forwarding-confirmed operational state.

The specification defines distinct quota and reference restoration policies, consistent timer semantics, and an administrative rebalance action protected by a state-generation precondition. It standardizes management behavior for this class of implementation; it does not define a new routing protocol, hash algorithm, or transport ordering guarantee.

Discussion and Implementation Status

This note is to be removed before publishing as an RFC.

This is an individual proposal intended for the Standards Track. No working group adoption or independent interoperability implementation is claimed. The authors invite review of the problem statement, overlap with existing work, and implementation experience.

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 3 May 2027.

1. Introduction

Changing a multipath group can change the next hop selected for an existing flow. The extent depends on the selection algorithm [RFC2992]. A bucket indirection table permits an implementation to repair buckets associated with a failed member while retaining other assignments. Linux resilient groups provide one public implementation of this approach [LINUX].

A controller managing this class of forwarding needs to distinguish capabilities from configured policy, decide whether an active bucket may move on recovery, and determine whether a reported table has reached the forwarding plane. A group name and weighted member list do not fully express these decisions.

This document proposes a common YANG interface for those decisions and observable invariants. A controller can select a policy, read current assignments and their generation, then issue a guarded rebalance. It does not select a particular internal hash, memory layout, or failure detector. The requirements apply only to implementations claiming support for this module.

2. Requirements Language

The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD 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.

3. Terminology

A member has a stable group-scoped identifier, positive weight, and an interface and/or next-hop address. Usability is the local forwarding subsystem assessment that the member can carry traffic. The assessment can depend on interface state, neighbor resolution, or liveness detection; it is not prescribed by this model.

B is the configured number of logical buckets. A(b) is the confirmed effective usable owner of bucket b; it is absent when forwarding through that bucket is confirmed suppressed or unassigned. R(b) is a reference owner used only by the reference policy. Raw hardware programming that is known to be unusable is not a usable assignment.

A migration changes a confirmed owner to another confirmed owner. Initial assignment and removal are separately observable mapping changes and are not owner-to-owner migration counts. A bucket is idle when no packet has been observed for it over the configured monotonic interval. An idle bucket can still represent a live but temporarily silent connection.

The quota policy seeks integer weighted shares of all B buckets over usable members. The reference policy seeks a stable reference ownership for every currently usable reference member. These are distinct balance conditions; neither promises equal traffic rates because bucket traffic can be uneven.

Flow-invariant fields -> fixed hash parameters -> bucket b -> A(b)
                                                         |
Member usability -> safety repair ------------------------+
Per-group policy -> quota targets T or reference R -------+
Recovery timers -> eligible discretionary movements ------+
Controller action -> generation check -> confirmed moves -+

4. Forwarding and Restoration Behavior

4.1. Mapping and Configuration Boundary

The mapping from packet fields to bucket index MUST remain independent of member count, member usability, and member weights. Changes to B or hash parameters are explicit administrative changes and MAY remap all flows. Implementations MUST document their hash inputs and supported bucket sizes. This model does not configure hash inputs or a hash seed.

A server MUST reject unsupported bucket counts or address-family combinations before accepting configuration. It MUST NOT silently substitute a different B. Weights express relative bucket allocation rather than guaranteed bandwidth. A member can receive zero buckets when B is small relative to the number or weight distribution of members; this does not make that member unusable.

Configuration commits MUST be serialized with group control operations. Configuration values in the operational datastore describe the applied configuration under NMDA [RFC8342]. If intended configuration cannot be applied, the server MUST retain and expose the actual applied state and report the error; an accepted edit is not a forwarding acknowledgment. Changes of B, members, weights, or policy MAY migrate usable buckets and are outside the failure-only minimal-change rule.

4.2. Failure Repair and Loss of Forwarding

When a member becomes unusable, safety repair MUST take priority over discretionary restoration. The repair pass MUST target buckets whose effective owner became unusable; it MUST NOT move a bucket with a still-usable owner merely to improve balance. It MUST proceed without waiting for hold-down, idle-timer, or unbalanced-timer. This is a scheduling requirement, not a zero-time hardware repair guarantee.

Each affected bucket MUST be assigned to a currently usable member, or have forwarding through the failed owner suppressed when no usable assignment can be installed. Before programming each new assignment the implementation MUST recheck destination usability. An unusable member MUST NOT remain eligible for packet selection. If the platform cannot suppress a failed member or confirm the result, it MUST report failed programming and MUST NOT claim successful repair.

In quota policy, receivers SHOULD be selected to reduce current deficits against T where possible; the repair pass need not achieve T because that could require moving unaffected buckets. In reference policy, an affected bucket MUST use its reference owner when that owner is usable; otherwise any usable temporary owner MAY be selected. Safety repair can use a member whose recovery hold-down has not expired.

If no member is usable, forwarding through the group MUST be suppressed. When members become usable from that condition, all unassigned buckets MUST be assigned without recovery hold-down. In reference policy a usable reference owner takes precedence; remaining buckets can use another usable owner. Programming can be partial: oper-status is up only if all buckets have usable confirmed assignments, degraded if some do, and down if none do.

4.3. Quota Policy

restoration/policy=quota is the default. With usable set U nonempty, let W be the sum of usable weights. The server MUST select a fixed integer T_i for each member, zero for unusable members, with T_i in {floor(B*w_i/W), ceil(B*w_i/W)} for usable members and sum(T_i)=B. It MUST expose target-buckets and retain these targets until B, membership, weights, or usability changes. Calculations MUST avoid intermediate integer overflow.

Largest-remainder apportionment with ties resolved by ascending member ID is one conforming algorithm. Identical target choices across devices are not required; the selected targets are observable. After safety repair has assigned all B buckets, every discretionary quota migration MUST move one bucket from a member with active-buckets>T_i to a usable eligible receiver with active-buckets<T_i.

This rule removes a common rounding deadlock: if an integer deficit exists, an integer surplus must also exist. Each successful quota migration reduces the sum of absolute deficits by two. It does not ensure prompt convergence when receiver hold-down, activity, non-revertive mode, or programming errors prevent movements. With U empty all target-buckets values MUST be zero.

4.4. Reference Policy

A server advertising reference-restoration supports restoration/policy=reference; advertising the feature MUST NOT switch a group from quota policy. On selecting reference policy, the server MUST establish R across all configured members, independently of current usability. Its per-member counts MUST be floor or ceiling of the configured weighted share and total B.

R MUST remain unchanged across usability transitions. Configuration changes affecting B, membership, weights, or policy MAY establish a new R; unchanged assignments SHOULD be retained where the new constraints allow. The reference-generation MUST advance whenever R is created or changed. No cross-device equality of R is required.

For discretionary reference restoration, a bucket is eligible to move only to its usable reference owner and only if A(b) differs from R(b). Quota donor and receiver limits MUST NOT constrain this move. Mixing these policies can prevent restoration when a temporary owner is not above its instantaneous weighted share.

With all members usable, stable R, and every required move eventually permitted and confirmed, A converges to R independently of the order of prior failures. This is a conditional convergence property: non-revertive mode, disabled force timers, continuously busy buckets, or hardware errors can leave convergence pending indefinitely. While a member is down, reference policy does not guarantee proportional allocation among surviving members.

4.5. Recovery, Eligibility, and Timer Semantics

In revertive mode a recovering member becomes eligible for discretionary receipt after hold-down seconds of continuous usability. A fresh failure cancels its hold-down. A continuously usable member remains eligible. An initial applied configuration is allowed to assign traffic directly; hold-down is a recovery safeguard, not a deployment delay.

For each eligible receiver with unmet policy work, the server starts a monotonic rebalance episode. The episode is cleared when that receiver has no unmet work or loses eligibility. Changes to B, weights, membership, usability, or restoration policy restart episodes against the new target set. A member that becomes eligible later starts its own episode then. rebalance-start is only a wall-clock display of the episode start.

The idle-timer describes a bucket observation interval. The unbalanced-timer describes how long an eligible receiver waits before forced movement to it is permitted. Zero unbalanced-timer MUST always mean that timer-forced movement is disabled, irrespective of activity support. The expiry of a timer does not guarantee completion within a deadline; hardware programming can fail or be delayed.

In non-revertive mode recovery MUST NOT automatically migrate buckets, even if they are idle or a force interval would expire. Safety repair, explicit configuration changes, and an accepted rebalance action remain permitted. Targets and pending work MUST still be observable.

Table 1: Discretionary Automatic Movement in Revertive Mode
Activity capability idle-timer unbalanced-timer Movement allowed after receiver eligibility
Present >0 0 Only buckets with measured idle duration >= idle-timer
Present >0 >0 Idle buckets first; active buckets after receiver episode expires
Present 0 Any All donor buckets are treated as idle immediately
Absent Unavailable 0 No automatic discretionary movement; wait for action or configuration
Absent Unavailable >0 Force eligible movement after receiver episode expires

Liveness safety is outside this table. Absence of activity information MUST NOT be treated as evidence of idleness. An implementation SHOULD show pending work to the operator when the configured timer policy permits indefinite waiting. Timer behavior is consistent with the Linux meaning of a disabled force timer [LINUX], while the additional reference policy is a separate proposed capability.

5. State Publication and Concurrency

boot-id is a UUID for a group incarnation. It MUST change on group deletion and recreation, loss of generation state, or any generation reset. Numeric generations MUST NOT wrap within an incarnation. It need not change for an unrelated device process restart if the group state is fully preserved.

state-generation MUST increase with each coherent publication that changes applied configuration, member usability or eligibility, targets, R, confirmed A, rebalance episode identity, or programming state. applied-generation MUST increase for a forwarding-confirmed change to effective A, including confirmed suppression or removal. reference-generation changes only when R is created or changed. Counter values and elapsed activity ages do not participate in state-generation.

The server MUST publish each revision as an internally consistent control-state view. While programming is outstanding it MUST show pending; after a failure it MUST show failed and last-programming-error. A still-usable confirmed owner MAY be retained when a replacement fails. The server MUST NOT expose an unconfirmed owner as active or increment applied-generation merely because a command was accepted.

A client collecting a group in several reads MUST bracket them with boot-id and state-generation reads. If either changes, the assembled control-state view MUST be discarded and retried. This mechanism covers the defined control state only; activity ages, statistics, and packet observations do not become a simultaneous snapshot. A server that cannot offer consistent revisions cannot claim this behavior by exposing an arbitrary counter.

5.1. Administrative Rebalance

The rebalance action requires boot-id and expected-state-generation. Under the same serialization used for group updates, the server MUST compare both before moving any bucket. A mismatch MUST return a protocol operation error with error-app-tag stale-generation and perform no action movement. A pending or failed programming state MUST reject a new action with error-app-tag group-not-ready until the server has reconciled its forwarding state.

An accepted action bypasses hold-down, idle-timer, and unbalanced-timer. It MUST obey the selected policy, current destination usability, and an optional receiving-member filter. It performs a bounded pass over the current B buckets; it MUST NOT wait indefinitely for an unusable target to recover. An explicitly selected unusable receiver MUST be rejected with error-app-tag member-unusable before any move.

A liveness event MUST interrupt further discretionary action movements so safety repair can run. The action returns interrupted with the number of its already confirmed migrations. A programming failure returns failed and updates operational diagnostics. Otherwise complete means no work for the requested scope remains against the accepted stable state; it does not mean the whole group is balanced if a member filter was used.

The output state-generation identifies the view at action completion. A later asynchronous event can change it before the client reads again. Clients MUST re-read before retrying a stale or interrupted action. Confirmed changes are not rolled back merely because an action is interrupted. The migrated-buckets count excludes independent safety repair and counts no planned or unconfirmed moves.

5.2. Observability and Notifications

The server MUST expose the required control-state leaves in the module, including per-member usable, eligible, pending work, and active counts. target-buckets is present only in quota policy. Reference counts and assignments are present only in reference policy. A server advertising bucket-table MUST return B bucket entries; an entry has no active-member when no effective usable assignment is confirmed.

The model exposes cumulative failure and migration counters with a discontinuity timestamp. Each confirmed migration MUST be counted once in the appropriate failure, idle, or forced category; a manual rebalance is forced even if its selected bucket happens to be idle. Initial assignment, removal, and configuration-driven replacement are not included in those three migration categories. A counter discontinuity MUST update discontinuity-time.

resilient-group-event includes group, event category, boot-id, state-generation, and the count of confirmed migrations for that event. A failure event can report zero migrations while repair is pending. Notifications MAY be coalesced or rate-limited and MUST NOT be treated as a lossless transaction log. Reading the current state is the recovery procedure. Time correlation with buffer telemetry is an operational analysis; it neither proves causality nor creates a dependency between the two protocols.

6. YANG Model and Route Binding

The module uses YANG 1.1 [RFC7950] and NMDA [RFC8342], importing the common types [RFC9911], interfaces [RFC8343], and routing modules [RFC8349]. The three independent features are bucket-activity, bucket-table, and reference-restoration. They advertise server capabilities; restoration/policy and restoration/mode select behavior per group.

Static IPv4 and IPv6 routes gain a next-hop choice that references a configured group. The reference MUST resolve and its address family MUST match the route. Deleting a referenced group is invalid unless the referencing routes are changed in the same valid configuration. Operational RIB references do not require the configuration to remain present indefinitely.

An IPv6 link-local next-hop address requires an outgoing interface. The model uses case-independent prefix checking and no-zone address types; a zone suffix is not an alternative to an explicit interface reference. Other next hops may identify an interface, an address, or both. Recursive resolution is a local implementation matter and MUST not create a forwarding loop or violate the usability rules.

The following tree is generated from the accompanying module and uses [RFC8340] notation. The module organization and contact identify an individual proposal rather than asserting working group ownership. RFC XXXX is an RFC Editor placeholder to be replaced upon publication.

module: ietf-resilient-next-hop-group

  augment /rt:routing:
    +--rw resilient-next-hop-groups
       +--rw group* [name]
          +---x rebalance
          |  +---w input
          |  |  +---w boot-id                      yang:uuid
          |  |  +---w expected-state-generation    uint64
          |  |  +---w member?
          |  |          -> ../../member/id
          |  +--ro output
          |     +--ro result              enumeration
          |     +--ro migrated-buckets    uint32
          |     +--ro state-generation    uint64
          +--rw name                      string
          +--rw description?              string
          +--rw address-family            identityref
          +--rw bucket-count?             uint32
          +--rw idle-timer?               uint32 {bucket-activity}?
          +--rw unbalanced-timer?         uint32
          +--rw restoration
          |  +--rw mode?        identityref
          |  +--rw hold-down?   uint32
          |  +--rw policy?      enumeration
          +--rw member* [id]
          |  +--rw id                       uint32
          |  +--rw outgoing-interface?      if:interface-ref
          |  +--rw next-hop-ipv4-address?
          |  |       inet:ipv4-address-no-zone
          |  +--rw next-hop-ipv6-address?
          |  |       inet:ipv6-address-no-zone
          |  +--rw weight?                  uint32
          |  +--ro usable                   boolean
          |  +--ro rebalance-pending        boolean
          |  +--ro target-buckets?          uint32
          |  +--ro active-buckets           uint32
          |  +--ro reference-buckets?       uint32
          |  |       {reference-restoration}?
          |  +--ro last-transition?         yang:date-and-time
          |  +--ro eligible                 boolean
          |  +--ro rebalance-start?         yang:date-and-time
          +--ro oper-status               enumeration
          +--ro boot-id                   yang:uuid
          +--ro applied-generation        uint64
          +--ro programming-status        enumeration
          +--ro last-programming-error?   string
          +--ro reference-restored?       boolean
          |       {reference-restoration}?
          +--ro usable-members            uint32
          +--ro balanced                  boolean
          +--ro reference-generation?     uint64
          |       {reference-restoration}?
          +--ro bucket* [index] {bucket-table}?
          |  +--ro index               uint32
          |  +--ro active-member?      -> ../../member/id
          |  +--ro reference-member?   -> ../../member/id
          |  |       {reference-restoration}?
          |  +--ro idle-time?          uint32 {bucket-activity}?
          +--ro statistics
          |  +--ro discontinuity-time?    yang:date-and-time
          |  +--ro member-failures?       yang:counter64
          |  +--ro member-restorations?   yang:counter64
          |  +--ro failure-migrations?    yang:counter64
          |  +--ro idle-migrations?       yang:counter64
          |  +--ro forced-migrations?     yang:counter64
          +--ro state-generation          uint64
  augment /rt:routing/rt:control-plane-protocols
            /rt:control-plane-protocol/rt:static-routes/v4ur:ipv4
            /v4ur:route/v4ur:next-hop/v4ur:next-hop-options:
    +--:(resilient-next-hop-group)
       +--rw resilient-next-hop-group    leafref
  augment /rt:routing/rt:control-plane-protocols
            /rt:control-plane-protocol/rt:static-routes/v6ur:ipv6
            /v6ur:route/v6ur:next-hop/v6ur:next-hop-options:
    +--:(resilient-next-hop-group)
       +--rw resilient-next-hop-group    leafref
  augment /rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/rt:next-hop
            /rt:next-hop-options:
    +--:(resilient-next-hop-group)
       +--ro resilient-next-hop-group?   leafref

  notifications:
    +---n resilient-group-event
       +--ro group               leafref
       +--ro event               identityref
       +--ro member?             uint32
       +--ro migrated-buckets    uint32
       +--ro boot-id             yang:uuid
       +--ro state-generation    uint64

7. YANG Module

module "ietf-resilient-next-hop-group" {
  yang-version "1.1";
  namespace
    "urn:ietf:params:xml:ns:yang:ietf-resilient-next-hop-grou"
  + "p";
  prefix "rnhg";
  import "ietf-yang-types" {
    prefix "yang";
    reference
      "RFC 9911: Common YANG Data Types";
  }
  import "ietf-inet-types" {
    prefix "inet";
    reference
      "RFC 9911: Common YANG Data Types";
  }
  import "ietf-interfaces" {
    prefix "if";
    reference
      "RFC 8343: A YANG Data Model for Interface Management";
  }
  import "ietf-routing" {
    prefix "rt";
    reference
      "RFC 8349: A YANG Data Model for Routing Management (NMDA
       Version)";
  }
  import "ietf-ipv4-unicast-routing" {
    prefix "v4ur";
    reference
      "RFC 8349: A YANG Data Model for Routing Management (NMDA
       Version)";
  }
  import "ietf-ipv6-unicast-routing" {
    prefix "v6ur";
    reference
      "RFC 8349: A YANG Data Model for Routing Management (NMDA
       Version)";
  }
  organization
    "Authors of this individual Internet-Draft";
  contact
    "Bohua Xu <xubh15@chinaunicom.cn> Junjie Wang
     <wangjj@centec.com> Discussion: <rtgwg@ietf.org> This
     module has not been adopted by a working group.";
  description
    "Configuration and operational state for resilient next-hop
     groups. This module augments the NMDA routing model with
     bucket policies, forwarding confirmation, and guarded
     administrative rebalancing.

     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 (RFC 2119) (RFC 8174)
     when, and only when, they appear in all capitals, as shown
     here.

     Copyright (c) 2026 IETF Trust and the persons identified as
     authors of the code. All rights reserved.

     Redistribution and use in source and binary forms, with or
     without modification, is permitted pursuant to, and subject
     to the license terms contained in, the Revised BSD License
     set forth in Section 4.c of the IETF Trust's Legal
     Provisions Relating to IETF Documents
     (https://trustee.ietf.org/license-info).

     This version of this YANG module is part of RFC XXXX
     (https://www.rfc-editor.org/info/rfcXXXX); see the RFC
     itself for full legal notices.";
  revision "2026-10-30" {
    description
      "Initial revision.";
    reference
      "RFC XXXX: A YANG Data Model for Resilient Next-Hop Groups";
  }
  feature "bucket-activity" {
    description
      "Supports idle-based migration and per-bucket activity
       measurement. Activity support does not imply that an idle
       bucket has no live transport sessions. Independent of
       bucket-table support.";
  }
  feature "bucket-table" {
    description
      "Indicates that the server exposes the per-bucket
       operational indirection table.";
  }
  feature "reference-restoration" {
    description
      "Supports the reference value of restoration/policy and
       its operational state. Advertising the feature does not
       select that policy. It is independent of bucket-activity
       and bucket-table.";
    reference
      "RFC XXXX: A YANG Data Model for Resilient Next-Hop Groups";
  }
  identity "restoration-mode" {
    description
      "Base identity for member recovery bucket restoration
       modes.";
  }
  identity "revertive" {
    base "restoration-mode";
    description
      "Buckets are automatically eligible for migration back to
       a recovered member after the hold-down timer expires,
       governed by the idle-timer and unbalanced-timer.";
    reference
      "RFC XXXX: A YANG Data Model for Resilient Next-Hop Groups";
  }
  identity "non-revertive" {
    base "restoration-mode";
    description
      "Buckets are not automatically migrated when a member
       recovers; rebalancing occurs only when explicitly invoked
       via the 'rebalance' action.";
    reference
      "RFC XXXX: A YANG Data Model for Resilient Next-Hop Groups";
  }
  identity "group-event" {
    description
      "Base identity for operational lifecycle events reported
       for a resilient next-hop group.";
  }
  identity "member-failure" {
    base "group-event";
    description
      "A member became unusable; repair may still be pending or
       failed.";
  }
  identity "member-restoration" {
    base "group-event";
    description
      "Buckets were migrated to a recovered usable member.";
  }
  identity "rebalance" {
    base "group-event";
    description
      "Buckets were migrated in response to an explicit
       'rebalance' action.";
  }
  identity "configuration-change" {
    base "group-event";
    description
      "Buckets were recomputed and migrated due to an
       administrative configuration change.";
  }
  identity "group-down" {
    base "group-event";
    description
      "All members of the resilient next-hop group became
       unusable.";
  }
  grouping "group-ref" {
    description
      "Reusable reference to a configured resilient next-hop
       group.";
    leaf "resilient-next-hop-group" {
      type "leafref" {
        path
          "/rt:routing/rnhg:resilient-next-hop-groups/rnhg:gr"
        + "oup/rnhg:name";
      }
      mandatory "true";
      description
        "Name of the resilient next-hop group used as the route
         next hop.";
    }
  }
  notification "resilient-group-event" {
    description
      "Reports a published control-state change. Notification
       delivery is not a durable event log; clients read current
       state to recover. boot-id and state-generation identify
       the state publication.";
    leaf "group" {
      type "leafref" {
        path
          "/rt:routing/rnhg:resilient-next-hop-groups/rnhg:gr"
        + "oup/rnhg:name";
        require-instance "false";
      }
      mandatory "true";
      description
        "The resilient next-hop group that experienced the
         event.";
    }
    leaf "event" {
      type "identityref" {
        base "group-event";
      }
      mandatory "true";
      description
        "The category of group lifecycle or migration event.";
    }
    leaf "member" {
      type "uint32";
      description
        "The member identifier associated with the event, when
         applicable.";
    }
    leaf "migrated-buckets" {
      type "uint32";
      mandatory "true";
      description
        "Confirmed owner-to-owner changes in this event, zero if
         none. An event does not assert that all planned repairs
         succeeded.";
    }
    leaf "boot-id" {
      type "yang:uuid";
      mandatory "true";
      description
        "Group incarnation for this event.";
    }
    leaf "state-generation" {
      type "uint64";
      mandatory "true";
      description
        "State publication associated with this event.";
    }
  }
  augment "/rt:routing" {
    description
      "Augments the routing subsystem with resilient next-hop
       groups.";
    container "resilient-next-hop-groups" {
      description
        "Container for resilient next-hop group configurations
         and operational state.";
      list "group" {
        key "name";
        description
          "A resilient group. Configuration nodes in operational
           represent the applied configuration. A server
           publishes state coherently under state-generation. A
           new group incarnation has a new boot-id.";
        action "rebalance" {
          description
            "Force a bounded administrative rebalance. Bypasses
             hold-down, idle and unbalanced timers, while
             preserving current usability and policy
             constraints. The precondition is checked atomically
             before any movement. Liveness changes interrupt
             remaining work. The reply counts confirmed moves
             only; the operation is not rollback-atomic.";
          input {
            leaf "boot-id" {
              type "yang:uuid";
              mandatory "true";
              description
                "Expected group boot-id; mismatch returns
                 stale-generation.";
            }
            leaf "expected-state-generation" {
              type "uint64";
              mandatory "true";
              description
                "Expected state-generation, checked at action
                 acceptance under the group serialization lock.
                 Mismatch returns stale-generation.";
            }
            leaf "member" {
              type "leafref" {
                path "../../member/id";
              }
              description
                "Optional receiving member. If absent, process
                 all usable receivers.";
            }
          }
          output {
            leaf "result" {
              type "enumeration" {
                enum "complete" {
                  description
                    "No eligible work for the requested scope
                     remains.";
                }
                enum "interrupted" {
                  description
                    "A liveness or control-state change stopped
                     further work.";
                }
                enum "failed" {
                  description
                    "Forwarding programming failed; inspect
                     operational state.";
                }
              }
              mandatory "true";
              description
                "Outcome of this accepted action.";
            }
            leaf "migrated-buckets" {
              type "uint32";
              mandatory "true";
              description
                "Number of confirmed owner-to-owner changes made
                 by this action. Safety repair after an
                 interruption is excluded.";
            }
            leaf "state-generation" {
              type "uint64";
              mandatory "true";
              description
                "State-generation at completion of this action
                 reply.";
            }
          }
        }
        leaf "name" {
          type "string" {
            length "1..64";
          }
          description
            "Unique administrative name of the resilient
             next-hop group.";
        }
        leaf "description" {
          type "string";
          description
            "Human-readable description of the group.";
        }
        leaf "address-family" {
          type "identityref" {
            base "rt:address-family";
          }
          must
            "derived-from-or-self(., 'rt:ipv4') or derived-fr"
          + "om-or-self(., 'rt:ipv6')" {
            error-message
              "Only IPv4 and IPv6 unicast groups are defined.";
          }
          mandatory "true";
          description
            "IPv4 or IPv6 unicast address family for this group.";
        }
        leaf "bucket-count" {
          type "uint32" {
            range "1..65535";
          }
          default "128";
          description
            "Number B of logical buckets. Hardware limitations
             are validated before accepting configuration; an
             unsupported size is rejected. Changing B can remap
             all flows and requires a configuration commit.";
          reference
            "RFC XXXX: A YANG Data Model for Resilient Next-Hop
             Groups";
        }
        leaf "idle-timer" {
          if-feature "bucket-activity";
          type "uint32" {
            range "0..65535";
          }
          units "seconds";
          default "120";
          description
            "Seconds without observed packets before a bucket is
             idle. Zero explicitly makes all buckets eligible
             for idle-based movement. Idleness does not imply
             the absence of established sessions.";
          reference
            "RFC XXXX: A YANG Data Model for Resilient Next-Hop
             Groups";
        }
        leaf "unbalanced-timer" {
          type "uint32" {
            range "0..65535";
          }
          units "seconds";
          default "0";
          description
            "Seconds after an eligible receiver develops unmet
             work before automatic forced movement to that
             receiver is permitted. Zero always disables
             timer-forced movement, including when bucket
             activity is unavailable. Uses monotonic elapsed
             time. It is not a guarantee on hardware programming
             completion time.";
          reference
            "RFC XXXX: A YANG Data Model for Resilient Next-Hop
             Groups";
        }
        container "restoration" {
          description
            "Parameters governing bucket restoration when a
             failed member becomes usable again.";
          leaf "mode" {
            type "identityref" {
              base "restoration-mode";
            }
            default "rnhg:revertive";
            description
              "Specifies whether bucket restoration occurs
               automatically (revertive) or only via explicit
               action (non-revertive).";
          }
          leaf "hold-down" {
            type "uint32" {
              range "0..3600";
            }
            units "seconds";
            default "0";
            description
              "Seconds of continuous usability after recovery
               before a member can receive discretionary
               automatic migrations. Does not delay failure
               repair, allocation from a down group,
               configuration application, or an explicit
               rebalance action.";
          }
          leaf "policy" {
            type "enumeration" {
              enum "quota" {
                description
                  "Use integer weighted targets for usable
                   members.";
              }
              enum "reference" {
                if-feature "reference-restoration";
                description
                  "Restore the reference assignment for usable
                   owners.";
              }
            }
            default "quota";
            description
              "Select the per-group restoration policy.";
          }
        }
        list "member" {
          must
            "outgoing-interface or next-hop-ipv4-address or n"
          + "ext-hop-ipv6-address" {
            error-message
              "Each member must specify an outgoing-interface"
            + ", a\nnext-hop address, or both.";
            description
              "Ensures each member identifies a valid forwarding
               next hop.";
          }
          must
            "not(starts-with(translate(next-hop-ipv6-address,"
          + " 'ABCDEF', 'abcdef'), 'fe8') or starts-with(tran"
          + "slate(next-hop-ipv6-address, 'ABCDEF', 'abcdef')"
          + ", 'fe9') or starts-with(translate(next-hop-ipv6-"
          + "address, 'ABCDEF', 'abcdef'), 'fea') or starts-w"
          + "ith(translate(next-hop-ipv6-address, 'ABCDEF', '"
          + "abcdef'), 'feb')) or outgoing-interface" {
            error-message
              "An IPv6 link-local next hop requires outgoing-"
            + "interface.";
            description
              "Case-independent fe80::/10 interface-scope
               validation.";
          }
          key "id";
          min-elements "1";
          description
            "Configured next-hop members of the resilient group.";
          leaf "id" {
            type "uint32";
            description
              "Unique numeric identifier of the member within
               the group.";
          }
          leaf "outgoing-interface" {
            type "if:interface-ref";
            description
              "Outgoing interface for this next-hop member.";
          }
          leaf "next-hop-ipv4-address" {
            when
              "derived-from-or-self(../../address-family, 'rt"
            + ":ipv4')";
            type "inet:ipv4-address-no-zone";
            description
              "IPv4 next-hop address of the member.";
          }
          leaf "next-hop-ipv6-address" {
            when
              "derived-from-or-self(../../address-family, 'rt"
            + ":ipv6')";
            type "inet:ipv6-address-no-zone";
            description
              "IPv6 next-hop address of the member.";
          }
          leaf "weight" {
            type "uint32" {
              range "1..max";
            }
            default "1";
            description
              "Relative load-sharing weight of the member (w_i).
               Target bucket allocation is proportional to w_i.";
          }
          leaf "usable" {
            type "boolean";
            config "false";
            mandatory "true";
            description
              "Indicates whether this member is currently
               resolved and usable for packet forwarding.";
          }
          leaf "rebalance-pending" {
            type "boolean";
            config "false";
            mandatory "true";
            description
              "True if the member is usable and currently holds
               fewer buckets than its target quota (or has
               un-restored reference buckets) awaiting
               migration.";
          }
          leaf "target-buckets" {
            when "../../restoration/policy = 'quota'";
            type "uint32";
            config "false";
            description
              "Current integer target bucket count (T_i) in
               proportional quota mode; omitted when operating
               under reference-restoration policy.";
          }
          leaf "active-buckets" {
            type "uint32";
            config "false";
            mandatory "true";
            description
              "Count of logical buckets with a confirmed usable
               assignment to this member. A suppressed unusable
               hardware entry is not counted.";
          }
          leaf "reference-buckets" {
            when "../../restoration/policy = 'reference'";
            if-feature "reference-restoration";
            type "uint32";
            config "false";
            description
              "Number of buckets whose canonical baseline
               reference assignment R(b) points to this member.";
          }
          leaf "last-transition" {
            type "yang:date-and-time";
            config "false";
            description
              "Timestamp of the most recent change in this
               member's 'usable' state.";
          }
          leaf "eligible" {
            type "boolean";
            config "false";
            mandatory "true";
            description
              "True when usable and the hold-down for automatic
               receipt has expired. Safety repair and
               administrative action may bypass it.";
          }
          leaf "rebalance-start" {
            type "yang:date-and-time";
            config "false";
            description
              "Wall-clock display of the start of the current
               eligible unmet-work episode. Absent without such
               an episode. Timers use monotonic time, not
               subtraction of this timestamp.";
          }
        }
        leaf "oper-status" {
          type "enumeration" {
            enum "up" {
              description
                "Every bucket has a confirmed usable effective
                 owner.";
            }
            enum "down" {
              description
                "No bucket has a confirmed usable effective
                 owner.";
            }
            enum "degraded" {
              description
                "Some, but not all, buckets have confirmed
                 usable owners.";
            }
          }
          config "false";
          mandatory "true";
          description
            "Forwarding availability of the group, derived from
             confirmed effective bucket assignments, not merely
             member liveness.";
        }
        leaf "boot-id" {
          type "yang:uuid";
          config "false";
          mandatory "true";
          description
            "UUID identifying this group incarnation and counter
             epoch. Changes on recreation or any loss/reset of
             generation state. It is not necessarily a device
             reboot identifier.";
        }
        leaf "applied-generation" {
          type "uint64";
          config "false";
          mandatory "true";
          description
            "Counter advanced on each forwarding-confirmed
             change of effective active assignments, including
             confirmed removal or suppression. State-generation
             also advances for the publication. No wrap within
             boot-id. This is not a general configuration
             revision.";
        }
        leaf "programming-status" {
          type "enumeration" {
            enum "ready" {
              description
                "The latest bucket table update has been
                 confirmed by the forwarding plane.";
            }
            enum "pending" {
              description
                "A bucket table update is in progress and not
                 yet confirmed by the forwarding plane.";
            }
            enum "failed" {
              description
                "The most recent forwarding-plane update failed.";
            }
          }
          config "false";
          mandatory "true";
          description
            "Status of hardware/forwarding-plane programming for
             the group.";
        }
        leaf "last-programming-error" {
          type "string";
          config "false";
          description
            "Last programming error. Absent after a subsequent
             successful programming transaction has cleared the
             error.";
        }
        leaf "reference-restored" {
          when "../restoration/policy = 'reference'";
          if-feature "reference-restoration";
          type "boolean";
          config "false";
          description
            "True when at least one member is usable, all
             buckets have confirmed usable assignments, and
             every usable reference owner owns its reference
             buckets. This does not promise intermediate weight
             balance.";
        }
        leaf "usable-members" {
          type "uint32";
          config "false";
          mandatory "true";
          description
            "Current count of usable members in the group.";
        }
        leaf "balanced" {
          type "boolean";
          config "false";
          mandatory "true";
          description
            "True if all buckets are assigned to usable members
             and the active restoration policy's balance
             condition is satisfied.";
        }
        leaf "reference-generation" {
          when "../restoration/policy = 'reference'";
          if-feature "reference-restoration";
          type "uint64";
          config "false";
          description
            "Advanced whenever R is created or changed.
             Usability transitions never change R. Meaningful
             only in reference policy. No wrap within boot-id.";
        }
        list "bucket" {
          if-feature "bucket-table";
          key "index";
          config "false";
          description
            "Operational state of individual hash buckets in the
             group's indirection table.";
          leaf "index" {
            type "uint32";
            must ". < ../../bucket-count" {
              error-message
                "Bucket index must be less than bucket-count.";
            }
            description
              "Zero-based bucket index (0 .. bucket-count - 1).";
          }
          leaf "active-member" {
            type "leafref" {
              path "../../member/id";
              require-instance "false";
            }
            description
              "Confirmed effective usable owner. Absent for a
               confirmed suppressed or unassigned bucket.
               Unknown hardware state is reported as failed
               programming and must not be presented as
               successful repair.";
          }
          leaf "reference-member" {
            when "../../restoration/policy = 'reference'";
            if-feature "reference-restoration";
            type "leafref" {
              path "../../member/id";
              require-instance "false";
            }
            description
              "Canonical reference member ID for this bucket
               (R(b)).";
          }
          leaf "idle-time" {
            if-feature "bucket-activity";
            type "uint32";
            units "seconds";
            description
              "Monotonic seconds since a packet was observed for
               this bucket, saturated at 4294967295. Not covered
               by state-generation; absent when activity data is
               unavailable.";
          }
        }
        container "statistics" {
          config "false";
          description
            "Cumulative operational counters for the resilient
             next-hop group.";
          leaf "discontinuity-time" {
            type "yang:date-and-time";
            description
              "Timestamp of the most recent occasion on which
               one or more counters experienced a discontinuity.";
          }
          leaf "member-failures" {
            type "yang:counter64";
            description
              "Total number of transitions of any member from
               usable to unusable.";
          }
          leaf "member-restorations" {
            type "yang:counter64";
            description
              "Total number of transitions of any member from
               unusable to usable.";
          }
          leaf "failure-migrations" {
            type "yang:counter64";
            description
              "Total number of immediate bucket migrations
               triggered by member failures.";
          }
          leaf "idle-migrations" {
            type "yang:counter64";
            description
              "Total number of idle-based bucket migrations
               performed while the migrated bucket was idle.";
          }
          leaf "forced-migrations" {
            type "yang:counter64";
            description
              "Total number of bucket migrations performed on
               active buckets due to unbalanced-timer expiry or
               explicit 'rebalance' action invocation.";
          }
        }
        leaf "state-generation" {
          type "uint64";
          config "false";
          mandatory "true";
          description
            "Revision of applied configuration, usability,
             eligibility, targets, reference and active
             assignments, and programming state. Increment on
             each coherent publication affecting these values.
             Excludes activity ages and statistics. Does not
             wrap within boot-id.";
        }
      }
    }
  }
  augment
    "/rt:routing/rt:control-plane-protocols/rt:control-plane-"
  + "protocol/rt:static-routes/v4ur:ipv4/v4ur:route/v4ur:next"
  + "-hop/v4ur:next-hop-options" {
    description
      "Enables an IPv4 static route to reference a resilient
       next-hop group.";
    case "resilient-next-hop-group" {
      uses "group-ref" {
        refine "resilient-next-hop-group" {
          must
            "derived-from-or-self(/rt:routing/rnhg:resilient-"
          + "next-hop-groups/rnhg:group[rnhg:name = current()"
          + "]/rnhg:address-family, 'rt:ipv4')" {
            error-message
              "The referenced resilient next-hop group must b"
            + "e\nconfigured for the IPv4 address family.";
          }
        }
      }
    }
  }
  augment
    "/rt:routing/rt:control-plane-protocols/rt:control-plane-"
  + "protocol/rt:static-routes/v6ur:ipv6/v6ur:route/v6ur:next"
  + "-hop/v6ur:next-hop-options" {
    description
      "Enables an IPv6 static route to reference a resilient
       next-hop group.";
    case "resilient-next-hop-group" {
      uses "group-ref" {
        refine "resilient-next-hop-group" {
          must
            "derived-from-or-self(/rt:routing/rnhg:resilient-"
          + "next-hop-groups/rnhg:group[rnhg:name = current()"
          + "]/rnhg:address-family, 'rt:ipv6')" {
            error-message
              "The referenced resilient next-hop group must b"
            + "e\nconfigured for the IPv6 address family.";
          }
        }
      }
    }
  }
  augment
    "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/rt:next-ho"
  + "p/rt:next-hop-options" {
    description
      "Exposes the resilient next-hop group bound to an active
       route in the Routing Information Base (RIB).";
    case "resilient-next-hop-group" {
      leaf "resilient-next-hop-group" {
        type "leafref" {
          path
            "/rt:routing/rnhg:resilient-next-hop-groups/rnhg:"
          + "group/rnhg:name";
          require-instance "false";
        }
        description
          "Name of the resilient next-hop group used by this
           active RIB route.";
      }
    }
  }
}

8. Worked Examples and Interoperability Checks

8.1. Integer Targets

For B=5 and two usable equal-weight members, valid targets are (3,2) or (2,3). If A has counts (4,1) and the selected targets are (3,2), one permitted donor-to-receiver migration reaches the target. Testing against real shares (2.5,2.5) instead of fixed integer targets can incorrectly keep the group pending or cause oscillation. The wire model exposes the selected integer values.

8.2. Restoration to a Reference Assignment

Table 2: Reference Restoration with Two Failures
Step Reference R Active A
Initial [1,2,3,1,2,3] [1,2,3,1,2,3]
1 fails Unchanged [2,2,3,2,2,3]
2 fails Unchanged [3,3,3,3,3,3]
1 recovers and its work completes Unchanged [1,3,3,1,3,3]
2 recovers and its work completes Unchanged [1,2,3,1,2,3]

Temporary holders in this example are choices, not a prescribed allocation algorithm. Reversing recovery order also reaches R when the same conditional convergence requirements hold. Recovery of a reference owner moves only its reference buckets; it does not impose quota balance on the temporary holders.

8.3. Waiting and Stale Actions

With no bucket-activity capability and unbalanced-timer=0, a recovered member can remain pending in revertive mode. This is intentional: the server has neither evidence of idleness nor permission for timer-forced movement. Setting a positive force timer or invoking a guarded action can progress the work.

If a client reads state-generation=8 and a member becomes unusable without any mapping update yet confirmed, state-generation still advances for the liveness change. An action carrying 8 is rejected. Comparing only applied-generation would miss this change, which is why the action precondition uses state-generation.

8.4. Example IPv4 Static Route Configuration

This JSON configuration is validated against the module and its dependencies. It uses the default quota policy. The address and interface values are illustrative.

{
 "ietf-interfaces:interfaces": {
  "interface": [
   {
    "name": "eth0",
    "type": "iana-if-type:ethernetCsmacd"
   }
  ]
 },
 "ietf-routing:routing": {
  "ietf-resilient-next-hop-group:resilient-next-hop-groups": {
   "group": [
    {
     "name": "fabric",
     "address-family": "ietf-routing:ipv4",
     "bucket-count": 8,
     "member": [
      {
       "id": 1,
       "next-hop-ipv4-address": "192.0.2.1",
       "outgoing-interface": "eth0"
      }
     ]
    }
   ]
  },
  "control-plane-protocols": {
   "control-plane-protocol": [
    {
     "type": "ietf-routing:static",
     "name": "static",
     "static-routes": {
      "ietf-ipv4-unicast-routing:ipv4": {
       "route": [
        {
         "destination-prefix": "192.0.2.0/24",
         "next-hop": {
          "ietf-resilient-next-hop-group:resilient-next-hop-group":
  "fabric"
         }
        }
       ]
      }
     }
    }
   ]
  }
 }
}

8.5. Implementation Review Checklist

Interoperability testing SHOULD exercise all eight feature combinations, both restoration modes, the timer combinations in Table 1, fractional weighted shares, B smaller than member count, all-members-down recovery, hardware rejection after partial programming, action interruption, configuration changes during pending work, counter discontinuity, and group recreation with the same name.

Schema validation SHOULD cover address-family mismatches, unresolved route references, uppercase and lowercase IPv6 link-local addresses without an interface, invalid weights and bucket counts, and reference policy selection when the feature is absent. Behavior simulations and schema tests do not substitute for two independent forwarding implementations.

9. Operational Considerations

Idle intervals should be chosen with expected traffic gaps and session affinity in mind. No finite idle interval proves that a stateful service has forgotten a flow. Non-revertive mode can avoid automatic recovery movements, but cannot prevent required failure repair or administrator-initiated changes.

Reference policy provides repeatable placement within one reference generation. It can require more movements than a policy that merely reaches current quotas, and can temporarily produce uneven survivor allocation. Operators should select it only when restoration to the recorded baseline matters.

Resilient local assignment does not guarantee transport ordering, losslessness, or availability of state at a replacement middlebox. A changed path can have different latency; even a flow that keeps its local next hop can encounter changes downstream. These limits SHOULD be disclosed in implementation documentation.

10. Security Considerations

This section follows the YANG security guidance in [RFC9907]. The module is intended for use with authenticated management protocols such as NETCONF [RFC6241] and RESTCONF [RFC8040], over their secure transport mappings. Deployments MUST authenticate peers and protect confidentiality and integrity. The Network Configuration Access Control Model [RFC8341] can restrict both data access and operations.

All configuration under /routing/resilient-next-hop-groups is sensitive. Changing membership or next-hop addresses can redirect traffic; changing weights, bucket-count, or policy can move active flows; setting idle-timer to zero or reducing unbalanced-timer can permit disruptive movement. These writes MUST be restricted to authorized administrators.

The rebalance action bypasses recovery timers and can move active traffic. Authorization MUST be checked independently of its generation precondition. Repeated reads, rejected actions, notifications, and large bucket-table retrievals can consume resources; servers MUST impose suitable request, work, and notification limits. A generation token prevents a stale decision from executing; it is not a secret or an authorization credential.

Operational next hops, interface bindings, bucket assignments, member transitions, and error diagnostics reveal topology and traffic-handling policy. Access SHOULD be limited to authorized consumers. Implementations SHOULD avoid exposing secrets or unnecessary internal details in diagnostic strings. Malicious liveness inputs can induce repeated repair, so liveness sources and their authentication require deployment-specific protection.

11. IANA Considerations

11.1. XML Namespace

IANA is requested to register urn:ietf:params:xml:ns:yang:ietf-resilient-next-hop-group in the IETF XML Registry [RFC3688]. Registrant Contact: The IESG. XML: N/A; the requested URI is an XML namespace.

11.2. YANG Module

IANA is requested to register the module under the YANG Module Names registry [RFC6020]: Name: ietf-resilient-next-hop-group; Namespace: urn:ietf:params:xml:ns:yang:ietf-resilient-next-hop-group; Prefix: rnhg; Reference: RFC XXXX. No assignment or working group ownership is implied before approval.

12. 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/info/rfc2119>.
[RFC3688]
Mealling, M., "The IETF XML Registry", BCP 81, RFC 3688, DOI 10.17487/RFC3688, , <https://www.rfc-editor.org/info/rfc3688>.
[RFC6020]
Bjorklund, M., Ed., "YANG - A Data Modeling Language for the Network Configuration Protocol (NETCONF)", RFC 6020, DOI 10.17487/RFC6020, , <https://www.rfc-editor.org/info/rfc6020>.
[RFC6241]
Enns, R., Ed., Bjorklund, M., Ed., Schoenwaelder, J., Ed., and A. Bierman, Ed., "Network Configuration Protocol (NETCONF)", RFC 6241, DOI 10.17487/RFC6241, , <https://www.rfc-editor.org/info/rfc6241>.
[RFC7950]
Bjorklund, M., Ed., "The YANG 1.1 Data Modeling Language", RFC 7950, DOI 10.17487/RFC7950, , <https://www.rfc-editor.org/info/rfc7950>.
[RFC8040]
Bierman, A., Bjorklund, M., and K. Watsen, "RESTCONF Protocol", RFC 8040, DOI 10.17487/RFC8040, , <https://www.rfc-editor.org/info/rfc8040>.
[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/info/rfc8174>.
[RFC8341]
Bierman, A. and M. Bjorklund, "Network Configuration Access Control Model", STD 91, RFC 8341, DOI 10.17487/RFC8341, , <https://www.rfc-editor.org/info/rfc8341>.
[RFC8342]
Bjorklund, M., Schoenwaelder, J., Shafer, P., Watsen, K., and R. Wilton, "Network Management Datastore Architecture (NMDA)", RFC 8342, DOI 10.17487/RFC8342, , <https://www.rfc-editor.org/info/rfc8342>.
[RFC8343]
Bjorklund, M., "A YANG Data Model for Interface Management", RFC 8343, DOI 10.17487/RFC8343, , <https://www.rfc-editor.org/info/rfc8343>.
[RFC8349]
Lhotka, L., Lindem, A., and Y. Qu, "A YANG Data Model for Routing Management (NMDA Version)", RFC 8349, DOI 10.17487/RFC8349, , <https://www.rfc-editor.org/info/rfc8349>.
[RFC9911]
Schoenwaelder, J., Ed., "Common YANG Data Types", RFC 9911, DOI 10.17487/RFC9911, , <https://www.rfc-editor.org/info/rfc9911>.

13. Informative References

[GRIBI]
OpenConfig, "gRIBI Service Definition", <https://github.com/openconfig/gribi/blob/master/v1/proto/service/gribi.proto>.
[I-D.ietf-bess-weighted-hrw]
Mohanty, S. R., Mishra, M. P., Lindem, A., Sajassi, A., and J. Drake, "Weighted HRW and its applications", Work in Progress, Internet-Draft, draft-ietf-bess-weighted-hrw-02, , <https://datatracker.ietf.org/doc/html/draft-ietf-bess-weighted-hrw-02>.
[LINUX]
Linux contributors, "Resilient Next-hop Groups", <https://docs.kernel.org/networking/nexthop-group-resilient.html>.
[OC-AFT]
OpenConfig, "OpenConfig Abstract Forwarding Table Common Model, revision 2026-10-04", <https://github.com/openconfig/public/blob/master/release/models/aft/openconfig-aft-common.yang>.
[OC-STATIC]
OpenConfig, "OpenConfig Network Instance Static Model, revision 2026-05-12", <https://github.com/openconfig/public/blob/master/release/models/network-instance/openconfig-network-instance-static.yang>.
[RFC2992]
Hopps, C., "Analysis of an Equal-Cost Multi-Path Algorithm", RFC 2992, DOI 10.17487/RFC2992, , <https://www.rfc-editor.org/info/rfc2992>.
[RFC8340]
Bjorklund, M. and L. Berger, Ed., "YANG Tree Diagrams", BCP 215, RFC 8340, DOI 10.17487/RFC8340, , <https://www.rfc-editor.org/info/rfc8340>.
[RFC9907]
Bierman, A., Boucadair, M., Ed., and Q. Wu, "Guidelines for Authors and Reviewers of Documents Containing YANG Data Models", BCP 216, RFC 9907, DOI 10.17487/RFC9907, , <https://www.rfc-editor.org/info/rfc9907>.
[SAI-NHG]
Open Compute Project, "Switch Abstraction Interface: Next Hop Group API", <https://github.com/opencomputeproject/SAI/blob/master/inc/sainexthopgroup.h>.

Authors' Addresses

Bohua Xu
China Unicom
Beijing
100000
China
Chang Liu
China Unicom
Beijing
100000
China
Yu Zhou
China Unicom
Beijing
100000
China
Chenyang Wen
China Unicom
Beijing
100000
China
Wei Cheng
Centec
Suzhou
215000
China
Junjie Wang
Centec
Suzhou
215000
China
Mingyu Wu
Centec
Suzhou
215000
China
Yongtao Yang
Centec
Suzhou
215000
China