diff --git a/Cargo.lock b/Cargo.lock
index 6ef6caf3..8c66e5ff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2446,10 +2446,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
-name = "libyang4-sys"
-version = "0.1.1"
+name = "libyang5-sys"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21e9b33465f2c872155d44f318b72efac73dd4063690a98e73a31d09552f0f2"
+checksum = "8d2af2bd5e6a586aee12e07735a223e232969686ac4ac5b7af639e555ec4a1e1"
dependencies = [
"cmake",
"pkg-config",
@@ -2799,7 +2799,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"tracing-test",
- "yang4",
+ "yang5",
]
[[package]]
@@ -3054,12 +3054,13 @@ dependencies = [
"rustc-hash",
"serde",
"serde_json",
+ "strum 0.28.0",
"strum_macros 0.28.0",
"tempfile",
"tokio",
"tracing",
"tracing-test",
- "yang4",
+ "yang5",
]
[[package]]
@@ -6568,14 +6569,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
-name = "yang4"
-version = "0.1.0"
+name = "yang5"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42f331187c087be4b9e5c823bd48ceec13b3628192278b19ea86b662764fd2d7"
+checksum = "1da35149085196bd35c11637c084c0723157c05d97766033cec6d90db535bd19"
dependencies = [
"bitflags",
"libc",
- "libyang4-sys",
+ "libyang5-sys",
"log",
"num-derive",
"num-traits",
diff --git a/Cargo.toml b/Cargo.toml
index 483e3fcc..5964854f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -84,6 +84,7 @@ anyhow = { version = "1" }
serde_with = { version = "3.20" }
indexmap = { version = "2.14" }
apache-avro = { version = "0.21" }
+# TODO: clean-up and consolidate towards schema-registry-client
schema_registry_converter = { version = "4.9", default-features = false }
rdkafka = { version = "0.39", default-features = false }
tracing-test = { version = "0.2" }
@@ -102,7 +103,7 @@ sysinfo = { version = "0.39", default-features = false }
shadow-rs = { version = "2.0", default-features = false }
smallvec = { version = "1.15" }
rustc-hash = { version = "2.1" }
-yang4 = { version = "0.1", features = ["bundled"] }
+yang5 = { version = "0.2", features = ["bundled"] }
tempfile = { version = "3.27" }
quick-xml = { version = "0.40" }
russh = { version = "0.61" }
diff --git a/assets/yang/ietf-interfaces/modules/ietf-distributed-notif@2026-04-13.yang b/assets/yang/ietf-interfaces/modules/ietf-distributed-notif@2026-04-13.yang
new file mode 100644
index 00000000..70ce99d2
--- /dev/null
+++ b/assets/yang/ietf-interfaces/modules/ietf-distributed-notif@2026-04-13.yang
@@ -0,0 +1,127 @@
+module ietf-distributed-notif {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-distributed-notif";
+ prefix dn;
+
+ import ietf-subscribed-notifications {
+ prefix sn;
+ reference
+ "RFC 8639: Subscription to YANG Notifications";
+ }
+ import ietf-yang-push {
+ prefix yp;
+ reference
+ "RFC 8641: Subscription to YANG Notifications for Datastore
+ Updates";
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web:
+ WG List:
+
+ Authors: Guangying Zheng
+
+ Tianran Zhou
+
+ Thomas Graf
+
+ Pierre Francois
+
+ Eric Voit
+ ";
+ description
+ "Defines augmentation for ietf-subscribed-notifications to
+ enable the distributed publication with single subscription.
+
+ 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).
+
+ All revisions of IETF and IANA published modules can be found
+ at the YANG Parameters registry
+ (https://www.iana.org/assignments/yang-parameters).
+
+ This version of this YANG module is part of RFC XXXX; see
+ the RFC itself for full legal notices.";
+
+ revision 2026-04-13 {
+ description
+ "Initial version";
+ reference
+ "RFC XXXX: Subscription to Notifications in a Distributed
+ Architecture";
+ }
+
+ grouping message-publisher-id {
+ description
+ "Provides a reusable message-publisher-id leaf.";
+ leaf message-publisher-id {
+ type uint32;
+ config false;
+ description
+ "Identifies the Component software process which publishes
+ notification messages (e.g., processor 1 on line card 1). This
+ field is used to notify the receiver which publisher process
+ published which message. The identifier is locally unique to
+ the Network Node.";
+ }
+ }
+
+ grouping message-publisher-ids {
+ description
+ "Provides a reusable message-publisher-id-list leaf-list.";
+ leaf-list message-publisher-id {
+ type uint32;
+ config false;
+ description
+ "Identifies the Component software process which publishes
+ notification messages (e.g., processor 1 on line card 1). This
+ field is used to notify the receiver which publisher processes
+ are going to publish. The identifiers are locally unique to
+ the Network Node.";
+ }
+ }
+
+ augment "/sn:subscriptions/sn:subscription" {
+ description
+ "This augmentation allows the Message
+ Publisher ID to be exposed for a subscription.";
+ uses message-publisher-ids;
+ }
+
+ augment "/sn:subscription-started" {
+ description
+ "This augmentation adds the Message Publisher ID to the
+ subscription-started subscription change notifications.";
+ uses message-publisher-ids;
+ }
+
+ augment "/sn:subscription-modified" {
+ description
+ "This augmentation adds the Message Publisher ID to the
+ subscription-modified subscription change notifications.";
+ uses message-publisher-ids;
+ }
+
+ augment "/yp:push-update" {
+ description
+ "This augmentation adds the Message Publisher ID in the
+ push-update notification.";
+ uses message-publisher-id;
+ }
+
+ augment "/yp:push-change-update" {
+ description
+ "This augmentation adds the Message Publisher ID in the
+ push-change-update notification.";
+ uses message-publisher-id;
+ }
+}
diff --git a/assets/yang/ietf-interfaces/modules/ietf-interfaces@2014-05-08.yang b/assets/yang/ietf-interfaces/modules/ietf-interfaces@2018-02-20.yang
similarity index 52%
rename from assets/yang/ietf-interfaces/modules/ietf-interfaces@2014-05-08.yang
rename to assets/yang/ietf-interfaces/modules/ietf-interfaces@2018-02-20.yang
index ad64425f..f66c205c 100644
--- a/assets/yang/ietf-interfaces/modules/ietf-interfaces@2014-05-08.yang
+++ b/assets/yang/ietf-interfaces/modules/ietf-interfaces@2018-02-20.yang
@@ -1,5 +1,5 @@
module ietf-interfaces {
-
+ yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces";
prefix if;
@@ -8,18 +8,12 @@ module ietf-interfaces {
}
organization
- "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+ "IETF NETMOD (Network Modeling) Working Group";
contact
- "WG Web:
+ "WG Web:
WG List:
- WG Chair: Thomas Nadeau
-
-
- WG Chair: Juergen Schoenwaelder
-
-
Editor: Martin Bjorklund
";
@@ -27,7 +21,7 @@ module ietf-interfaces {
"This module contains a collection of YANG definitions for
managing network interfaces.
- Copyright (c) 2014 IETF Trust and the persons identified as
+ Copyright (c) 2018 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or
@@ -35,11 +29,18 @@ module ietf-interfaces {
to the license terms contained in, the Simplified BSD License
set forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
- (http://trustee.ietf.org/license-info).
+ (https://trustee.ietf.org/license-info).
- This version of this YANG module is part of RFC 7223; see
+ This version of this YANG module is part of RFC 8343; see
the RFC itself for full legal notices.";
+ revision 2018-02-20 {
+ description
+ "Updated to support NMDA.";
+ reference
+ "RFC 8343: A YANG Data Model for Interface Management";
+ }
+
revision 2014-05-08 {
description
"Initial revision.";
@@ -57,16 +58,7 @@ module ietf-interfaces {
}
description
"This type is used by data models that need to reference
- configured interfaces.";
- }
-
- typedef interface-state-ref {
- type leafref {
- path "/if:interfaces-state/if:interface/if:name";
- }
- description
- "This type is used by data models that need to reference
- the operationally present interfaces.";
+ interfaces.";
}
/*
@@ -95,7 +87,6 @@ module ietf-interfaces {
possible to configure an interface whose physical interface
hardware is not present on the device.";
}
-
feature if-mib {
description
"This feature indicates that the device implements
@@ -105,29 +96,32 @@ module ietf-interfaces {
}
/*
- * Configuration data nodes
+ * Data nodes
*/
container interfaces {
description
- "Interface configuration parameters.";
+ "Interface parameters.";
list interface {
key "name";
description
- "The list of configured interfaces on the device.
+ "The list of interfaces on the device.
- The operational state of an interface is available in the
- /interfaces-state/interface list. If the configuration of a
+ The status of an interface is available in this list in the
+ operational state. If the configuration of a
system-controlled interface cannot be used by the system
(e.g., the interface hardware present does not match the
interface type), then the configuration is not applied to
- the system-controlled interface shown in the
- /interfaces-state/interface list. If the configuration
- of a user-controlled interface cannot be used by the system,
- the configured interface is not instantiated in the
- /interfaces-state/interface list.";
+ the system-controlled interface shown in the operational
+ state. If the configuration of a user-controlled interface
+ cannot be used by the system, the configured interface is
+ not instantiated in the operational state.
+
+ System-controlled interfaces created by the system are
+ always present in this list in the operational state,
+ whether or not they are configured.";
leaf name {
type string;
@@ -137,18 +131,16 @@ module ietf-interfaces {
A device MAY restrict the allowed values for this leaf,
possibly depending on the type of the interface.
For system-controlled interfaces, this leaf is the
- device-specific name of the interface. The 'config false'
- list /interfaces-state/interface contains the currently
- existing interfaces on the device.
+ device-specific name of the interface.
If a client tries to create configuration for a
system-controlled interface that is not present in the
- /interfaces-state/interface list, the server MAY reject
- the request if the implementation does not support
- pre-provisioning of interfaces or if the name refers to
- an interface that can never exist in the system. A
- NETCONF server MUST reply with an rpc-error with the
- error-tag 'invalid-value' in this case.
+ operational state, the server MAY reject the request if
+ the implementation does not support pre-provisioning of
+ interfaces or if the name refers to an interface that can
+ never exist in the system. A Network Configuration
+ Protocol (NETCONF) server MUST reply with an rpc-error
+ with the error-tag 'invalid-value' in this case.
If the device supports pre-provisioning of interface
configuration, the 'pre-provisioning' feature is
@@ -159,7 +151,15 @@ module ietf-interfaces {
When a configured user-controlled interface is created by
the system, it is instantiated with the same name in the
- /interface-state/interface list.";
+ operational state.
+
+ A server implementation MAY map this leaf to the ifName
+ MIB object. Such an implementation needs to use some
+ mechanism to handle the differences in size and characters
+ allowed between this leaf and ifName. The definition of
+ such a mechanism is outside the scope of this document.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifName";
}
leaf description {
@@ -176,21 +176,7 @@ module ietf-interfaces {
Since ifAlias is defined to be stored in non-volatile
storage, the MIB implementation MUST map ifAlias to the
value of 'description' in the persistently stored
- datastore.
-
- Specifically, if the device supports ':startup', when
- ifAlias is read the device MUST return the value of
- 'description' in the 'startup' datastore, and when it is
- written, it MUST be written to the 'running' and 'startup'
- datastores. Note that it is up to the implementation to
-
- decide whether to modify this single leaf in 'startup' or
- perform an implicit copy-config from 'running' to
- 'startup'.
-
- If the device does not support ':startup', ifAlias MUST
- be mapped to the 'description' leaf in the 'running'
- datastore.";
+ configuration.";
reference
"RFC 2863: The Interfaces Group MIB - ifAlias";
}
@@ -226,15 +212,12 @@ module ietf-interfaces {
interface.
Systems that implement the IF-MIB use the value of this
- leaf in the 'running' datastore to set
+ leaf in the intended configuration to set
IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry
has been initialized, as described in RFC 2863.
-
-
- Changes in this leaf in the 'running' datastore are
- reflected in ifAdminStatus, but if ifAdminStatus is
- changed over SNMP, this leaf is not affected.";
+ Changes in this leaf in the intended configuration are
+ reflected in ifAdminStatus.";
reference
"RFC 2863: The Interfaces Group MIB - ifAdminStatus";
}
@@ -244,9 +227,15 @@ module ietf-interfaces {
type enumeration {
enum enabled {
value 1;
+ description
+ "The device will generate linkUp/linkDown SNMP
+ notifications for this interface.";
}
enum disabled {
value 2;
+ description
+ "The device will not generate linkUp/linkDown SNMP
+ notifications for this interface.";
}
}
description
@@ -261,34 +250,443 @@ module ietf-interfaces {
"RFC 2863: The Interfaces Group MIB -
ifLinkUpDownTrapEnable";
}
+
+ leaf admin-status {
+ if-feature if-mib;
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+ description
+ "Not ready to pass packets and not in some test mode.";
+ }
+ enum testing {
+ value 3;
+ description
+ "In some test mode.";
+ }
+ }
+ config false;
+ mandatory true;
+ description
+ "The desired state of the interface.
+
+ This leaf has the same read semantics as ifAdminStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+ }
+
+ leaf oper-status {
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+
+ description
+ "The interface does not pass any packets.";
+ }
+ enum testing {
+ value 3;
+ description
+ "In some test mode. No operational packets can
+ be passed.";
+ }
+ enum unknown {
+ value 4;
+ description
+ "Status cannot be determined for some reason.";
+ }
+ enum dormant {
+ value 5;
+ description
+ "Waiting for some external event.";
+ }
+ enum not-present {
+ value 6;
+ description
+ "Some component (typically hardware) is missing.";
+ }
+ enum lower-layer-down {
+ value 7;
+ description
+ "Down due to state of lower-layer interface(s).";
+ }
+ }
+ config false;
+ mandatory true;
+ description
+ "The current operational state of the interface.
+
+ This leaf has the same semantics as ifOperStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOperStatus";
+ }
+
+ leaf last-change {
+ type yang:date-and-time;
+ config false;
+ description
+ "The time the interface entered its current operational
+ state. If the current state was entered prior to the
+ last re-initialization of the local network management
+ subsystem, then this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifLastChange";
+ }
+
+ leaf if-index {
+ if-feature if-mib;
+ type int32 {
+ range "1..2147483647";
+ }
+ config false;
+ mandatory true;
+ description
+ "The ifIndex value for the ifEntry represented by this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifIndex";
+ }
+
+ leaf phys-address {
+ type yang:phys-address;
+ config false;
+ description
+ "The interface's address at its protocol sub-layer. For
+ example, for an 802.x interface, this object normally
+ contains a Media Access Control (MAC) address. The
+ interface's media-specific modules must define the bit
+ and byte ordering and the format of the value of this
+ object. For interfaces that do not have such an address
+ (e.g., a serial line), this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifPhysAddress";
+ }
+
+ leaf-list higher-layer-if {
+ type interface-ref;
+ config false;
+ description
+ "A list of references to interfaces layered on top of this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf-list lower-layer-if {
+ type interface-ref;
+ config false;
+
+ description
+ "A list of references to interfaces layered underneath this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf speed {
+ type yang:gauge64;
+ units "bits/second";
+ config false;
+ description
+ "An estimate of the interface's current bandwidth in bits
+ per second. For interfaces that do not vary in
+ bandwidth or for those where no accurate estimation can
+ be made, this node should contain the nominal bandwidth.
+ For interfaces that have no concept of bandwidth, this
+ node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifSpeed, ifHighSpeed";
+ }
+
+ container statistics {
+ config false;
+ description
+ "A collection of interface-related statistics objects.";
+
+ leaf discontinuity-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The time on the most recent occasion at which any one or
+ more of this interface's counters suffered a
+ discontinuity. If no such discontinuities have occurred
+ since the last re-initialization of the local management
+ subsystem, then this node contains the time the local
+ management subsystem re-initialized itself.";
+ }
+
+ leaf in-octets {
+ type yang:counter64;
+ description
+ "The total number of octets received on the interface,
+ including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInOctets";
+ }
+
+ leaf in-unicast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were not addressed to a
+ multicast or broadcast address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts";
+ }
+
+ leaf in-broadcast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a broadcast
+ address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInBroadcastPkts";
+ }
+
+ leaf in-multicast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a multicast
+ address at this sub-layer. For a MAC-layer protocol,
+ this includes both Group and Functional addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInMulticastPkts";
+ }
+
+ leaf in-discards {
+ type yang:counter32;
+ description
+ "The number of inbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being deliverable to a higher-layer
+ protocol. One possible reason for discarding such a
+ packet could be to free up buffer space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInDiscards";
+ }
+
+ leaf in-errors {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of inbound
+ packets that contained errors preventing them from being
+ deliverable to a higher-layer protocol. For character-
+ oriented or fixed-length interfaces, the number of
+ inbound transmission units that contained errors
+ preventing them from being deliverable to a higher-layer
+ protocol.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInErrors";
+ }
+
+ leaf in-unknown-protos {
+ type yang:counter32;
+
+ description
+ "For packet-oriented interfaces, the number of packets
+ received via the interface that were discarded because
+ of an unknown or unsupported protocol. For
+ character-oriented or fixed-length interfaces that
+ support protocol multiplexing, the number of
+ transmission units received via the interface that were
+ discarded because of an unknown or unsupported protocol.
+ For any interface that does not support protocol
+ multiplexing, this counter is not present.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos";
+ }
+
+ leaf out-octets {
+ type yang:counter64;
+ description
+ "The total number of octets transmitted out of the
+ interface, including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets";
+ }
+
+ leaf out-unicast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were not addressed
+ to a multicast or broadcast address at this sub-layer,
+ including those that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts";
+ }
+
+ leaf out-broadcast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were addressed to a
+ broadcast address at this sub-layer, including those
+ that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutBroadcastPkts";
+ }
+
+ leaf out-multicast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were addressed to a
+ multicast address at this sub-layer, including those
+ that were discarded or not sent. For a MAC-layer
+ protocol, this includes both Group and Functional
+ addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutMulticastPkts";
+ }
+
+ leaf out-discards {
+ type yang:counter32;
+ description
+ "The number of outbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being transmitted. One possible reason
+ for discarding such a packet could be to free up buffer
+ space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutDiscards";
+ }
+
+ leaf out-errors {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of outbound
+ packets that could not be transmitted because of errors.
+ For character-oriented or fixed-length interfaces, the
+ number of outbound transmission units that could not be
+ transmitted because of errors.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutErrors";
+ }
+ }
+
+ }
+ }
+
+ /*
+ * Legacy typedefs
+ */
+
+ typedef interface-state-ref {
+ type leafref {
+ path "/if:interfaces-state/if:interface/if:name";
}
+ status deprecated;
+ description
+ "This type is used by data models that need to reference
+ the operationally present interfaces.";
}
/*
- * Operational state data nodes
+ * Legacy operational state data nodes
*/
container interfaces-state {
config false;
+ status deprecated;
description
"Data nodes for the operational state of interfaces.";
list interface {
key "name";
-
-
-
-
+ status deprecated;
description
"The list of interfaces on the device.
System-controlled interfaces created by the system are
- always present in this list, whether they are configured or
- not.";
+ always present in this list, whether or not they are
+ configured.";
leaf name {
type string;
+ status deprecated;
description
"The name of the interface.
@@ -306,6 +704,7 @@ module ietf-interfaces {
base interface-type;
}
mandatory true;
+ status deprecated;
description
"The type of the interface.";
reference
@@ -325,9 +724,6 @@ module ietf-interfaces {
description
"Not ready to pass packets and not in some test mode.";
}
-
-
-
enum testing {
value 3;
description
@@ -335,6 +731,7 @@ module ietf-interfaces {
}
}
mandatory true;
+ status deprecated;
description
"The desired state of the interface.
@@ -383,6 +780,7 @@ module ietf-interfaces {
}
}
mandatory true;
+ status deprecated;
description
"The current operational state of the interface.
@@ -393,6 +791,7 @@ module ietf-interfaces {
leaf last-change {
type yang:date-and-time;
+ status deprecated;
description
"The time the interface entered its current operational
state. If the current state was entered prior to the
@@ -408,22 +807,23 @@ module ietf-interfaces {
range "1..2147483647";
}
mandatory true;
+ status deprecated;
description
"The ifIndex value for the ifEntry represented by this
interface.";
+
reference
"RFC 2863: The Interfaces Group MIB - ifIndex";
}
leaf phys-address {
type yang:phys-address;
+ status deprecated;
description
"The interface's address at its protocol sub-layer. For
example, for an 802.x interface, this object normally
contains a Media Access Control (MAC) address. The
interface's media-specific modules must define the bit
-
-
and byte ordering and the format of the value of this
object. For interfaces that do not have such an address
(e.g., a serial line), this node is not present.";
@@ -433,6 +833,7 @@ module ietf-interfaces {
leaf-list higher-layer-if {
type interface-state-ref;
+ status deprecated;
description
"A list of references to interfaces layered on top of this
interface.";
@@ -442,6 +843,7 @@ module ietf-interfaces {
leaf-list lower-layer-if {
type interface-state-ref;
+ status deprecated;
description
"A list of references to interfaces layered underneath this
interface.";
@@ -452,10 +854,12 @@ module ietf-interfaces {
leaf speed {
type yang:gauge64;
units "bits/second";
+ status deprecated;
description
"An estimate of the interface's current bandwidth in bits
per second. For interfaces that do not vary in
bandwidth or for those where no accurate estimation can
+
be made, this node should contain the nominal bandwidth.
For interfaces that have no concept of bandwidth, this
node is not present.";
@@ -464,21 +868,15 @@ module ietf-interfaces {
ifSpeed, ifHighSpeed";
}
-
-
-
-
-
-
-
-
container statistics {
+ status deprecated;
description
"A collection of interface-related statistics objects.";
leaf discontinuity-time {
type yang:date-and-time;
mandatory true;
+ status deprecated;
description
"The time on the most recent occasion at which any one or
more of this interface's counters suffered a
@@ -490,12 +888,13 @@ module ietf-interfaces {
leaf in-octets {
type yang:counter64;
+ status deprecated;
description
"The total number of octets received on the interface,
including framing characters.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -504,31 +903,29 @@ module ietf-interfaces {
leaf in-unicast-pkts {
type yang:counter64;
+ status deprecated;
description
"The number of packets, delivered by this sub-layer to a
higher (sub-)layer, that were not addressed to a
multicast or broadcast address at this sub-layer.
-
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
"RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts";
}
-
-
-
leaf in-broadcast-pkts {
type yang:counter64;
+ status deprecated;
description
"The number of packets, delivered by this sub-layer to a
higher (sub-)layer, that were addressed to a broadcast
address at this sub-layer.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -538,6 +935,7 @@ module ietf-interfaces {
leaf in-multicast-pkts {
type yang:counter64;
+ status deprecated;
description
"The number of packets, delivered by this sub-layer to a
higher (sub-)layer, that were addressed to a multicast
@@ -545,7 +943,7 @@ module ietf-interfaces {
this includes both Group and Functional addresses.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -555,6 +953,8 @@ module ietf-interfaces {
leaf in-discards {
type yang:counter32;
+ status deprecated;
+
description
"The number of inbound packets that were chosen to be
discarded even though no errors had been detected to
@@ -563,17 +963,16 @@ module ietf-interfaces {
packet could be to free up buffer space.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
-
-
reference
"RFC 2863: The Interfaces Group MIB - ifInDiscards";
}
leaf in-errors {
type yang:counter32;
+ status deprecated;
description
"For packet-oriented interfaces, the number of inbound
packets that contained errors preventing them from being
@@ -584,7 +983,7 @@ module ietf-interfaces {
protocol.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -593,6 +992,7 @@ module ietf-interfaces {
leaf in-unknown-protos {
type yang:counter32;
+ status deprecated;
description
"For packet-oriented interfaces, the number of packets
received via the interface that were discarded because
@@ -603,27 +1003,23 @@ module ietf-interfaces {
discarded because of an unknown or unsupported protocol.
For any interface that does not support protocol
multiplexing, this counter is not present.
-
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
"RFC 2863: The Interfaces Group MIB - ifInUnknownProtos";
}
-
-
-
-
leaf out-octets {
type yang:counter64;
+ status deprecated;
description
"The total number of octets transmitted out of the
interface, including framing characters.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -632,14 +1028,15 @@ module ietf-interfaces {
leaf out-unicast-pkts {
type yang:counter64;
+ status deprecated;
description
"The total number of packets that higher-level protocols
- requested be transmitted, and that were not addressed
+ requested be transmitted and that were not addressed
to a multicast or broadcast address at this sub-layer,
including those that were discarded or not sent.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -648,14 +1045,16 @@ module ietf-interfaces {
leaf out-broadcast-pkts {
type yang:counter64;
+ status deprecated;
+
description
"The total number of packets that higher-level protocols
- requested be transmitted, and that were addressed to a
+ requested be transmitted and that were addressed to a
broadcast address at this sub-layer, including those
that were discarded or not sent.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -663,19 +1062,19 @@ module ietf-interfaces {
ifHCOutBroadcastPkts";
}
-
leaf out-multicast-pkts {
type yang:counter64;
+ status deprecated;
description
"The total number of packets that higher-level protocols
- requested be transmitted, and that were addressed to a
+ requested be transmitted and that were addressed to a
multicast address at this sub-layer, including those
that were discarded or not sent. For a MAC-layer
protocol, this includes both Group and Functional
addresses.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -685,6 +1084,7 @@ module ietf-interfaces {
leaf out-discards {
type yang:counter32;
+ status deprecated;
description
"The number of outbound packets that were chosen to be
discarded even though no errors had been detected to
@@ -693,7 +1093,7 @@ module ietf-interfaces {
space.
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
@@ -702,6 +1102,7 @@ module ietf-interfaces {
leaf out-errors {
type yang:counter32;
+ status deprecated;
description
"For packet-oriented interfaces, the number of outbound
packets that could not be transmitted because of errors.
@@ -709,11 +1110,8 @@ module ietf-interfaces {
number of outbound transmission units that could not be
transmitted because of errors.
-
-
-
Discontinuities in the value of this counter can occur
- at re-initialization of the management system, and at
+ at re-initialization of the management system and at
other times as indicated by the value of
'discontinuity-time'.";
reference
diff --git a/assets/yang/ietf-interfaces/modules/ietf-system-capabilities@2022-02-17.yang b/assets/yang/ietf-interfaces/modules/ietf-system-capabilities@2022-02-17.yang
index 55f959d8..2a9743ee 100644
--- a/assets/yang/ietf-interfaces/modules/ietf-system-capabilities@2022-02-17.yang
+++ b/assets/yang/ietf-interfaces/modules/ietf-system-capabilities@2022-02-17.yang
@@ -23,7 +23,7 @@ module ietf-system-capabilities {
contact
"WG Web:
WG List:
-
+
Editor: Balazs Lengyel
";
description
@@ -31,11 +31,11 @@ module ietf-system-capabilities {
capabilities for a server or a publisher. System capabilities
may include capabilities of a NETCONF or RESTCONF server or a
notification publisher.
-
+
This module does not contain any specific capabilities; it only
provides a structure where containers containing the actual
capabilities are augmented in.
-
+
Capability values can be specified at the system level, at the
datastore level (by selecting all nodes in the datastore), or
for specific data nodes of a specific datastore (and their
@@ -43,54 +43,54 @@ module ietf-system-capabilities {
Capability values specified for a specific datastore or
node-set override values specified on the system/publisher
level.
-
+
The same grouping MUST be used to define hierarchical
capabilities supported both at the system level and at the
datastore/data-node level.
-
+
To find a capability value for a specific data node in a
specific datastore, the user SHALL:
-
+
1) search for a datastore-capabilities list entry for
the specific datastore. When stating a specific capability, the
relative path for any specific capability must be the same
under the system-capabilities container and under the
per-node-capabilities list.
-
+
2) If the datastore entry is found within that entry, process
all per-node-capabilities entries in the order they appear in
the list. The first entry that specifies the specific
capability and has a node-selector selecting the specific data
node defines the capability value.
-
+
3) If the capability value is not found above and the specific
capability is specified under the system-capabilities container
(outside the datastore-capabilities list), this value shall be
used.
-
+
4) If no values are found in the previous steps, the
system/publisher is not capable of providing a value. Possible
reasons are that it is unknown, the capability is changing for
some reason, there is no specified limit, etc. In this case,
the system's behavior is unspecified.
-
+
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) 2022 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 9196
(https://www.rfc-editor.org/info/rfc9196); see the RFC itself
for full legal notices.";
@@ -118,13 +118,12 @@ module ietf-system-capabilities {
key "datastore";
description
"Capabilities values per datastore.
-
+
For non-NMDA servers/publishers, 'config false' data is
considered as if it were part of the running datastore.";
leaf datastore {
type leafref {
- path
- "/yanglib:yang-library/yanglib:datastore/yanglib:name";
+ path "/yanglib:yang-library/yanglib:datastore/yanglib:name";
}
description
"The datastore for which capabilities are defined.
@@ -137,11 +136,11 @@ module ietf-system-capabilities {
"Each list entry specifies capabilities for the selected
data nodes. The same capabilities apply to the data nodes
in the subtree below the selected nodes.
-
+
The system SHALL order the entries according to their
precedence. The order of the entries MUST NOT change
unless the underlying capabilities also change.
-
+
Note that the longest patch matching can be achieved
by ordering more specific matches before less
specific ones.";
@@ -160,10 +159,10 @@ module ietf-system-capabilities {
"RFC 8341: Network Configuration Access Control Model";
}
}
- /*
- * "Augmentation point for datastore- or data-node-level
- * capabilities."
- */
+ /*
+ * "Augmentation point for datastore- or data-node-level
+ * capabilities."
+ */
}
}
}
diff --git a/assets/yang/ietf-interfaces/modules/ietf-yang-push-revision@2025-08-29.yang b/assets/yang/ietf-interfaces/modules/ietf-yang-push-revision@2025-12-07.yang
similarity index 78%
rename from assets/yang/ietf-interfaces/modules/ietf-yang-push-revision@2025-08-29.yang
rename to assets/yang/ietf-interfaces/modules/ietf-yang-push-revision@2025-12-07.yang
index 731f654e..b714a42f 100644
--- a/assets/yang/ietf-interfaces/modules/ietf-yang-push-revision@2025-08-29.yang
+++ b/assets/yang/ietf-interfaces/modules/ietf-yang-push-revision@2025-12-07.yang
@@ -84,7 +84,7 @@ module ietf-yang-push-revision {
described in BCP 14 (RFC 2119) (RFC 8174) when, and only when,
they appear in all capitals, as shown here.";
- revision 2025-08-29 {
+ revision 2025-12-07 {
description
"First revision";
reference
@@ -92,13 +92,6 @@ module ietf-yang-push-revision {
Subscription";
}
- feature yang-push-revision-supported {
- description
- "This feature indicates the YANG Subscription Notifications
- supports specifying the list of modules, revisions and
- version in the YANG subscription.";
- }
-
// Identities
identity revision-unsupported {
base sn:establish-subscription-error;
@@ -113,7 +106,7 @@ module ietf-yang-push-revision {
base sn:establish-subscription-error;
base sn:modify-subscription-error;
description
- "Version not supported. This failure can be due to
+ "Specific version not supported. This failure can be due to
subscribing to a specific version not supported by the
publisher.";
}
@@ -122,14 +115,13 @@ module ietf-yang-push-revision {
base sn:establish-subscription-error;
base sn:modify-subscription-error;
description
- "The combination of revision and the version are
+ "The combination of revision and version are
incompatible. This failure happens when the revision and the
version are both specified in the RPC and the YANG
- module supported by the publisher does not support one of the
- revision or the version.";
+ module supported by the publisher does not support one of them.";
}
- grouping yang-push-module-version-config {
+ grouping yang-push-module-version-subs {
description
"This grouping combines the module name, the revision and
version leaves. This grouping is to be used for
@@ -139,21 +131,30 @@ module ietf-yang-push-revision {
description
"This references the YANG module name.";
}
- leaf revision {
- type rev:revision-date;
- description
- "This references the YANG module revision to be sent in the
- subscription.";
- }
- leaf version {
- type ysver:version;
+ choice revision-version {
description
- "This references the YANG module semantic version to be sent
+ "Specifies the revision or version of the YANG module sent
in the subscription.";
- }
+ case revision {
+ leaf revision {
+ type rev:revision-date;
+ description
+ "This references the YANG module revision to be sent in the
+ subscription.";
+ }
+ }
+ case version {
+ leaf version {
+ type ysver:version;
+ description
+ "This references the YANG module semantic version to be sent
+ in the subscription.";
+ }
+ }
+ }
}
- grouping yang-push-module-version {
+ grouping yang-push-module-version-notif {
description
"This grouping combines the module name, the revision and
version leaves. This grouping is to be used for
@@ -183,9 +184,9 @@ module ietf-yang-push-revision {
}
}
- grouping yang-push-module-version-list {
+ grouping yang-push-module-version-notif-list {
description
- "This grouping defines a list of yang-push-module-version
+ "This grouping defines a list of yang-push-module-version-notif
grouping.";
list module-version {
key "name";
@@ -193,7 +194,7 @@ module ietf-yang-push-revision {
description
"List of yang-push-module-version grouping. The revision is
not configurable.";
- uses ypr:yang-push-module-version;
+ uses ypr:yang-push-module-version-notif;
}
leaf yang-library-content-id {
type leafref {
@@ -206,78 +207,75 @@ module ietf-yang-push-revision {
}
}
- grouping yang-push-module-version-config-list {
+ grouping yang-push-module-version-subs-list {
description
- "This grouping defines a list of yang-push-module-version-config
+ "This grouping defines a list of yang-push-module-version-subs
grouping.";
- list module-version-config {
+ list module-version-subs {
key "name";
description
- "List of yang-push-module-version-config grouping. The
+ "List of yang-push-module-version-subs grouping. The
revision is configurable.";
- uses ypr:yang-push-module-version-config;
+ uses ypr:yang-push-module-version-subs;
}
}
// Subscription parameters
augment "/sn:establish-subscription/sn:input" {
- if-feature "yang-push-revision-supported";
description
"Augment the establish-subscription RPC from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-config-list grouping.";
- uses ypr:yang-push-module-version-config-list;
+ yang-push-module-version-subs-list grouping.";
+ uses ypr:yang-push-module-version-subs-list;
}
augment "/sn:modify-subscription/sn:input" {
- if-feature "yang-push-revision-supported";
description
"Augment the modify-subscription RPC from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-config-list grouping.";
- uses ypr:yang-push-module-version-config-list;
+ yang-push-module-version-subs-list grouping.";
+ uses ypr:yang-push-module-version-subs-list;
}
// Subscription notifications
augment "/sn:subscription-started" {
- if-feature "yang-push-revision-supported";
description
"Augment the subscription-started notification from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-list grouping.";
- uses ypr:yang-push-module-version-list;
+ yang-push-module-version-notif-list grouping.";
+ uses ypr:yang-push-module-version-notif-list;
}
augment "/sn:subscription-modified" {
- if-feature "yang-push-revision-supported";
description
"Augment the subscription-modified notification from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-list grouping.";
- uses ypr:yang-push-module-version-list;
+ yang-push-module-version-notif-list grouping.";
+ uses ypr:yang-push-module-version-notif-list;
}
// Subscription container
augment "/sn:subscriptions/sn:subscription" {
- if-feature "yang-push-revision-supported";
description
"Augment the subscriptions RPC container from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-config-list grouping.";
- uses ypr:yang-push-module-version-config-list;
+ yang-push-module-version-subs-list grouping.";
+ uses ypr:yang-push-module-version-subs-list;
}
- // Event capabilities
+ // Subscription capabilities
augment "/sysc:system-capabilities/notc:subscription-capabilities" {
description
"Add system level capabilities";
leaf yang-push-module-revision-supported {
type boolean;
+ default true;
description
"Specifies whether the publisher supports exporting
revision and version in YANG-Push subscription state change
- notifications.";
+ notifications. If set to true, publisher supports. If set to
+ false, publisher does not support.";
reference
"RFC XXXX: Support of Versioning in YANG Notifications
Subscription";
}
}
-}
\ No newline at end of file
+}
diff --git a/assets/yang/ietf-interfaces/modules/ietf-yp-observation@2026-05-11.yang b/assets/yang/ietf-interfaces/modules/ietf-yp-observation@2026-05-11.yang
new file mode 100644
index 00000000..8a245621
--- /dev/null
+++ b/assets/yang/ietf-interfaces/modules/ietf-yp-observation@2026-05-11.yang
@@ -0,0 +1,138 @@
+module ietf-yp-observation {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-yp-observation";
+ prefix iypo;
+
+ import ietf-yang-types {
+ prefix yang;
+ reference
+ "RFC 9911: Common YANG Data Types";
+ }
+ import ietf-yang-push {
+ prefix yp;
+ reference
+ "RFC 8641: Subscription to YANG Notifications for Datastore
+ Updates";
+ }
+ import ietf-system-capabilities {
+ prefix sysc;
+ reference
+ "RFC 9196: YANG Modules Describing Capabilities for
+ Systems and Datastore Update Notifications";
+ }
+ import ietf-notification-capabilities {
+ prefix notc;
+ reference
+ "RFC 9196: YANG Modules Describing Capabilities for
+ Systems and Datastore Update Notifications";
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web:
+ WG List:
+
+ Authors: Thomas Graf
+
+ Benoit Claise
+
+ Alex Huang Feng
+ ";
+ description
+ "Defines YANG-Push event notification header with the observation
+ time in streaming update notifications.
+
+ 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).
+
+ All revisions of IETF and IANA published modules can be found
+ at the YANG Parameters registry group
+ (https://www.iana.org/assignments/yang-parameters).
+
+ This version of this YANG module is part of RFC XXXX; see
+ the RFC itself for full legal notices.";
+
+ revision 2026-05-11 {
+ description
+ "Initial version.";
+ reference
+ "RFC XXXX: Extensible YANG Model for YANG-Push Notifications";
+ }
+
+ grouping yang-push-observation {
+ description
+ "This grouping adds the observation timestamp for the
+ observed metrics.";
+ leaf timestamp {
+ type yang:date-and-time;
+ description
+ "This is the time when the metrics were observed.";
+ }
+ leaf point-in-time {
+ type enumeration {
+ enum current-accounting {
+ description
+ "For periodic subscriptions, the point-in-time
+ where the metrics are being polled and observed.";
+ }
+ enum initial-state {
+ description
+ "For 'on-change sync on start' subscriptions, the
+ initial point in time when the subscription was
+ established and the state was observed.";
+ }
+ enum state-changed {
+ description
+ "For 'on-change sync on start' subscriptions, the
+ point in time when the state change was observed after
+ the subscription was established.";
+ }
+ }
+ description
+ "This describes at which point in time the metrics were
+ observed.";
+ }
+ }
+
+ // Event notifications
+ augment "/yp:push-update" {
+ description
+ "This augmentation adds the observation timestamp of the
+ accounted metrics in the push-update notification.";
+ uses iypo:yang-push-observation;
+ }
+
+ augment "/yp:push-change-update" {
+ description
+ "This augmentation adds the observation timestamp of the
+ event in the push-change-update notification.";
+ uses iypo:yang-push-observation;
+ }
+
+ // Event capabilities
+ augment "/sysc:system-capabilities"
+ + "/notc:subscription-capabilities" {
+ description
+ "Add YANG-Push notification capabilities to system-level
+ capability container.";
+ leaf yang-push-observation-time-supported {
+ type boolean;
+ default "false";
+ description
+ "Specifies whether the publisher supports exporting
+ observation-timestamp and point-in-time in notifications.
+ If set to true, publisher supports. If set to false,
+ the observation-timestamp is not supported.";
+ reference
+ "RFC XXXX: Extensible YANG Model for YANG-Push Notifications";
+ }
+ }
+}
diff --git a/assets/yang/ietf-interfaces/subscriptions-info.json b/assets/yang/ietf-interfaces/subscriptions-info.json
index b7778881..24ba4642 100644
--- a/assets/yang/ietf-interfaces/subscriptions-info.json
+++ b/assets/yang/ietf-interfaces/subscriptions-info.json
@@ -1,7 +1,6 @@
[
{
- "collector": "127.0.0.1:10000",
- "peer": "0.0.0.0:830",
+ "peer_ip": "0.0.0.0",
"id": 1,
"target": {
"ietf-yang-push:datastore": "ietf-datastores:operational",
@@ -26,4 +25,4 @@
],
"content_id": "test-content-id-1"
}
-]
+]
\ No newline at end of file
diff --git a/assets/yang/ietf-interfaces/yang-lib.xml b/assets/yang/ietf-interfaces/yang-lib.xml
index ab19d8d8..8bd3d20e 100644
--- a/assets/yang/ietf-interfaces/yang-lib.xml
+++ b/assets/yang/ietf-interfaces/yang-lib.xml
@@ -1,190 +1,259 @@
-
+ complete
+
+
+
+
+
+
+
+
- ietf-interfaces
- 2014-05-08
- urn:ietf:params:xml:ns:yang:ietf-interfaces
- arbitrary-names
- pre-provisioning
- if-mib
- ietf-ip
- ietf-network-instance
-
+ ietf-yp-notification
+ 2025-02-24
+ urn:ietf:params:xml:ns:yang:ietf-yp-notification
-
+
- ietf-yang-types
- 2013-07-15
- urn:ietf:params:xml:ns:yang:ietf-yang-types
+ ietf-system-capabilities
+ 2022-02-17
+ urn:ietf:params:xml:ns:yang:ietf-system-capabilities
- ietf-inet-types
- 2021-02-22
- urn:ietf:params:xml:ns:yang:ietf-inet-types
+ ietf-notification-capabilities
+ 2022-02-17
+ urn:ietf:params:xml:ns:yang:ietf-notification-capabilities
+
+
- ietf-platform-manifest
- 2025-02-21
- urn:ietf:params:xml:ns:yang:ietf-platform-manifest
+ ietf-yang-push
+ 2019-09-09
+ urn:ietf:params:xml:ns:yang:ietf-yang-push
+
+ on-change
+
- ietf-yang-structure-ext
- 2020-06-17
- urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext
+ ietf-yang-push-revision
+ 2025-12-07
+ urn:ietf:params:xml:ns:yang:ietf-yang-push-revision
-
+
- ietf-subscribed-notifications
- 2019-09-09
- urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications
-
- encode-json
- encode-xml
- configured
- subtree
- xpath
+ ietf-yp-observation
+ 2026-05-11
+ urn:ietf:params:xml:ns:yang:ietf-yp-observation
+
+
- ietf-yang-push
- 2019-09-09
- urn:ietf:params:xml:ns:yang:ietf-yang-push
-
- on-change
+ ietf-subscribed-notifications
+ 2019-09-09
+ urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications
+
+ encode-json
+ encode-xml
+ configured
+ subtree
+ xpath
+
- ietf-datastores
- 2018-02-14
- urn:ietf:params:xml:ns:yang:ietf-datastores
+ ietf-distributed-notif
+ 2026-04-13
+ urn:ietf:params:xml:ns:yang:ietf-distributed-notif
+
- ietf-yang-revisions
- 2024-06-04
- urn:ietf:params:xml:ns:yang:ietf-yang-revisions
+ ietf-netconf-acm
+ 2018-02-14
+ urn:ietf:params:xml:ns:yang:ietf-netconf-acm
- ietf-yang-semver
- 2024-07-02
- urn:ietf:params:xml:ns:yang:ietf-yang-semver
+ ietf-restconf
+ 2017-01-26
+ urn:ietf:params:xml:ns:yang:ietf-restconf
+
+
+
+
+ ietf-platform-manifest
+ 2025-02-21
+ urn:ietf:params:xml:ns:yang:ietf-platform-manifest
- ietf-yang-library
- 2019-01-04
- urn:ietf:params:xml:ns:yang:ietf-yang-library
+ ietf-yang-library
+ 2019-01-04
+ urn:ietf:params:xml:ns:yang:ietf-yang-library
+
+
+
+
+
+
+
+
+
+ ietf-udp-notif-transport
+ 2025-02-14
+ urn:ietf:params:xml:ns:yang:ietf-udp-notif-transport
+
+
+
+ ietf-subscribed-notif-receivers
+ 2024-02-01
+ urn:ietf:params:xml:ns:yang:ietf-subscribed-notif-receivers
-
- ietf-netconf-acm
- 2018-02-14
- urn:ietf:params:xml:ns:yang:ietf-netconf-acm
+ ietf-udp-client
+ 2025-02-24
+ urn:ietf:params:xml:ns:yang:ietf-udp-client
- ietf-network-instance
- 2019-01-21
- urn:ietf:params:xml:ns:yang:ietf-network-instance
+ ietf-tls-client
+ 2024-10-10
+ urn:ietf:params:xml:ns:yang:ietf-tls-client
+
- ietf-restconf
- 2017-01-26
- urn:ietf:params:xml:ns:yang:ietf-restconf
+ ietf-crypto-types
+ 2024-10-10
+ urn:ietf:params:xml:ns:yang:ietf-crypto-types
-
- ietf-yang-patch
- 2017-02-22
- urn:ietf:params:xml:ns:yang:ietf-yang-patch
+ ietf-truststore
+ 2024-10-10
+ urn:ietf:params:xml:ns:yang:ietf-truststore
-
- ietf-ip
- 2018-02-22
- urn:ietf:params:xml:ns:yang:ietf-ip
+ ietf-keystore
+ 2024-10-10
+ urn:ietf:params:xml:ns:yang:ietf-keystore
- ietf-yang-schema-mount
- 2019-01-14
- urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount
+ ietf-tls-common
+ 2024-10-10
+ urn:ietf:params:xml:ns:yang:ietf-tls-common
-
+
- ietf-yp-notification
- 2025-02-24
- urn:ietf:params:xml:ns:yang:ietf-yp-notification
+ iana-tls-cipher-suite-algs
+ 2024-10-16
+ urn:ietf:params:xml:ns:yang:iana-tls-cipher-suite-algs
-
+
+
+
+
+
+
+
- ietf-system-capabilities
- 2022-02-17
- urn:ietf:params:xml:ns:yang:ietf-system-capabilities
+ ietf-yang-types
+ 2013-07-15
+ urn:ietf:params:xml:ns:yang:ietf-yang-types
- ietf-notification-capabilities
- 2022-02-17
- urn:ietf:params:xml:ns:yang:ietf-notification-capabilities
+ ietf-inet-types
+ 2021-02-22
+ urn:ietf:params:xml:ns:yang:ietf-inet-types
-
+
- ietf-udp-notif-transport
- 2025-02-14
- urn:ietf:params:xml:ns:yang:ietf-udp-notif-transport
+ ietf-datastores
+ 2018-02-14
+ urn:ietf:params:xml:ns:yang:ietf-datastores
-
+
- ietf-subscribed-notif-receivers
- 2024-02-01
- urn:ietf:params:xml:ns:yang:ietf-subscribed-notif-receivers
+ ietf-yang-patch
+ 2017-02-22
+ urn:ietf:params:xml:ns:yang:ietf-yang-patch
+
- ietf-udp-client
- 2025-02-24
- urn:ietf:params:xml:ns:yang:ietf-udp-client
+ ietf-yang-revisions
+ 2024-06-04
+ urn:ietf:params:xml:ns:yang:ietf-yang-revisions
+
- ietf-tls-client
- 2024-10-10
- urn:ietf:params:xml:ns:yang:ietf-tls-client
+ ietf-yang-semver
+ 2024-07-02
+ urn:ietf:params:xml:ns:yang:ietf-yang-semver
-
+
- iana-if-type
- 2014-05-08
- urn:ietf:params:xml:ns:yang:iana-if-type
+ ietf-yang-structure-ext
+ 2020-06-17
+ urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext
-
+
- ietf-crypto-types
- 2024-10-10
- urn:ietf:params:xml:ns:yang:ietf-crypto-types
+ ietf-yang-schema-mount
+ 2019-01-14
+ urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount
+
+
+
+
+
+
+
+
- ietf-truststore
- 2024-10-10
- urn:ietf:params:xml:ns:yang:ietf-truststore
+ ietf-interfaces
+ 2018-02-20
+ urn:ietf:params:xml:ns:yang:ietf-interfaces
+ arbitrary-names
+ pre-provisioning
+ if-mib
+
+ ietf-ip
+
+ ietf-network-instance
+
- ietf-keystore
- 2024-10-10
- urn:ietf:params:xml:ns:yang:ietf-keystore
+ ietf-ip
+ 2018-02-22
+ urn:ietf:params:xml:ns:yang:ietf-ip
+
- ietf-tls-common
- 2024-10-10
- urn:ietf:params:xml:ns:yang:ietf-tls-common
+ ietf-network-instance
+ 2019-01-21
+ urn:ietf:params:xml:ns:yang:ietf-network-instance
-
+
- iana-tls-cipher-suite-algs
- 2024-10-16
- urn:ietf:params:xml:ns:yang:iana-tls-cipher-suite-algs
+ iana-if-type
+ 2014-05-08
+ urn:ietf:params:xml:ns:yang:iana-if-type
-
-
+
+
+ completecomplete
-
-ietf-telemetry-message-full
+
+ yp-envelope-ietf-interface
-34
-
+ 34
+
\ No newline at end of file
diff --git a/assets/yang/ietf-telemetry-message/modules/ietf-distributed-notif@2026-04-13.yang b/assets/yang/ietf-telemetry-message/modules/ietf-distributed-notif@2026-04-13.yang
new file mode 100644
index 00000000..70ce99d2
--- /dev/null
+++ b/assets/yang/ietf-telemetry-message/modules/ietf-distributed-notif@2026-04-13.yang
@@ -0,0 +1,127 @@
+module ietf-distributed-notif {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-distributed-notif";
+ prefix dn;
+
+ import ietf-subscribed-notifications {
+ prefix sn;
+ reference
+ "RFC 8639: Subscription to YANG Notifications";
+ }
+ import ietf-yang-push {
+ prefix yp;
+ reference
+ "RFC 8641: Subscription to YANG Notifications for Datastore
+ Updates";
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web:
+ WG List:
+
+ Authors: Guangying Zheng
+
+ Tianran Zhou
+
+ Thomas Graf
+
+ Pierre Francois
+
+ Eric Voit
+ ";
+ description
+ "Defines augmentation for ietf-subscribed-notifications to
+ enable the distributed publication with single subscription.
+
+ 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).
+
+ All revisions of IETF and IANA published modules can be found
+ at the YANG Parameters registry
+ (https://www.iana.org/assignments/yang-parameters).
+
+ This version of this YANG module is part of RFC XXXX; see
+ the RFC itself for full legal notices.";
+
+ revision 2026-04-13 {
+ description
+ "Initial version";
+ reference
+ "RFC XXXX: Subscription to Notifications in a Distributed
+ Architecture";
+ }
+
+ grouping message-publisher-id {
+ description
+ "Provides a reusable message-publisher-id leaf.";
+ leaf message-publisher-id {
+ type uint32;
+ config false;
+ description
+ "Identifies the Component software process which publishes
+ notification messages (e.g., processor 1 on line card 1). This
+ field is used to notify the receiver which publisher process
+ published which message. The identifier is locally unique to
+ the Network Node.";
+ }
+ }
+
+ grouping message-publisher-ids {
+ description
+ "Provides a reusable message-publisher-id-list leaf-list.";
+ leaf-list message-publisher-id {
+ type uint32;
+ config false;
+ description
+ "Identifies the Component software process which publishes
+ notification messages (e.g., processor 1 on line card 1). This
+ field is used to notify the receiver which publisher processes
+ are going to publish. The identifiers are locally unique to
+ the Network Node.";
+ }
+ }
+
+ augment "/sn:subscriptions/sn:subscription" {
+ description
+ "This augmentation allows the Message
+ Publisher ID to be exposed for a subscription.";
+ uses message-publisher-ids;
+ }
+
+ augment "/sn:subscription-started" {
+ description
+ "This augmentation adds the Message Publisher ID to the
+ subscription-started subscription change notifications.";
+ uses message-publisher-ids;
+ }
+
+ augment "/sn:subscription-modified" {
+ description
+ "This augmentation adds the Message Publisher ID to the
+ subscription-modified subscription change notifications.";
+ uses message-publisher-ids;
+ }
+
+ augment "/yp:push-update" {
+ description
+ "This augmentation adds the Message Publisher ID in the
+ push-update notification.";
+ uses message-publisher-id;
+ }
+
+ augment "/yp:push-change-update" {
+ description
+ "This augmentation adds the Message Publisher ID in the
+ push-change-update notification.";
+ uses message-publisher-id;
+ }
+}
diff --git a/assets/yang/ietf-telemetry-message/modules/ietf-system-capabilities@2022-02-17.yang b/assets/yang/ietf-telemetry-message/modules/ietf-system-capabilities@2022-02-17.yang
index 55f959d8..2a9743ee 100644
--- a/assets/yang/ietf-telemetry-message/modules/ietf-system-capabilities@2022-02-17.yang
+++ b/assets/yang/ietf-telemetry-message/modules/ietf-system-capabilities@2022-02-17.yang
@@ -23,7 +23,7 @@ module ietf-system-capabilities {
contact
"WG Web:
WG List:
-
+
Editor: Balazs Lengyel
";
description
@@ -31,11 +31,11 @@ module ietf-system-capabilities {
capabilities for a server or a publisher. System capabilities
may include capabilities of a NETCONF or RESTCONF server or a
notification publisher.
-
+
This module does not contain any specific capabilities; it only
provides a structure where containers containing the actual
capabilities are augmented in.
-
+
Capability values can be specified at the system level, at the
datastore level (by selecting all nodes in the datastore), or
for specific data nodes of a specific datastore (and their
@@ -43,54 +43,54 @@ module ietf-system-capabilities {
Capability values specified for a specific datastore or
node-set override values specified on the system/publisher
level.
-
+
The same grouping MUST be used to define hierarchical
capabilities supported both at the system level and at the
datastore/data-node level.
-
+
To find a capability value for a specific data node in a
specific datastore, the user SHALL:
-
+
1) search for a datastore-capabilities list entry for
the specific datastore. When stating a specific capability, the
relative path for any specific capability must be the same
under the system-capabilities container and under the
per-node-capabilities list.
-
+
2) If the datastore entry is found within that entry, process
all per-node-capabilities entries in the order they appear in
the list. The first entry that specifies the specific
capability and has a node-selector selecting the specific data
node defines the capability value.
-
+
3) If the capability value is not found above and the specific
capability is specified under the system-capabilities container
(outside the datastore-capabilities list), this value shall be
used.
-
+
4) If no values are found in the previous steps, the
system/publisher is not capable of providing a value. Possible
reasons are that it is unknown, the capability is changing for
some reason, there is no specified limit, etc. In this case,
the system's behavior is unspecified.
-
+
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) 2022 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 9196
(https://www.rfc-editor.org/info/rfc9196); see the RFC itself
for full legal notices.";
@@ -118,13 +118,12 @@ module ietf-system-capabilities {
key "datastore";
description
"Capabilities values per datastore.
-
+
For non-NMDA servers/publishers, 'config false' data is
considered as if it were part of the running datastore.";
leaf datastore {
type leafref {
- path
- "/yanglib:yang-library/yanglib:datastore/yanglib:name";
+ path "/yanglib:yang-library/yanglib:datastore/yanglib:name";
}
description
"The datastore for which capabilities are defined.
@@ -137,11 +136,11 @@ module ietf-system-capabilities {
"Each list entry specifies capabilities for the selected
data nodes. The same capabilities apply to the data nodes
in the subtree below the selected nodes.
-
+
The system SHALL order the entries according to their
precedence. The order of the entries MUST NOT change
unless the underlying capabilities also change.
-
+
Note that the longest patch matching can be achieved
by ordering more specific matches before less
specific ones.";
@@ -160,10 +159,10 @@ module ietf-system-capabilities {
"RFC 8341: Network Configuration Access Control Model";
}
}
- /*
- * "Augmentation point for datastore- or data-node-level
- * capabilities."
- */
+ /*
+ * "Augmentation point for datastore- or data-node-level
+ * capabilities."
+ */
}
}
}
diff --git a/assets/yang/ietf-telemetry-message/modules/ietf-yang-push-revision@2025-08-29.yang b/assets/yang/ietf-telemetry-message/modules/ietf-yang-push-revision@2025-12-07.yang
similarity index 78%
rename from assets/yang/ietf-telemetry-message/modules/ietf-yang-push-revision@2025-08-29.yang
rename to assets/yang/ietf-telemetry-message/modules/ietf-yang-push-revision@2025-12-07.yang
index 731f654e..b714a42f 100644
--- a/assets/yang/ietf-telemetry-message/modules/ietf-yang-push-revision@2025-08-29.yang
+++ b/assets/yang/ietf-telemetry-message/modules/ietf-yang-push-revision@2025-12-07.yang
@@ -84,7 +84,7 @@ module ietf-yang-push-revision {
described in BCP 14 (RFC 2119) (RFC 8174) when, and only when,
they appear in all capitals, as shown here.";
- revision 2025-08-29 {
+ revision 2025-12-07 {
description
"First revision";
reference
@@ -92,13 +92,6 @@ module ietf-yang-push-revision {
Subscription";
}
- feature yang-push-revision-supported {
- description
- "This feature indicates the YANG Subscription Notifications
- supports specifying the list of modules, revisions and
- version in the YANG subscription.";
- }
-
// Identities
identity revision-unsupported {
base sn:establish-subscription-error;
@@ -113,7 +106,7 @@ module ietf-yang-push-revision {
base sn:establish-subscription-error;
base sn:modify-subscription-error;
description
- "Version not supported. This failure can be due to
+ "Specific version not supported. This failure can be due to
subscribing to a specific version not supported by the
publisher.";
}
@@ -122,14 +115,13 @@ module ietf-yang-push-revision {
base sn:establish-subscription-error;
base sn:modify-subscription-error;
description
- "The combination of revision and the version are
+ "The combination of revision and version are
incompatible. This failure happens when the revision and the
version are both specified in the RPC and the YANG
- module supported by the publisher does not support one of the
- revision or the version.";
+ module supported by the publisher does not support one of them.";
}
- grouping yang-push-module-version-config {
+ grouping yang-push-module-version-subs {
description
"This grouping combines the module name, the revision and
version leaves. This grouping is to be used for
@@ -139,21 +131,30 @@ module ietf-yang-push-revision {
description
"This references the YANG module name.";
}
- leaf revision {
- type rev:revision-date;
- description
- "This references the YANG module revision to be sent in the
- subscription.";
- }
- leaf version {
- type ysver:version;
+ choice revision-version {
description
- "This references the YANG module semantic version to be sent
+ "Specifies the revision or version of the YANG module sent
in the subscription.";
- }
+ case revision {
+ leaf revision {
+ type rev:revision-date;
+ description
+ "This references the YANG module revision to be sent in the
+ subscription.";
+ }
+ }
+ case version {
+ leaf version {
+ type ysver:version;
+ description
+ "This references the YANG module semantic version to be sent
+ in the subscription.";
+ }
+ }
+ }
}
- grouping yang-push-module-version {
+ grouping yang-push-module-version-notif {
description
"This grouping combines the module name, the revision and
version leaves. This grouping is to be used for
@@ -183,9 +184,9 @@ module ietf-yang-push-revision {
}
}
- grouping yang-push-module-version-list {
+ grouping yang-push-module-version-notif-list {
description
- "This grouping defines a list of yang-push-module-version
+ "This grouping defines a list of yang-push-module-version-notif
grouping.";
list module-version {
key "name";
@@ -193,7 +194,7 @@ module ietf-yang-push-revision {
description
"List of yang-push-module-version grouping. The revision is
not configurable.";
- uses ypr:yang-push-module-version;
+ uses ypr:yang-push-module-version-notif;
}
leaf yang-library-content-id {
type leafref {
@@ -206,78 +207,75 @@ module ietf-yang-push-revision {
}
}
- grouping yang-push-module-version-config-list {
+ grouping yang-push-module-version-subs-list {
description
- "This grouping defines a list of yang-push-module-version-config
+ "This grouping defines a list of yang-push-module-version-subs
grouping.";
- list module-version-config {
+ list module-version-subs {
key "name";
description
- "List of yang-push-module-version-config grouping. The
+ "List of yang-push-module-version-subs grouping. The
revision is configurable.";
- uses ypr:yang-push-module-version-config;
+ uses ypr:yang-push-module-version-subs;
}
}
// Subscription parameters
augment "/sn:establish-subscription/sn:input" {
- if-feature "yang-push-revision-supported";
description
"Augment the establish-subscription RPC from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-config-list grouping.";
- uses ypr:yang-push-module-version-config-list;
+ yang-push-module-version-subs-list grouping.";
+ uses ypr:yang-push-module-version-subs-list;
}
augment "/sn:modify-subscription/sn:input" {
- if-feature "yang-push-revision-supported";
description
"Augment the modify-subscription RPC from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-config-list grouping.";
- uses ypr:yang-push-module-version-config-list;
+ yang-push-module-version-subs-list grouping.";
+ uses ypr:yang-push-module-version-subs-list;
}
// Subscription notifications
augment "/sn:subscription-started" {
- if-feature "yang-push-revision-supported";
description
"Augment the subscription-started notification from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-list grouping.";
- uses ypr:yang-push-module-version-list;
+ yang-push-module-version-notif-list grouping.";
+ uses ypr:yang-push-module-version-notif-list;
}
augment "/sn:subscription-modified" {
- if-feature "yang-push-revision-supported";
description
"Augment the subscription-modified notification from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-list grouping.";
- uses ypr:yang-push-module-version-list;
+ yang-push-module-version-notif-list grouping.";
+ uses ypr:yang-push-module-version-notif-list;
}
// Subscription container
augment "/sn:subscriptions/sn:subscription" {
- if-feature "yang-push-revision-supported";
description
"Augment the subscriptions RPC container from the
ietf-subscribed-notifications YANG module with the
- yang-push-module-version-config-list grouping.";
- uses ypr:yang-push-module-version-config-list;
+ yang-push-module-version-subs-list grouping.";
+ uses ypr:yang-push-module-version-subs-list;
}
- // Event capabilities
+ // Subscription capabilities
augment "/sysc:system-capabilities/notc:subscription-capabilities" {
description
"Add system level capabilities";
leaf yang-push-module-revision-supported {
type boolean;
+ default true;
description
"Specifies whether the publisher supports exporting
revision and version in YANG-Push subscription state change
- notifications.";
+ notifications. If set to true, publisher supports. If set to
+ false, publisher does not support.";
reference
"RFC XXXX: Support of Versioning in YANG Notifications
Subscription";
}
}
-}
\ No newline at end of file
+}
diff --git a/assets/yang/ietf-telemetry-message/modules/ietf-yp-observation@2026-05-11.yang b/assets/yang/ietf-telemetry-message/modules/ietf-yp-observation@2026-05-11.yang
new file mode 100644
index 00000000..8a245621
--- /dev/null
+++ b/assets/yang/ietf-telemetry-message/modules/ietf-yp-observation@2026-05-11.yang
@@ -0,0 +1,138 @@
+module ietf-yp-observation {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-yp-observation";
+ prefix iypo;
+
+ import ietf-yang-types {
+ prefix yang;
+ reference
+ "RFC 9911: Common YANG Data Types";
+ }
+ import ietf-yang-push {
+ prefix yp;
+ reference
+ "RFC 8641: Subscription to YANG Notifications for Datastore
+ Updates";
+ }
+ import ietf-system-capabilities {
+ prefix sysc;
+ reference
+ "RFC 9196: YANG Modules Describing Capabilities for
+ Systems and Datastore Update Notifications";
+ }
+ import ietf-notification-capabilities {
+ prefix notc;
+ reference
+ "RFC 9196: YANG Modules Describing Capabilities for
+ Systems and Datastore Update Notifications";
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web:
+ WG List:
+
+ Authors: Thomas Graf
+
+ Benoit Claise
+
+ Alex Huang Feng
+ ";
+ description
+ "Defines YANG-Push event notification header with the observation
+ time in streaming update notifications.
+
+ 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).
+
+ All revisions of IETF and IANA published modules can be found
+ at the YANG Parameters registry group
+ (https://www.iana.org/assignments/yang-parameters).
+
+ This version of this YANG module is part of RFC XXXX; see
+ the RFC itself for full legal notices.";
+
+ revision 2026-05-11 {
+ description
+ "Initial version.";
+ reference
+ "RFC XXXX: Extensible YANG Model for YANG-Push Notifications";
+ }
+
+ grouping yang-push-observation {
+ description
+ "This grouping adds the observation timestamp for the
+ observed metrics.";
+ leaf timestamp {
+ type yang:date-and-time;
+ description
+ "This is the time when the metrics were observed.";
+ }
+ leaf point-in-time {
+ type enumeration {
+ enum current-accounting {
+ description
+ "For periodic subscriptions, the point-in-time
+ where the metrics are being polled and observed.";
+ }
+ enum initial-state {
+ description
+ "For 'on-change sync on start' subscriptions, the
+ initial point in time when the subscription was
+ established and the state was observed.";
+ }
+ enum state-changed {
+ description
+ "For 'on-change sync on start' subscriptions, the
+ point in time when the state change was observed after
+ the subscription was established.";
+ }
+ }
+ description
+ "This describes at which point in time the metrics were
+ observed.";
+ }
+ }
+
+ // Event notifications
+ augment "/yp:push-update" {
+ description
+ "This augmentation adds the observation timestamp of the
+ accounted metrics in the push-update notification.";
+ uses iypo:yang-push-observation;
+ }
+
+ augment "/yp:push-change-update" {
+ description
+ "This augmentation adds the observation timestamp of the
+ event in the push-change-update notification.";
+ uses iypo:yang-push-observation;
+ }
+
+ // Event capabilities
+ augment "/sysc:system-capabilities"
+ + "/notc:subscription-capabilities" {
+ description
+ "Add YANG-Push notification capabilities to system-level
+ capability container.";
+ leaf yang-push-observation-time-supported {
+ type boolean;
+ default "false";
+ description
+ "Specifies whether the publisher supports exporting
+ observation-timestamp and point-in-time in notifications.
+ If set to true, publisher supports. If set to false,
+ the observation-timestamp is not supported.";
+ reference
+ "RFC XXXX: Extensible YANG Model for YANG-Push Notifications";
+ }
+ }
+}
diff --git a/assets/yang/ietf-telemetry-message/subscriptions-info.json b/assets/yang/ietf-telemetry-message/subscriptions-info.json
index d1081e60..7c0f6503 100644
--- a/assets/yang/ietf-telemetry-message/subscriptions-info.json
+++ b/assets/yang/ietf-telemetry-message/subscriptions-info.json
@@ -1,6 +1,6 @@
[
{
- "peer": "0.0.0.0:830",
+ "peer_ip": "0.0.0.0",
"id": 1,
"content_id": "ietf-telemetry-message",
"target": {
@@ -12,4 +12,4 @@
"ietf-yang-push-telemetry-message"
]
}
-]
+]
\ No newline at end of file
diff --git a/assets/yang/ietf-telemetry-message/yang-lib-full.xml b/assets/yang/ietf-telemetry-message/yang-lib-full.xml
index aa31e4f2..1c90cb0a 100644
--- a/assets/yang/ietf-telemetry-message/yang-lib-full.xml
+++ b/assets/yang/ietf-telemetry-message/yang-lib-full.xml
@@ -1,7 +1,15 @@
complete
-
+
+
+
+
+
+
+
+
+
ietf-telemetry-message2025-10-19
@@ -9,44 +17,41 @@
network-node-manifestdata-collection-manifest
+
ietf-yang-push-telemetry-message2025-10-19urn:ietf:params:xml:ns:yang:ietf-yang-push-telemetry-message
-
+
+
+
+
+
+
+
+
- ietf-yang-types
- 2013-07-15
- urn:ietf:params:xml:ns:yang:ietf-yang-types
-
-
- ietf-inet-types
- 2021-02-22
- urn:ietf:params:xml:ns:yang:ietf-inet-types
-
-
- ietf-platform-manifest
- 2025-02-21
- urn:ietf:params:xml:ns:yang:ietf-platform-manifest
+ ietf-yp-notification
+ 2025-02-24
+ urn:ietf:params:xml:ns:yang:ietf-yp-notification
+
- ietf-yang-structure-ext
- 2020-06-17
- urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext
+ ietf-system-capabilities
+ 2022-02-17
+ urn:ietf:params:xml:ns:yang:ietf-system-capabilities
-
- ietf-subscribed-notifications
- 2019-09-09
- urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications
-
- encode-json
- encode-xml
- configured
- subtree
- xpath
+ ietf-notification-capabilities
+ 2022-02-17
+ urn:ietf:params:xml:ns:yang:ietf-notification-capabilities
+
+
ietf-yang-push2019-09-09
@@ -54,89 +59,79 @@
on-change
+
- ietf-datastores
- 2018-02-14
- urn:ietf:params:xml:ns:yang:ietf-datastores
-
-
- ietf-yang-revisions
- 2024-06-04
- urn:ietf:params:xml:ns:yang:ietf-yang-revisions
+ ietf-yang-push-revision
+ 2025-12-07
+ urn:ietf:params:xml:ns:yang:ietf-yang-push-revision
+
- ietf-yang-semver
- 2024-07-02
- urn:ietf:params:xml:ns:yang:ietf-yang-semver
+ ietf-yp-observation
+ 2026-05-11
+ urn:ietf:params:xml:ns:yang:ietf-yp-observation
-
+
+
- ietf-yang-library
- 2019-01-04
- urn:ietf:params:xml:ns:yang:ietf-yang-library
+ ietf-subscribed-notifications
+ 2019-09-09
+ urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications
+
+ encode-json
+ encode-xml
+ configured
+ subtree
+ xpath
-
+
- ietf-interfaces
- 2018-02-20
- urn:ietf:params:xml:ns:yang:ietf-interfaces
+ ietf-distributed-notif
+ 2026-04-13
+ urn:ietf:params:xml:ns:yang:ietf-distributed-notif
+
ietf-netconf-acm2018-02-14urn:ietf:params:xml:ns:yang:ietf-netconf-acm
-
- ietf-network-instance
- 2019-01-21
- urn:ietf:params:xml:ns:yang:ietf-network-instance
- ietf-restconf2017-01-26urn:ietf:params:xml:ns:yang:ietf-restconf
-
-
- ietf-yang-patch
- 2017-02-22
- urn:ietf:params:xml:ns:yang:ietf-yang-patch
-
-
-
- ietf-ip
- 2018-02-22
- urn:ietf:params:xml:ns:yang:ietf-ip
-
-
- ietf-yang-schema-mount
- 2019-01-14
- urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount
-
-
+
+
- ietf-yp-notification
- 2025-02-24
- urn:ietf:params:xml:ns:yang:ietf-yp-notification
-
-
-
- ietf-system-capabilities
- 2022-02-17
- urn:ietf:params:xml:ns:yang:ietf-system-capabilities
+ ietf-platform-manifest
+ 2025-02-21
+ urn:ietf:params:xml:ns:yang:ietf-platform-manifest
+
- ietf-notification-capabilities
- 2022-02-17
- urn:ietf:params:xml:ns:yang:ietf-notification-capabilities
+ ietf-yang-library
+ 2019-01-04
+ urn:ietf:params:xml:ns:yang:ietf-yang-library
-
+
+
+
+
+
+
+
ietf-udp-notif-transport2025-02-14urn:ietf:params:xml:ns:yang:ietf-udp-notif-transport
-
+
ietf-subscribed-notif-receivers2024-02-01
@@ -152,13 +147,7 @@
2024-10-10urn:ietf:params:xml:ns:yang:ietf-tls-client
-
-
- iana-if-type
- 2014-05-08
- urn:ietf:params:xml:ns:yang:iana-if-type
-
-
+
ietf-crypto-types2024-10-10
@@ -179,12 +168,112 @@
2024-10-10urn:ietf:params:xml:ns:yang:ietf-tls-common
-
+
iana-tls-cipher-suite-algs2024-10-16urn:ietf:params:xml:ns:yang:iana-tls-cipher-suite-algs
+
+
+
+
+
+
+
+
+
+ ietf-yang-types
+ 2013-07-15
+ urn:ietf:params:xml:ns:yang:ietf-yang-types
+
+
+
+ ietf-inet-types
+ 2021-02-22
+ urn:ietf:params:xml:ns:yang:ietf-inet-types
+
+
+
+ ietf-datastores
+ 2018-02-14
+ urn:ietf:params:xml:ns:yang:ietf-datastores
+
+
+
+ ietf-yang-patch
+ 2017-02-22
+ urn:ietf:params:xml:ns:yang:ietf-yang-patch
+
+
+
+ ietf-yang-revisions
+ 2024-06-04
+ urn:ietf:params:xml:ns:yang:ietf-yang-revisions
+
+
+
+ ietf-yang-semver
+ 2024-07-02
+ urn:ietf:params:xml:ns:yang:ietf-yang-semver
+
+
+
+ ietf-yang-structure-ext
+ 2020-06-17
+ urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext
+
+
+
+ ietf-yang-schema-mount
+ 2019-01-14
+ urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount
+
+
+
+
+
+
+
+
+
+
+ ietf-interfaces
+ 2018-02-20
+ urn:ietf:params:xml:ns:yang:ietf-interfaces
+
+ ietf-ip
+
+ ietf-network-instance
+
+
+
+ ietf-ip
+ 2018-02-22
+ urn:ietf:params:xml:ns:yang:ietf-ip
+
+
+
+ ietf-network-instance
+ 2019-01-21
+ urn:ietf:params:xml:ns:yang:ietf-network-instance
+
+
+
+ iana-if-type
+ 2014-05-08
+ urn:ietf:params:xml:ns:yang:iana-if-type
+
+
complete
@@ -194,4 +283,4 @@
34
-
+
\ No newline at end of file
diff --git a/assets/yang/ietf-telemetry-message/yang-lib-min.xml b/assets/yang/ietf-telemetry-message/yang-lib-min.xml
index 8e6548ac..0a0a6394 100644
--- a/assets/yang/ietf-telemetry-message/yang-lib-min.xml
+++ b/assets/yang/ietf-telemetry-message/yang-lib-min.xml
@@ -44,4 +44,4 @@
34
-
+
\ No newline at end of file
diff --git a/crates/collector/Cargo.toml b/crates/collector/Cargo.toml
index 86b3cc26..a88c0ac4 100644
--- a/crates/collector/Cargo.toml
+++ b/crates/collector/Cargo.toml
@@ -125,7 +125,7 @@ chrono = { workspace = true, default-features = false, features = [
"clock",
] }
tempfile = { workspace = true }
-yang4 = { workspace = true}
+yang5 = { workspace = true }
tracing-test = { workspace = true }
[features]
diff --git a/crates/collector/examples/kafka-yang-consumer.rs b/crates/collector/examples/kafka-yang-consumer.rs
index 961d2d91..8436589a 100644
--- a/crates/collector/examples/kafka-yang-consumer.rs
+++ b/crates/collector/examples/kafka-yang-consumer.rs
@@ -1,3 +1,4 @@
+// Copyright (C) 2026-present The NetCalyx Authors.
// Copyright (C) 2026-present The NetGauze Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -45,11 +46,11 @@ use schema_registry_client::rest::schema_registry_client::{Client, SchemaRegistr
use serde_json::json;
use shadow_rs::shadow;
use std::collections::{HashMap, HashSet};
-use std::net::{IpAddr, SocketAddr};
+use std::net::IpAddr;
use tokio::signal;
use tracing::{debug, error, info, trace, warn};
-use yang4::context::Context;
-use yang4::data::{DataFormat, DataParserFlags, DataValidationFlags};
+use yang5::context::Context;
+use yang5::data::{DataFormat, DataParserFlags, DataValidationFlags};
shadow!(build);
@@ -131,7 +132,6 @@ struct ValidationStats {
failed: usize,
context_errors: usize,
no_schema: usize,
- no_tm_schema: usize,
}
/// Cache for YANG contexts indexed by schema ID
@@ -206,7 +206,7 @@ impl YangContextCache {
&yang_lib_ref.yang_library_path(),
DataFormat::XML,
&search_dir.as_path(),
- yang4::context::ContextFlags::empty(),
+ yang5::context::ContextFlags::empty(),
)?;
info!(
@@ -803,7 +803,6 @@ fn log_statistics(stats: &ValidationStats, total_count: usize) {
info!(" Failed: {}", stats.failed);
info!(" Context errors: {}", stats.context_errors);
info!(" No schema-id (skipped): {}", stats.no_schema);
- info!(" No ietf-tm schema (skipped): {}", stats.no_tm_schema);
info!("");
let validated_total = stats.passed + stats.failed;
@@ -846,12 +845,8 @@ async fn main() -> Result<()> {
let sr_client = SchemaRegistryClient::new(sr_config);
// Create placeholder subscription info (reused for all schemas)
- let subscription_info = SubscriptionInfo::new_empty(
- SocketAddr::new(IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), 0),
- None,
- SocketAddr::new(IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), 0),
- 0,
- );
+ let subscription_info =
+ SubscriptionInfo::new_empty(IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), 0);
// Create YANG context cache
let mut yang_ctx_cache = YangContextCache::new();
@@ -971,64 +966,36 @@ async fn main() -> Result<()> {
Ok(yang_ctx) => {
debug!("Using YANG context for schema ID: {}", schema_id);
- // Get Telemetry Message module
- let tm_module =
- yang_ctx.get_module_implemented("ietf-telemetry-message");
-
- if tm_module.is_none() {
- warn!(
- "Message validation SKIPPED [partition: {}, offset: {}, key: {}]: \
- ietf-telemetry-message schema not found",
- borrowed_message.partition(),
- borrowed_message.offset(),
- String::from_utf8_lossy(key)
- );
- validation_stats.no_tm_schema += 1;
- } else {
- // Extract ietf-telemetry-message extension instance
- let tm_ext = tm_module.as_ref().and_then(|m| m.extensions().next());
-
- // Validate message payload
- let validation_result = match &tm_ext {
- Some(ext) => yang4::data::DataTree::parse_ext_string(
- ext,
- payload,
- DataFormat::JSON,
- DataParserFlags::STRICT,
- DataValidationFlags::PRESENT,
- ),
- // Support legacy ietf-telemetry-message without YANG structure
- None => yang4::data::DataTree::parse_string(
- yang_ctx,
- payload,
- DataFormat::JSON,
- DataParserFlags::STRICT,
- DataValidationFlags::PRESENT,
- ),
- };
-
- match validation_result {
- Ok(_) => {
- debug!(
- "Message validation PASSED [partition: {}, offset: {}, key: {}, schema_id: {}]",
- borrowed_message.partition(),
- borrowed_message.offset(),
- String::from_utf8_lossy(key),
- schema_id
- );
- validation_stats.passed += 1;
- }
- Err(err) => {
- error!(
- "Message validation FAILED [partition: {}, offset: {}, key: {}, schema_id: {}]: {}",
- borrowed_message.partition(),
- borrowed_message.offset(),
- String::from_utf8_lossy(key),
- schema_id,
- err
- );
- validation_stats.failed += 1;
- }
+ // Validate message payload against YANG schemas.
+ let validation_result = yang5::data::DataTree::parse_string(
+ yang_ctx,
+ payload,
+ DataFormat::JSON,
+ DataParserFlags::STRICT | DataParserFlags::ANYDATA_STRICT,
+ DataValidationFlags::PRESENT,
+ );
+
+ match validation_result {
+ Ok(_) => {
+ debug!(
+ "Message validation PASSED [partition: {}, offset: {}, key: {}, schema_id: {}]",
+ borrowed_message.partition(),
+ borrowed_message.offset(),
+ String::from_utf8_lossy(key),
+ schema_id
+ );
+ validation_stats.passed += 1;
+ }
+ Err(err) => {
+ error!(
+ "Message validation FAILED [partition: {}, offset: {}, key: {}, schema_id: {}]: {}",
+ borrowed_message.partition(),
+ borrowed_message.offset(),
+ String::from_utf8_lossy(key),
+ schema_id,
+ err
+ );
+ validation_stats.failed += 1;
}
}
}
diff --git a/crates/collector/src/lib.rs b/crates/collector/src/lib.rs
index f4ae77b2..50412619 100644
--- a/crates/collector/src/lib.rs
+++ b/crates/collector/src/lib.rs
@@ -1,3 +1,4 @@
+// Copyright (C) 2026-present The NetCalyx Authors.
// Copyright (C) 2024-present The NetGauze Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +25,7 @@ use crate::publishers::http::{HttpPublisherActorHandle, Message};
use crate::publishers::kafka_avro::KafkaAvroPublisherActorHandle;
use crate::publishers::kafka_json::KafkaJsonPublisherActorHandle;
use crate::publishers::kafka_yang::KafkaYangPublisherActorHandle;
-use crate::yang_push::enrichment::YangPushEnrichmentActorHandle;
+use crate::yang_push::enrichment::{EnrichedNotification, YangPushEnrichmentActorHandle};
use futures_util::StreamExt;
use futures_util::stream::FuturesUnordered;
@@ -36,11 +37,9 @@ use netcalyx_flow_service::flow_supervisor::FlowCollectorsSupervisorActorHandle;
use netcalyx_udp_notif_pkt::raw::MediaType;
use netcalyx_udp_notif_service::UdpNotifRequest;
use netcalyx_udp_notif_service::supervisor::UdpNotifSupervisorHandle;
-use netcalyx_yang_push::ContentId;
use netcalyx_yang_push::cache::actor::CacheActorHandle;
use netcalyx_yang_push::cache::fetcher::{NetconfYangLibraryFetcher, RetryConfig};
-use netcalyx_yang_push::cache::storage::SubscriptionInfo;
-use netcalyx_yang_push::model::telemetry::{Manifest, TelemetryMessageWrapper};
+use netcalyx_yang_push::model::telemetry::Manifest;
use netcalyx_yang_push::validation::ValidationActorHandle;
use shadow_rs::shadow;
use std::net::IpAddr;
@@ -926,10 +925,10 @@ fn serialize_udp_notif(
}
fn serialize_telemetry_json(
- input: (Option, SubscriptionInfo, TelemetryMessageWrapper),
+ input: EnrichedNotification,
_writer_id: String,
) -> Result<(Option, serde_json::Value), UdpNotifSerializationError> {
- let tmw = input.2;
+ let tmw = input.message;
let ip = tmw.message().telemetry_message_metadata().export_address();
let value = serde_json::to_value(tmw)?;
let key = serde_json::Value::String(ip.to_string());
@@ -1054,6 +1053,7 @@ mod tests {
use super::*;
use bytes::Bytes;
use netcalyx_udp_notif_pkt::raw::UdpNotifPacket;
+ use netcalyx_udp_notif_service::SessionInfo;
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
@@ -1070,7 +1070,10 @@ mod tests {
Bytes::from(&[0xffu8, 0xffu8][..]),
);
- let request = Arc::new(UdpNotifRequest::new(collector, None, peer, pkt));
+ let request = Arc::new(UdpNotifRequest::new(
+ SessionInfo::new(collector, None, peer),
+ pkt,
+ ));
let serialized = serialize_udp_notif(request.clone(), writer_id.clone());
assert!(matches!(
serialized,
@@ -1112,12 +1115,13 @@ mod tests {
}
);
let request_invalid = Arc::new(UdpNotifRequest::new(
- collector,
- None,
- peer,
+ SessionInfo::new(collector, None, peer),
pkt_invalid_json,
));
- let request_good = Arc::new(UdpNotifRequest::new(collector, None, peer, pkt));
+ let request_good = Arc::new(UdpNotifRequest::new(
+ SessionInfo::new(collector, None, peer),
+ pkt,
+ ));
let result_invalid = serialize_udp_notif(request_invalid, writer_id.clone());
let serialized =
serialize_udp_notif(request_good, writer_id.clone()).expect("failed to serialize json");
@@ -1168,12 +1172,13 @@ mod tests {
);
let request_invalid = Arc::new(UdpNotifRequest::new(
- collector,
- None,
- peer,
+ SessionInfo::new(collector, None, peer),
pkt_invalid_utf8,
));
- let request_good = Arc::new(UdpNotifRequest::new(collector, None, peer, pkt));
+ let request_good = Arc::new(UdpNotifRequest::new(
+ SessionInfo::new(collector, None, peer),
+ pkt,
+ ));
let result_invalid = serialize_udp_notif(request_invalid, writer_id.clone());
let serialized =
serialize_udp_notif(request_good, writer_id.clone()).expect("failed to serialize json");
@@ -1226,8 +1231,14 @@ mod tests {
}
);
- let request_invalid = Arc::new(UdpNotifRequest::new(collector, None, peer, pkt_invalid));
- let request_good = Arc::new(UdpNotifRequest::new(collector, None, peer, pkt));
+ let request_invalid = Arc::new(UdpNotifRequest::new(
+ SessionInfo::new(collector, None, peer),
+ pkt_invalid,
+ ));
+ let request_good = Arc::new(UdpNotifRequest::new(
+ SessionInfo::new(collector, None, peer),
+ pkt,
+ ));
let result_invalid = serialize_udp_notif(request_invalid, writer_id.clone());
let serialized =
serialize_udp_notif(request_good, writer_id.clone()).expect("failed to serialize json");
diff --git a/crates/collector/src/publishers/kafka_yang.rs b/crates/collector/src/publishers/kafka_yang.rs
index 21a86c90..394a2177 100644
--- a/crates/collector/src/publishers/kafka_yang.rs
+++ b/crates/collector/src/publishers/kafka_yang.rs
@@ -1,3 +1,4 @@
+// Copyright (C) 2026-present The NetCalyx Authors.
// Copyright (C) 2025-present The NetGauze Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -430,7 +431,7 @@ where
return if let Some(default_schema_id) = self.default_schema_id {
if let Some(subscription_info) = subscription_info {
warn!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -449,7 +450,7 @@ where
} else {
if let Some(subscription_info) = subscription_info {
warn!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -470,7 +471,7 @@ where
if let Some(&schema_id) = self.schema_id_cache.get(id) {
if let Some(subscription_info) = subscription_info {
trace!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
diff --git a/crates/collector/src/yang_push/config.rs b/crates/collector/src/yang_push/config.rs
index 87c79e6f..39970096 100644
--- a/crates/collector/src/yang_push/config.rs
+++ b/crates/collector/src/yang_push/config.rs
@@ -1,3 +1,4 @@
+// Copyright (C) 2026-present The NetCalyx Authors.
// Copyright (C) 2025-present The NetGauze Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,9 +22,9 @@
//! JSON format.
use crate::publishers::kafka_yang::YangConverter;
+use crate::yang_push::enrichment::EnrichedNotification;
use netcalyx_yang_push::ContentId;
use netcalyx_yang_push::cache::storage::{SubscriptionInfo, YangLibraryReference};
-use netcalyx_yang_push::model::telemetry::TelemetryMessageWrapper;
use serde::{Deserialize, Serialize};
#[derive(Debug, strum_macros::Display)]
@@ -81,12 +82,7 @@ impl TelemetryYangConverter {
}
}
-impl
- YangConverter<
- (Option, SubscriptionInfo, TelemetryMessageWrapper),
- TelemetryYangConverterError,
- > for TelemetryYangConverter
-{
+impl YangConverter for TelemetryYangConverter {
fn subject_prefix(&self) -> Option<&str> {
self.subject_prefix.as_deref()
}
@@ -103,38 +99,27 @@ impl
self.extension_yang_lib_ref.as_ref()
}
- fn content_id(
- &self,
- input: &(Option, SubscriptionInfo, TelemetryMessageWrapper),
- ) -> Option {
- input.0.clone()
+ fn content_id(&self, input: &EnrichedNotification) -> Option {
+ input.cached_content_id.clone()
}
- fn get_key(
- &self,
- input: &(Option, SubscriptionInfo, TelemetryMessageWrapper),
- ) -> Option {
- let (_, subscription_info, _) = input;
- let ip = subscription_info.peer().ip();
+ fn get_key(&self, input: &EnrichedNotification) -> Option {
+ let ip = input.subscription_info.peer_ip();
Some(serde_json::Value::String(ip.to_string()))
}
fn serialize_json(
&self,
- input: (Option, SubscriptionInfo, TelemetryMessageWrapper),
+ input: EnrichedNotification,
) -> Result, TelemetryYangConverterError> {
- let telemetry_message_wrapper = input.2;
- serde_json::to_vec(&telemetry_message_wrapper).map_err(Into::into)
+ serde_json::to_vec(&input.message).map_err(Into::into)
}
- fn subscription_info(
- &self,
- input: &(Option, SubscriptionInfo, TelemetryMessageWrapper),
- ) -> Option {
- if input.1.is_empty() {
+ fn subscription_info(&self, input: &EnrichedNotification) -> Option {
+ if input.subscription_info.is_empty() {
None
} else {
- Some(input.1.clone())
+ Some(input.subscription_info.clone())
}
}
}
@@ -145,14 +130,13 @@ mod tests {
use chrono::TimeZone;
use netcalyx_netconf_proto::yang_push::identities::{Encoding, Transport};
use netcalyx_netconf_proto::yang_push::subscription::YangPushModuleVersion;
+ use netcalyx_udp_notif_service::SessionInfo;
use netcalyx_yang_push::model::telemetry::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
fn create_test_subscription_info(ip: IpAddr) -> SubscriptionInfo {
SubscriptionInfo::new(
- SocketAddr::from(([127, 0, 0, 1], 10000)),
- None,
- SocketAddr::new(ip, 8080),
+ ip,
1,
netcalyx_udp_notif_pkt::notification::Target::new_datastore(
"ietf-datastores:operational".to_string(),
@@ -223,7 +207,16 @@ mod tests {
let sub_info = create_test_subscription_info(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
let msg = create_test_telemetry_message_wrapper();
- let input = (content_id.clone(), sub_info, msg);
+ let input = EnrichedNotification {
+ cached_content_id: content_id.clone(),
+ subscription_info: sub_info,
+ session: SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 0)),
+ None,
+ SocketAddr::from(([127, 0, 0, 1], 0)),
+ ),
+ message: msg,
+ };
assert_eq!(converter.content_id(&input), content_id);
}
@@ -234,7 +227,16 @@ mod tests {
let sub_info = create_test_subscription_info(ip);
let msg = create_test_telemetry_message_wrapper();
- let input = (None, sub_info, msg);
+ let input = EnrichedNotification {
+ cached_content_id: None,
+ subscription_info: sub_info,
+ session: SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 0)),
+ None,
+ SocketAddr::from(([127, 0, 0, 1], 0)),
+ ),
+ message: msg,
+ };
let key = converter.get_key(&input).unwrap();
assert_eq!(key, serde_json::Value::String("192.168.1.1".to_string()));
}
@@ -246,7 +248,16 @@ mod tests {
let sub_info = create_test_subscription_info(ip);
let msg = create_test_telemetry_message_wrapper();
- let input = (None, sub_info, msg);
+ let input = EnrichedNotification {
+ cached_content_id: None,
+ subscription_info: sub_info,
+ session: SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 0)),
+ None,
+ SocketAddr::from(([127, 0, 0, 1], 0)),
+ ),
+ message: msg,
+ };
let key = converter.get_key(&input).unwrap();
assert_eq!(key, serde_json::Value::String("2001:db8::1".to_string()));
}
@@ -259,7 +270,16 @@ mod tests {
let expected = serde_json::to_value(&msg).unwrap();
// Call serialize_json to serialize into bytes
- let input = (None, sub_info, msg);
+ let input = EnrichedNotification {
+ cached_content_id: None,
+ subscription_info: sub_info,
+ session: SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 0)),
+ None,
+ SocketAddr::from(([127, 0, 0, 1], 0)),
+ ),
+ message: msg,
+ };
let result = converter.serialize_json(input);
assert!(result.is_ok());
diff --git a/crates/collector/src/yang_push/enrichment.rs b/crates/collector/src/yang_push/enrichment.rs
index 4586984f..cb6fffae 100644
--- a/crates/collector/src/yang_push/enrichment.rs
+++ b/crates/collector/src/yang_push/enrichment.rs
@@ -36,12 +36,13 @@ use crate::yang_push::{
};
use chrono::Utc;
use netcalyx_udp_notif_pkt::decoded::{UdpNotifPacketDecoded, UdpNotifPayload};
-use netcalyx_udp_notif_service::OTL_UDP_NOTIF_PUBLISHER_ID_KEY;
+use netcalyx_udp_notif_service::{OTL_UDP_NOTIF_PUBLISHER_ID_KEY, SessionInfo};
use netcalyx_yang_push::cache::storage::SubscriptionInfo;
use netcalyx_yang_push::model::telemetry::{
EventType, Label, Manifest, NetworkOperatorMetadata, SessionProtocol, TelemetryMessage,
TelemetryMessageMetadata, TelemetryMessageWrapper, YangPushSubscriptionMetadata,
};
+use netcalyx_yang_push::validation::ValidatedNotification;
use netcalyx_yang_push::{
ContentId, OTL_YANG_PUSH_CACHED_CONTENT_ID_KEY, OTL_YANG_PUSH_SUBSCRIPTION_ID_KEY,
OTL_YANG_PUSH_SUBSCRIPTION_ROUTER_CONTENT_ID_KEY, OTL_YANG_PUSH_SUBSCRIPTION_TARGET_KEY,
@@ -150,15 +151,31 @@ impl YangPushEnrichmentStats {
}
}
+/// The output of the enrichment stage: a `TelemetryMessage` assembled from a
+/// validated UDP-Notif packet, together with the subscription identity and
+/// transport session that produced it.
+///
+/// - `cached_content_id`: the YANG schema fingerprint used for validation, or
+/// `None` when the packet was forwarded unvalidated.
+/// - `subscription_info`: subscription identity (peer IP, target, modules…).
+/// - `session`: transport session context (collector, interface, full peer
+/// `SocketAddr`) — available for logging, routing, and Kafka key selection.
+/// - `message`: the enriched telemetry message ready for publishing.
+#[derive(Debug)]
+pub struct EnrichedNotification {
+ pub cached_content_id: Option,
+ pub subscription_info: SubscriptionInfo,
+ pub session: SessionInfo,
+ pub message: TelemetryMessageWrapper,
+}
+
/// Actor responsible for enriching YANG-Push notifications.
/// Sends enriched TelemetryMessage objects.
struct YangPushEnrichmentActor {
cmd_rx: mpsc::Receiver,
enrichment_rx: async_channel::Receiver,
- validated_rx:
- async_channel::Receiver<(Option, SubscriptionInfo, UdpNotifPacketDecoded)>,
- enriched_tx:
- async_channel::Sender<(Option, SubscriptionInfo, TelemetryMessageWrapper)>,
+ validated_rx: async_channel::Receiver,
+ enriched_tx: async_channel::Sender,
labels: HashMap>,
manifest: Manifest,
stats: YangPushEnrichmentStats,
@@ -168,16 +185,8 @@ impl YangPushEnrichmentActor {
fn new(
cmd_rx: mpsc::Receiver,
enrichment_rx: async_channel::Receiver,
- validated_rx: async_channel::Receiver<(
- Option,
- SubscriptionInfo,
- UdpNotifPacketDecoded,
- )>,
- enriched_tx: async_channel::Sender<(
- Option,
- SubscriptionInfo,
- TelemetryMessageWrapper,
- )>,
+ validated_rx: async_channel::Receiver,
+ enriched_tx: async_channel::Sender,
manifest: Manifest,
stats: YangPushEnrichmentStats,
) -> Self {
@@ -340,12 +349,13 @@ impl YangPushEnrichmentActor {
&mut self,
content_id: Option<&ContentId>,
subscription_info: &SubscriptionInfo,
+ session: &SessionInfo,
decoded_packet: &UdpNotifPacketDecoded,
) -> Result {
if decoded_packet.notification_type().is_none() {
return Err(YangPushEnrichmentActorError::NotificationWithoutContent);
}
- let peer = subscription_info.peer();
+ let peer_ip = subscription_info.peer_ip();
let message_id = decoded_packet.message_id();
let publisher_id = decoded_packet.publisher_id();
let notification_type = decoded_packet
@@ -355,7 +365,7 @@ impl YangPushEnrichmentActor {
let labels: Option> = self
.labels
- .get(&peer.ip())
+ .get(&peer_ip)
.map(|l_map| l_map.values().cloned().map(|wl| wl.label).collect());
// Match on the wrapper and process the notification content
@@ -378,17 +388,17 @@ impl YangPushEnrichmentActor {
EventType::Log,
None, // we don't set sequence numbers for now
SessionProtocol::YangPush, // only option at the moment
- peer.ip(),
- Some(peer.port()),
- None,
- None,
+ peer_ip,
+ Some(session.peer().port()),
+ Some(session.collector().ip()),
+ Some(session.collector().port()),
subscription_metadata,
);
// Re-serialize the UDP-Notif payload into JSON
let json_payload = serde_json::to_value(decoded_packet.payload()).map_err(|err| {
error!(
- peer=%peer,
+ peer_ip=%peer_ip,
message_id,
publisher_id,
subscription_id=subscription_info.id(),
@@ -444,17 +454,13 @@ impl YangPushEnrichmentActor {
msg = self.validated_rx.recv() => {
match msg {
Ok(msg) => {
- let (content_id, subscription_info, pkt) = msg;
- let peer = subscription_info.peer();
+ let ValidatedNotification { cached_content_id: content_id, subscription_info, session, packet: pkt } = msg;
+ let peer_ip = subscription_info.peer_ip();
let publisher_id = pkt.publisher_id();
let peer_tags = [
opentelemetry::KeyValue::new(
"network.peer.address",
- format!("{}", peer.ip()),
- ),
- opentelemetry::KeyValue::new(
- "network.peer.port",
- opentelemetry::Value::I64(peer.port().into()),
+ format!("{peer_ip}"),
),
opentelemetry::KeyValue::new(
OTL_UDP_NOTIF_PUBLISHER_ID_KEY,
@@ -480,9 +486,14 @@ impl YangPushEnrichmentActor {
self.stats.received_messages.add(1, &peer_tags);
// Process the payload and send the enriched TelemetryMessage
- match self.process_decoded_udp_notif_packet(content_id.as_ref(), &subscription_info, &pkt) {
+ match self.process_decoded_udp_notif_packet(content_id.as_ref(), &subscription_info, &session, &pkt) {
Ok(telemetry_message) => {
- if let Err(err) = self.enriched_tx.send((content_id, subscription_info, telemetry_message)).await {
+ if let Err(err) = self.enriched_tx.send(EnrichedNotification {
+ cached_content_id: content_id,
+ subscription_info,
+ session,
+ message: telemetry_message,
+ }).await {
error!("YangPushEnrichmentActor send error: {err}");
self.stats.send_error.add(1, &peer_tags);
} else {
@@ -519,18 +530,13 @@ impl std::error::Error for YangPushEnrichmentActorHandleError {}
pub struct YangPushEnrichmentActorHandle {
cmd_send: mpsc::Sender,
enrichment_tx: async_channel::Sender,
- enriched_rx:
- async_channel::Receiver<(Option, SubscriptionInfo, TelemetryMessageWrapper)>,
+ enriched_rx: async_channel::Receiver,
}
impl YangPushEnrichmentActorHandle {
pub fn new(
buffer_size: usize,
- validated_rx: async_channel::Receiver<(
- Option,
- SubscriptionInfo,
- UdpNotifPacketDecoded,
- )>,
+ validated_rx: async_channel::Receiver,
manifest: Manifest,
stats: either::Either,
) -> (JoinHandle>, Self) {
@@ -565,10 +571,7 @@ impl YangPushEnrichmentActorHandle {
.map_err(|_| YangPushEnrichmentActorHandleError::SendError)
}
- pub fn subscribe(
- &self,
- ) -> async_channel::Receiver<(Option, SubscriptionInfo, TelemetryMessageWrapper)>
- {
+ pub fn subscribe(&self) -> async_channel::Receiver {
self.enriched_rx.clone()
}
}
@@ -625,7 +628,7 @@ mod tests {
#[allow(clippy::type_complexity)]
fn create_actor_handle() -> (
- async_channel::Sender<(Option, SubscriptionInfo, UdpNotifPacketDecoded)>,
+ async_channel::Sender,
Manifest,
JoinHandle>,
YangPushEnrichmentActorHandle,
@@ -655,7 +658,7 @@ mod tests {
}
fn create_subscription_started(
- peer: SocketAddr,
+ peer_ip: IpAddr,
id: SubscriptionId,
) -> (SubscriptionInfo, serde_json::Value, UdpNotifPacketDecoded) {
let payload = json!({
@@ -679,7 +682,6 @@ mod tests {
}
});
- let collector = SocketAddr::from(([127, 0, 0, 1], 10000));
let packet = UdpNotifPacket::new(
MediaType::YangDataJson,
1234,
@@ -690,9 +692,7 @@ mod tests {
let decoded: UdpNotifPacketDecoded = (&packet).try_into().unwrap();
let subscription_info = SubscriptionInfo::new(
- collector,
- None,
- peer,
+ peer_ip,
id,
Target::new_datastore(
DatastoreName::Operational.to_string(),
@@ -722,21 +722,30 @@ mod tests {
async fn test_process_payload_empty_subscription() {
// Set up the enrichment actor and input test data
let (msgs_tx, test_manifest, join_handle, actor_handle) = create_actor_handle();
- let collector = SocketAddr::from(([127, 0, 0, 1], 10000));
- let peer = SocketAddr::from(([127, 0, 0, 1], 12345));
- let (_subscription_info, json_payload, decoded) = create_subscription_started(peer, 1);
- let empty_subscription_info = SubscriptionInfo::new_empty(collector, None, peer, 1);
+ let peer_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let (_subscription_info, json_payload, decoded) = create_subscription_started(peer_ip, 1);
+ let empty_subscription_info = SubscriptionInfo::new_empty(peer_ip, 1);
msgs_tx
- .send((
- Some(empty_subscription_info.content_id().clone()),
- empty_subscription_info.clone(),
- decoded.clone(),
- ))
+ .send(ValidatedNotification {
+ cached_content_id: Some(empty_subscription_info.content_id().clone()),
+ subscription_info: empty_subscription_info.clone(),
+ session: SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 10000)),
+ None,
+ SocketAddr::new(peer_ip, 0),
+ ),
+ packet: decoded.clone(),
+ })
.await
.expect("Failed to send message to the actor");
tokio::task::yield_now().await;
- let (received_content_id, received_subscription_info, received_enriched) = actor_handle
+ let EnrichedNotification {
+ cached_content_id: received_content_id,
+ subscription_info: received_subscription_info,
+ message: received_enriched,
+ ..
+ } = actor_handle
.enriched_rx
.recv()
.await
@@ -756,9 +765,9 @@ mod tests {
None,
SessionProtocol::YangPush,
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
- Some(12345),
- None,
- None,
+ Some(0),
+ Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
+ Some(10000),
None,
),
Some(test_manifest.clone()),
@@ -785,19 +794,29 @@ mod tests {
async fn test_process_payload_envelope() {
// Set up the enrichment actor and input test data
let (msgs_tx, test_manifest, join_handle, actor_handle) = create_actor_handle();
- let peer = SocketAddr::from(([127, 0, 0, 1], 12345));
- let (subscription_info, json_payload, decoded) = create_subscription_started(peer, 1);
+ let peer_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let (subscription_info, json_payload, decoded) = create_subscription_started(peer_ip, 1);
msgs_tx
- .send((
- Some(subscription_info.content_id().clone()),
- subscription_info.clone(),
- decoded.clone(),
- ))
+ .send(ValidatedNotification {
+ cached_content_id: Some(subscription_info.content_id().clone()),
+ subscription_info: subscription_info.clone(),
+ session: SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 10000)),
+ None,
+ SocketAddr::new(peer_ip, 0),
+ ),
+ packet: decoded.clone(),
+ })
.await
.expect("Failed to send message to the actor");
tokio::task::yield_now().await;
- let (received_content_id, received_subscription_info, received_enriched) = actor_handle
+ let EnrichedNotification {
+ cached_content_id: received_content_id,
+ subscription_info: received_subscription_info,
+ message: received_enriched,
+ ..
+ } = actor_handle
.enriched_rx
.recv()
.await
@@ -818,9 +837,9 @@ mod tests {
None,
SessionProtocol::YangPush,
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
- Some(12345),
- None,
- None,
+ Some(0),
+ Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
+ Some(10000),
Some(expected_metadata),
),
Some(test_manifest.clone()),
@@ -845,8 +864,7 @@ mod tests {
#[test]
fn test_process_payload_envelope_without_content() {
let mut actor = create_actor();
- let collector = SocketAddr::from(([127, 0, 0, 1], 10000));
- let peer = SocketAddr::from(([127, 0, 0, 1], 12345));
+ let peer_ip = IpAddr::from([127, 0, 0, 1]);
// Create a UdpNotifPayload without content
let payload = json!({
@@ -867,11 +885,17 @@ mod tests {
Bytes::from(payload),
);
- let subscription_info = SubscriptionInfo::new_empty(collector, None, peer, 1);
+ let subscription_info = SubscriptionInfo::new_empty(peer_ip, 1);
// Attempt to decode the packet (should succeed)
let decoded: UdpNotifPacketDecoded = (&packet).try_into().unwrap();
+ let session = SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 10000)),
+ None,
+ SocketAddr::new(peer_ip, 0),
+ );
- let result = actor.process_decoded_udp_notif_packet(None, &subscription_info, &decoded);
+ let result =
+ actor.process_decoded_udp_notif_packet(None, &subscription_info, &session, &decoded);
assert_eq!(
result,
diff --git a/crates/udp-notif-service/src/actor.rs b/crates/udp-notif-service/src/actor.rs
index 0e784177..788ebcf5 100644
--- a/crates/udp-notif-service/src/actor.rs
+++ b/crates/udp-notif-service/src/actor.rs
@@ -115,8 +115,8 @@
//! ```
use crate::{
- ActorId, OTL_UDP_NOTIF_PUBLISHER_ID_KEY, SubscriberId, Subscription, UdpNotifPacket,
- UdpNotifReceiver, UdpNotifRequest, UdpNotifSender, create_udp_notif_channel,
+ ActorId, OTL_UDP_NOTIF_PUBLISHER_ID_KEY, SessionInfo, SubscriberId, Subscription,
+ UdpNotifPacket, UdpNotifReceiver, UdpNotifRequest, UdpNotifSender, create_udp_notif_channel,
};
use bytes::{Bytes, BytesMut};
use futures_util::StreamExt;
@@ -645,9 +645,11 @@ impl UdpNotifActor {
);
let mut send_handlers = vec![];
let request = Arc::new(UdpNotifRequest::new(
- self.socket_addr,
- self.interface_bind.clone().map(String::into_boxed_str),
- peer,
+ SessionInfo::new(
+ self.socket_addr,
+ self.interface_bind.clone().map(String::into_boxed_str),
+ peer,
+ ),
msg,
));
for (id, tx) in &self.subscribers {
@@ -1322,15 +1324,11 @@ mod tests {
// Create references to compare with the received ones
let ref1 = Ok(Ok(Arc::new(UdpNotifRequest::new(
- handle.local_addr(),
- None,
- local_addr1,
+ SessionInfo::new(handle.local_addr(), None, local_addr1),
pkt1.clone(),
))));
let ref2 = Ok(Ok(Arc::new(UdpNotifRequest::new(
- handle.local_addr(),
- None,
- local_addr2,
+ SessionInfo::new(handle.local_addr(), None, local_addr2),
pkt2.clone(),
))));
diff --git a/crates/udp-notif-service/src/lib.rs b/crates/udp-notif-service/src/lib.rs
index 67425346..9794dcfa 100644
--- a/crates/udp-notif-service/src/lib.rs
+++ b/crates/udp-notif-service/src/lib.rs
@@ -38,38 +38,65 @@ pub type ActorId = u32;
/// Type alias to that YANG-Push subscription ID as defined in [RFC8641](https://datatracker.ietf.org/doc/html/rfc8641)
pub type SubscriberId = u32;
-/// The UDP-Notif packet and the peer [SocketAddr] that sent it.
+/// Transport session context for a UDP-Notif packet.
+///
+/// - **collector**: The local socket address on which the collector received
+/// the packet.
+/// - **interface**: The network interface or VRF the collector socket is bound
+/// to, if any.
+/// - **peer**: The full remote socket address (IP + source port) of the sending
+/// device.
+#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
+pub struct SessionInfo {
+ pub collector: SocketAddr,
+ pub interface: Option>,
+ pub peer: SocketAddr,
+}
+
+impl SessionInfo {
+ pub fn new(collector: SocketAddr, interface: Option>, peer: SocketAddr) -> Self {
+ Self {
+ collector,
+ interface,
+ peer,
+ }
+ }
+
+ pub const fn collector(&self) -> SocketAddr {
+ self.collector
+ }
+
+ pub fn interface(&self) -> Option<&str> {
+ self.interface.as_deref()
+ }
+
+ pub const fn peer(&self) -> SocketAddr {
+ self.peer
+ }
+}
+
+/// The UDP-Notif packet together with the transport session it arrived on.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UdpNotifRequest {
- collector_address: SocketAddr,
- collector_interface: Option>,
- peer_address: SocketAddr,
+ session: SessionInfo,
packet: UdpNotifPacket,
}
impl UdpNotifRequest {
- pub fn new(
- collector_address: SocketAddr,
- collector_interface: Option>,
- peer_address: SocketAddr,
- packet: UdpNotifPacket,
- ) -> Self {
- Self {
- collector_address,
- collector_interface,
- peer_address,
- packet,
- }
+ pub fn new(session: SessionInfo, packet: UdpNotifPacket) -> Self {
+ Self { session, packet }
+ }
+ pub const fn session(&self) -> &SessionInfo {
+ &self.session
}
-
pub const fn collector_address(&self) -> SocketAddr {
- self.collector_address
+ self.session.collector
}
pub fn collector_interface(&self) -> Option<&str> {
- self.collector_interface.as_deref()
+ self.session.interface.as_deref()
}
pub const fn peer_address(&self) -> SocketAddr {
- self.peer_address
+ self.session.peer
}
pub const fn packet(&self) -> &UdpNotifPacket {
&self.packet
diff --git a/crates/yang-push/Cargo.toml b/crates/yang-push/Cargo.toml
index a6f20159..4bc9bccd 100644
--- a/crates/yang-push/Cargo.toml
+++ b/crates/yang-push/Cargo.toml
@@ -22,6 +22,7 @@ netcalyx-udp-notif-service = { workspace = true }
netcalyx-netconf-proto = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
+strum = { workspace = true }
strum_macros = { workspace = true }
chrono = { workspace = true, default-features = true, features = ["serde"] }
tokio = { workspace = true, default-features = false }
@@ -29,7 +30,7 @@ tracing = { workspace = true }
opentelemetry = { workspace = true, features = ["metrics", "trace", "logs"] }
either = { workspace = true }
async-channel = { workspace = true }
-yang4 = { workspace = true }
+yang5 = { workspace = true }
rustc-hash = { workspace = true }
quick-xml = { workspace = true }
futures-util = { workspace = true }
diff --git a/crates/yang-push/src/cache/actor.rs b/crates/yang-push/src/cache/actor.rs
index e411090d..224eddd9 100644
--- a/crates/yang-push/src/cache/actor.rs
+++ b/crates/yang-push/src/cache/actor.rs
@@ -224,8 +224,8 @@ use crate::{
use futures_util::StreamExt;
use futures_util::stream::FuturesUnordered;
use netcalyx_netconf_proto::yang_push::types::SubscriptionId;
+use netcalyx_udp_notif_service::SessionInfo;
use rustc_hash::FxHashMap;
-use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@@ -305,22 +305,26 @@ pub enum CacheActorCommand {
#[derive(Debug)]
pub enum CacheLookupCommand {
- LookupBySubscriptionInfo(SubscriptionInfo, async_channel::Sender),
+ LookupBySubscriptionInfo(
+ SubscriptionInfo,
+ SessionInfo,
+ async_channel::Sender,
+ ),
- LookupBySubscriptionInfoOneShot(SubscriptionInfo, oneshot::Sender),
+ LookupBySubscriptionInfoOneShot(
+ SubscriptionInfo,
+ SessionInfo,
+ oneshot::Sender,
+ ),
LookupBySubscriptionId {
- collector: SocketAddr,
- interface: Option,
- peer: SocketAddr,
+ session: SessionInfo,
subscription_id: SubscriptionId,
tx: async_channel::Sender,
},
LookupBySubscriptionIdOneShot {
- collector: SocketAddr,
- interface: Option,
- peer: SocketAddr,
+ session: SessionInfo,
subscription_id: SubscriptionId,
tx: oneshot::Sender,
},
@@ -339,37 +343,44 @@ pub enum CacheLookupCommand {
impl std::fmt::Display for CacheLookupCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- Self::LookupBySubscriptionInfo(subscription_info, _) => {
- write!(f, "lookup by subscription info {subscription_info}")
+ Self::LookupBySubscriptionInfo(subscription_info, session, _) => {
+ write!(
+ f,
+ "lookup by subscription info {subscription_info} from peer {}",
+ session.peer()
+ )
}
- Self::LookupBySubscriptionInfoOneShot(subscription_info, _) => {
+ Self::LookupBySubscriptionInfoOneShot(subscription_info, session, _) => {
write!(
f,
- "lookup by subscription info {subscription_info} (one shot)"
+ "lookup by subscription info {subscription_info} from peer {} (one shot)",
+ session.peer()
)
}
Self::LookupBySubscriptionId {
- collector,
- interface,
- peer,
+ session,
subscription_id,
tx: _tx,
} => {
write!(
f,
- "lookup by subscription id {subscription_id} from peer {peer}, collector {collector}, interface {interface:?}",
+ "lookup by subscription id {subscription_id} from peer {}, collector {}, interface {:?}",
+ session.peer(),
+ session.collector(),
+ session.interface(),
)
}
Self::LookupBySubscriptionIdOneShot {
- collector,
- interface,
- peer,
+ session,
subscription_id,
tx: _tx,
} => {
write!(
f,
- "lookup by subscription id {subscription_id} from peer {peer}, collector {collector}, interface {interface:?} (one shot)",
+ "lookup by subscription id {subscription_id} from peer {}, collector {}, interface {:?} (one shot)",
+ session.peer(),
+ session.collector(),
+ session.interface(),
)
}
Self::LookupByContentId(content_id, _) => {
@@ -455,7 +466,7 @@ impl CacheActor {
match sender.send(response).await {
Ok(_) => {
debug!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -466,7 +477,7 @@ impl CacheActor {
}
Err(err) => {
warn!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -525,7 +536,7 @@ impl CacheActor {
match sender.send(response) {
Ok(_) => {
debug!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -536,7 +547,7 @@ impl CacheActor {
}
Err(_err) => {
warn!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -588,11 +599,7 @@ impl CacheActor {
&[
opentelemetry::KeyValue::new(
"network.peer.address",
- format!("{}", subscription_info.peer().ip()),
- ),
- opentelemetry::KeyValue::new(
- "network.peer.port",
- opentelemetry::Value::I64(subscription_info.peer().port().into()),
+ format!("{}", subscription_info.peer_ip()),
),
opentelemetry::KeyValue::new(
OTL_YANG_PUSH_SUBSCRIPTION_ID_KEY,
@@ -610,7 +617,7 @@ impl CacheActor {
],
);
warn!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -632,7 +639,7 @@ impl CacheActor {
.await
.map_err(|error| {
warn!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -641,7 +648,7 @@ impl CacheActor {
);
});
debug!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -655,11 +662,7 @@ impl CacheActor {
let otl_tags = [
opentelemetry::KeyValue::new(
"network.peer.address",
- format!("{}", subscription_info.peer().ip()),
- ),
- opentelemetry::KeyValue::new(
- "network.peer.port",
- opentelemetry::Value::I64(subscription_info.peer().port().into()),
+ format!("{}", subscription_info.peer_ip()),
),
opentelemetry::KeyValue::new(
OTL_YANG_PUSH_SUBSCRIPTION_ID_KEY,
@@ -695,7 +698,7 @@ impl CacheActor {
let yang_lib_ref = match result {
Ok(yang_lib_ref) => {
info!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -705,7 +708,7 @@ impl CacheActor {
}
Err(err) => {
warn!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -718,9 +721,7 @@ impl CacheActor {
// First, remove all pending requests for this subscription info that are
// requested with full subscription info
let empty = SubscriptionInfo::new_empty(
- subscription_info.collector(),
- subscription_info.interface(),
- subscription_info.peer(),
+ subscription_info.peer_ip(),
subscription_info.id(),
);
let mut pending_senders = self
@@ -755,11 +756,7 @@ impl CacheActor {
Vec::from([
opentelemetry::KeyValue::new(
"network.peer.address",
- format!("{}", subscription_info.peer().ip()),
- ),
- opentelemetry::KeyValue::new(
- "network.peer.port",
- opentelemetry::Value::I64(subscription_info.peer().port().into()),
+ format!("{}", subscription_info.peer_ip()),
),
opentelemetry::KeyValue::new(
OTL_YANG_PUSH_SUBSCRIPTION_ID_KEY,
@@ -779,11 +776,11 @@ impl CacheActor {
async fn process_request(&mut self, request: CacheLookupCommand) {
match request {
- CacheLookupCommand::LookupBySubscriptionInfo(subscription_info, sender) => {
+ CacheLookupCommand::LookupBySubscriptionInfo(subscription_info, session, sender) => {
let otl_tags = Self::otl_tags_from_subscription_inf(&subscription_info);
self.stats.requests_received.add(1, otl_tags.as_ref());
debug!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -795,7 +792,7 @@ impl CacheActor {
match yang_lib_ref {
Some(yang_lib_ref) => {
info!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -819,7 +816,7 @@ impl CacheActor {
if should_fetch {
info!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -828,14 +825,15 @@ impl CacheActor {
self.stats.device_fetch_request.add(1, &otl_tags);
let job_result = tokio::time::timeout(
self.fetcher_timeout,
- self.fetcher.fetch(subscription_info.clone()),
+ self.fetcher
+ .fetch(subscription_info.clone(), session.clone()),
)
.await;
let job = match job_result {
Ok(worker_result) => worker_result,
Err(err) => {
warn!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -855,7 +853,7 @@ impl CacheActor {
.record(self.workers_queue.len() as u64, &[]);
} else {
debug!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -869,11 +867,15 @@ impl CacheActor {
}
}
}
- CacheLookupCommand::LookupBySubscriptionInfoOneShot(subscription_info, sender) => {
+ CacheLookupCommand::LookupBySubscriptionInfoOneShot(
+ subscription_info,
+ session,
+ sender,
+ ) => {
let mut otl_tags = Self::otl_tags_from_subscription_inf(&subscription_info);
self.stats.requests_received.add(1, otl_tags.as_ref());
debug!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -885,7 +887,7 @@ impl CacheActor {
.get_by_subscription_info(&subscription_info)
{
info!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -898,7 +900,8 @@ impl CacheActor {
self.stats.device_fetch_request.add(1, &otl_tags);
let worker_result = tokio::time::timeout(
self.fetcher_timeout,
- self.fetcher.fetch_blocking(subscription_info.clone()),
+ self.fetcher
+ .fetch_blocking(subscription_info.clone(), session.clone()),
)
.await;
@@ -924,18 +927,13 @@ impl CacheActor {
Self::send_yang_lib_ref_oneshot(&subscription_info, yang_lib_ref, sender);
}
CacheLookupCommand::LookupBySubscriptionId {
- collector,
- interface,
- peer,
+ session,
subscription_id,
tx,
} => {
+ let peer_ip = session.peer().ip();
let otel_tags = [
- opentelemetry::KeyValue::new("network.peer.address", format!("{}", peer.ip())),
- opentelemetry::KeyValue::new(
- "network.peer.port",
- opentelemetry::Value::I64(peer.port().into()),
- ),
+ opentelemetry::KeyValue::new("network.peer.address", format!("{peer_ip}")),
opentelemetry::KeyValue::new(
OTL_YANG_PUSH_SUBSCRIPTION_ID_KEY,
opentelemetry::Value::I64(subscription_id.into()),
@@ -943,29 +941,24 @@ impl CacheActor {
];
self.stats.requests_received.add(1, &otel_tags);
debug!(
- peer=%peer,
+ peer_ip=%peer_ip,
subscription_id,
"processing cache lookup by subscription id request"
);
let response = self
.schema_cache
- .get_by_subscription_id(peer.ip(), subscription_id);
+ .get_by_subscription_id(peer_ip, subscription_id);
if let Some((subscription_info, yang_lib_ref)) = response {
self.stats.cache_hits.add(1, &otel_tags);
Self::send_yang_lib_ref(&subscription_info, yang_lib_ref, tx).await;
} else {
warn!(
- peer=%peer,
+ peer_ip=%peer_ip,
subscription_id,
"cache miss: subscription id not found in cache"
);
self.stats.cache_misses.add(1, &otel_tags);
- let subscription_info = SubscriptionInfo::new_empty(
- collector,
- interface.clone(),
- peer,
- subscription_id,
- );
+ let subscription_info = SubscriptionInfo::new_empty(peer_ip, subscription_id);
let entry = self
.pending_requests
.entry(subscription_info.clone())
@@ -975,26 +968,22 @@ impl CacheActor {
if should_fetch {
info!(
- peer=%peer,
+ peer_ip=%peer_ip,
subscription_id,
"cache miss: starting fetch from device by subscription id"
);
self.stats.device_fetch_request.add(1, &otel_tags);
let job_result = tokio::time::timeout(
self.fetcher_timeout,
- self.fetcher.fetch_by_subscription_id(
- collector,
- interface.clone().map(String::into_boxed_str),
- peer,
- subscription_id,
- ),
+ self.fetcher
+ .fetch_by_subscription_id(session.clone(), subscription_id),
)
.await;
let job = match job_result {
Ok(worker_result) => worker_result,
Err(err) => {
warn!(
- peer=%peer,
+ peer_ip=%peer_ip,
subscription_id,
router_content_id=subscription_info.clone().content_id().clone(),
target=%subscription_info.target(),
@@ -1014,7 +1003,7 @@ impl CacheActor {
.record(self.workers_queue.len() as u64, &[]);
} else {
debug!(
- peer=%subscription_info.peer(),
+ peer_ip=%subscription_info.peer_ip(),
subscription_id=subscription_info.id(),
router_content_id=subscription_info.content_id(),
target=%subscription_info.target(),
@@ -1028,18 +1017,13 @@ impl CacheActor {
}
}
CacheLookupCommand::LookupBySubscriptionIdOneShot {
- collector,
- interface,
- peer,
+ session,
subscription_id,
tx,
} => {
+ let peer_ip = session.peer().ip();
let mut otel_tags = vec![
- opentelemetry::KeyValue::new("network.peer.address", format!("{}", peer.ip())),
- opentelemetry::KeyValue::new(
- "network.peer.port",
- opentelemetry::Value::I64(peer.port().into()),
- ),
+ opentelemetry::KeyValue::new("network.peer.address", format!("{peer_ip}")),
opentelemetry::KeyValue::new(
OTL_YANG_PUSH_SUBSCRIPTION_ID_KEY,
opentelemetry::Value::I64(subscription_id.into()),
@@ -1047,18 +1031,18 @@ impl CacheActor {
];
self.stats.requests_received.add(1, &otel_tags);
debug!(
- peer=%peer,
+ peer_ip=%peer_ip,
subscription_id,
"processing cache lookup by subscription id request (one shot)");
let response = self
.schema_cache
- .get_by_subscription_id(peer.ip(), subscription_id);
+ .get_by_subscription_id(peer_ip, subscription_id);
if let Some((subscription_info, yang_lib_ref)) = response {
self.stats.cache_hits.add(1, &otel_tags);
Self::send_yang_lib_ref_oneshot(&subscription_info, yang_lib_ref, tx);
} else {
warn!(
- peer=%peer,
+ peer_ip=%peer_ip,
subscription_id,
"cache miss: subscription id not found in cache"
);
@@ -1067,12 +1051,8 @@ impl CacheActor {
let worker_result = tokio::time::timeout(
self.fetcher_timeout,
- self.fetcher.fetch_by_subscription_id_blocking(
- collector,
- interface.clone().map(String::into_boxed_str),
- peer,
- subscription_id,
- ),
+ self.fetcher
+ .fetch_by_subscription_id_blocking(session.clone(), subscription_id),
)
.await;
let worker_result = match worker_result {
@@ -1086,12 +1066,7 @@ impl CacheActor {
format!("{err}"),
));
self.stats.device_fetch_failed.add(1, &otel_tags);
- let empty = SubscriptionInfo::new_empty(
- collector,
- interface,
- peer,
- subscription_id,
- );
+ let empty = SubscriptionInfo::new_empty(peer_ip, subscription_id);
Err(Box::new((empty.clone(), err.into())))
}
};
@@ -1295,11 +1270,17 @@ pub(crate) mod tests {
use std::path::Path;
use std::time::Duration;
- pub(crate) fn test_subscription_info() -> SubscriptionInfo {
- SubscriptionInfo::new(
+ pub(crate) fn test_session_info() -> SessionInfo {
+ SessionInfo::new(
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 10000),
None,
- SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 200)), 830),
+ SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 200)), 50000),
+ )
+ }
+
+ pub(crate) fn test_subscription_info() -> SubscriptionInfo {
+ SubscriptionInfo::new(
+ IpAddr::V4(Ipv4Addr::new(192, 168, 1, 200)),
1,
Target::new_datastore(
"ds:operational".to_string(),
@@ -1437,6 +1418,7 @@ pub(crate) mod tests {
.request_tx()
.send(CacheLookupCommand::LookupBySubscriptionInfo(
subscription_info.clone(),
+ test_session_info(),
tx,
))
.await
@@ -1489,6 +1471,7 @@ pub(crate) mod tests {
.request_tx()
.send(CacheLookupCommand::LookupBySubscriptionInfo(
subscription_info.clone(),
+ test_session_info(),
tx,
))
.await
@@ -1517,6 +1500,7 @@ pub(crate) mod tests {
.request_tx()
.send(CacheLookupCommand::LookupBySubscriptionInfo(
subscription_info.clone(),
+ test_session_info(),
tx,
))
.await
@@ -1563,7 +1547,11 @@ pub(crate) mod tests {
tasks.push_back(tokio::spawn(async move {
let (tx, rx) = async_channel::unbounded();
h.request_tx()
- .send(CacheLookupCommand::LookupBySubscriptionInfo(sub, tx))
+ .send(CacheLookupCommand::LookupBySubscriptionInfo(
+ sub,
+ test_session_info(),
+ tx,
+ ))
.await
.unwrap();
tokio::time::timeout(Duration::from_secs(1), rx.recv())
@@ -1612,9 +1600,7 @@ pub(crate) mod tests {
handle
.request_tx()
.send(CacheLookupCommand::LookupBySubscriptionId {
- collector: subscription_info.collector(),
- interface: subscription_info.interface(),
- peer: subscription_info.peer(),
+ session: test_session_info(),
subscription_id: subscription_info.id(),
tx,
})
@@ -1629,12 +1615,7 @@ pub(crate) mod tests {
.expect("failed to receive response");
assert_eq!(
response.subscription_info(),
- &SubscriptionInfo::new_empty(
- subscription_info.collector(),
- subscription_info.interface(),
- subscription_info.peer(),
- subscription_info.id(),
- )
+ &SubscriptionInfo::new_empty(subscription_info.peer_ip(), subscription_info.id(),)
);
assert_eq!(response.yang_lib_ref(), None);
@@ -1647,9 +1628,7 @@ pub(crate) mod tests {
assert_eq!(hits_counts.len(), 1);
assert_eq!(
hits_counts.get(&SubscriptionInfo::new_empty(
- subscription_info.collector(),
- subscription_info.interface(),
- subscription_info.peer(),
+ subscription_info.peer_ip(),
subscription_info.id()
)),
Some(&1)
@@ -1684,9 +1663,11 @@ pub(crate) mod tests {
handle
.request_tx()
.send(CacheLookupCommand::LookupBySubscriptionId {
- collector: subscription_info.collector(),
- interface: subscription_info.interface(),
- peer: subscription_info.peer(),
+ session: SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 10000)),
+ None,
+ SocketAddr::new(subscription_info.peer_ip(), 0),
+ ),
subscription_id: subscription_info.id(),
tx,
})
@@ -1716,6 +1697,7 @@ pub(crate) mod tests {
.request_tx()
.send(CacheLookupCommand::LookupBySubscriptionInfo(
subscription_info.clone(),
+ test_session_info(),
tx,
))
.await
@@ -1763,9 +1745,11 @@ pub(crate) mod tests {
let (tx, rx) = async_channel::unbounded();
h.request_tx()
.send(CacheLookupCommand::LookupBySubscriptionId {
- collector: sub.collector(),
- interface: sub.interface(),
- peer: sub.peer(),
+ session: SessionInfo::new(
+ SocketAddr::from(([127, 0, 0, 1], 10000)),
+ None,
+ SocketAddr::new(sub.peer_ip(), 0),
+ ),
subscription_id: sub.id(),
tx,
})
diff --git a/crates/yang-push/src/cache/fetcher.rs b/crates/yang-push/src/cache/fetcher.rs
index f6bca478..6c30570e 100644
--- a/crates/yang-push/src/cache/fetcher.rs
+++ b/crates/yang-push/src/cache/fetcher.rs
@@ -35,13 +35,14 @@ use netcalyx_netconf_proto::yang_push::subscription::{
};
use netcalyx_netconf_proto::yang_push::types::SubscriptionId;
use netcalyx_netconf_proto::yanglib::{DatastoreName, PermissiveVersionChecker, YangLibrary};
+use netcalyx_udp_notif_service::SessionInfo;
use rand::RngExt;
use std::collections::{HashMap, HashSet};
use std::future::Future;
-use std::net::SocketAddr;
+use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use tokio::task::JoinHandle;
-use tracing::{error, info, trace, warn};
+use tracing::{debug, error, info, trace, warn};
pub type FetcherResult = Result<
(SubscriptionInfo, YangLibrary, HashMap, Box>),
@@ -55,27 +56,25 @@ pub trait YangLibraryFetcher {
fn fetch(
&self,
subscription_info: SubscriptionInfo,
+ session: SessionInfo,
) -> impl Future