Automation

gNSI Acctz — Management Plane Accounting on Cisco IOS XR

April 27, 202618 دقیقه مطالعهبدون دیدگاه

The Problem

Certz authenticates clients through TLS. Authz decides which gRPC Services a user can call. Pathz controls which gNMI Paths they can touch. But after all of that — what actually happened? Traditional AAA Accounting on IOS XR covers CLI and NETCONF sessions, but gRPC-based activity (gNMI, gNOI, gRIBI, gNSI itself) has no standard Audit Trail. Without structured Accounting Records for the gRPC Management Plane, compliance teams have no way to answer “who did what, when, and from where.” Previously, the only option was syslog.

What is Acctz

gNSI Acctz fills that gap. It streams structured Accounting Records from the device to a Collector over gRPC. Records cover everything that touches the Management Plane:

  • CLI and Shell commands (expanded to full form — sh ver det becomes show version detail)
  • NETCONF sessions
  • RESTCONF and WebUI activity
  • gRPC Services — gNMI, gNOI, gNSI, gRIBI, P4RT
  • Login and Logout events

Each Record carries Session metadata (5-tuple, channel ID, TTY), Authentication details (type, status, cause of failure), and Authorization outcome (PERMIT/DENY with Policy detail). For gRPC Records, the RPC name (e.g., /gnmi.gNMI/Set, /gnsi.certz.v1.Certz/Rotate) and an optional payload snapshot are included. Sensitive payloads (Private Keys rotated via Certz / Credentialz) may be redacted.

The device holds a Circular Buffer of recent Records. A Collector calls RecordSubscribe, passes a timestamp, and the device streams every Record from that point forward. A timestamp of 0 means “send all buffered history.” The stream continues indefinitely until the gNSI session ends or an error occurs.

Within AAA, Acctz is the Accounting plane — the structured audit trail for every action that touches the management plane, regardless of whether it was permitted or denied.


IOS XR Support

Release What changed
24.3.1 gNSI Acctz Logging introduced — bidirectional Acctz.RecordSubscribe. Command and gRPC Service Records. grpc aaa accounting queue-size CLI.
24.4.1 AcctzStream (server-streaming) replaces the deprecated bidirectional Acctz. grpc aaa accounting history-memory CLI. Per-Collector statistics in show gnsi acctz statistics.

Platforms: Cisco 8000, NCS 5500, ASR 9000 families. Confirm exact SKU and PI/SMU in Release Notes.

Validated on: This article was end-to-end exercised on Cisco NCS-540
running IOS XR 25.3.1. Where the device behavior diverges from the
proto/spec or from the original xrdocs text, the divergence is called
out inline below.


The two gRPC Services

The acctz.proto defines two services:

Service Stream type Status
Acctz Bidirectional stream (RecordSubscribe) Deprecated (IOS XR 24.3.1)
AcctzStream Client → server stream (RecordSubscribe) Preferred (IOS XR 24.4.1+)

AcctzStream is a unidirectional server-streaming service — the Collector sends one RecordRequest, and the server streams RecordResponse messages back. The deprecated Acctz used a bidirectional stream. Implementations may expose one or the other; always check with Reflection.


How Streaming Works

  1. A Collector calls RecordSubscribe.
  2. RecordRequest carries a timestamp (nanoseconds since epoch). 0 means “send all buffered history.” Current time means “only new Records.”
  3. The server streams RecordResponse messages as new Records are created.
  4. The stream runs indefinitely until the gNSI session ends, a TCP keepalive expires, or the server disconnects a stuck Collector whose output buffer stays full for too long.
 +-----------------+
 | Remote Collector|
 +-----------------+
 ^
 | gNSI Acctz stream
 |
 +----------------------------------+
 | IOS XR Router |
 | CLI NETCONF gNMI gNOI gRIBI |
 | +------+-------+------+-----+--+
 | Acctz Service |
 +----------------------------------+

History and the Circular Buffer

The device maintains a Circular Buffer so short Collector outages do not lose everything. Key behavior:

  • History depth is configurable via grpc aaa accounting queue-size (default: 40 records, range: 1–512) and grpc aaa accounting history-memory (default: 40 MB, range: 1–400 MB).
  • When the queue is full and no Collectors are connected, the oldest Records are overwritten.
  • Besides the queue Records stored for streaming, up to 512 additional Records are held during processing.
  • Worst-case EMSd memory usage: (N+1) × history-memory, where N is the number of connected Collectors.
  • If history_istruncated appears in a RecordResponse, the Collector requested a timestamp older than the buffer — some Records were lost.

On 25.3.1: RecordResponse.history_istruncated is true on the first record of every replay, even on a fresh buffer with timestamp = "1970-01-01T00:00:00Z". Treat the flag as informational on this build and cross-check Total History Truncation in show gnsi acctz statistics to detect real buffer wrap.


RecordResponse Structure

Each RecordResponse carries four categories of information:

Category Key fields Purpose
Session local_address, remote_address, remote_port, channel_id, tty, status Who connected and from where
User / Authn identity, role, authn.type, authn.status Who authenticated and how
Service payload cmd_service (CLI/NETCONF/etc.) or grpc_service (gNMI/gNOI/gNSI/etc.) What was executed
Authorization authz.status (PERMIT/DENY), authz.detail Whether it was allowed

The full proto field tree is in the Appendix at the end of this article.

Command vs gRPC Records

Record type What it captures Key fields
CommandService CLI, Shell, NETCONF, RESTCONF, WebUI cmd + cmd_args (expanded from abbreviation), authz outcome
GrpcService gNMI, gNOI, gNSI, gRIBI, P4RT rpc_name (HTTP/2 :path header), optional payload, authz outcome

Note: GRIBI Records can be very verbose. The proto comments recommend keeping GRIBI logging off by default in Authorization Policy.


IOS XR CLI Reference

Command What it does Default Range Release
aaa accounting commands default start-stop group tacacs+ Enable AAA Accounting (required for Acctz) — — —
grpc aaa accounting queue-size <n> Number of Records the Circular Buffer holds 40 1–512 24.3.1
grpc aaa accounting history-memory <MB> Maximum memory for cached history Records 40 MB 1–400 MB 24.4.1
Show command What it shows
show gnsi state gNSI subsystem status (Acctz Thread, RDSFS State)
show gnsi acctz statistics History Buffer, Queue Counters (sent/dropped/truncated), connected Collectors, per-Collector Records/Drops
show aaa accounting statistics AAA-level Accounting event Counters
show tech-support gnsi Full gNSI diagnostic bundle

Hands-on: End-to-End Acctz Collection

Prerequisites

  • IOS XR router with gRPC enabled (24.3.1 or later for Acctz; 24.4.1+ for AcctzStream)
  • grpcurl installed
  • OpenConfig gNSI protos cloned: git clone https://github.com/openconfig/gnsi.git
  • AAA Accounting configured on the router (see configuration below)

This walkthrough covers the full flow: configure the router, discover the RPCs, start a Collector stream, trigger Management Plane activity, inspect the Records, and verify with IOS XR CLI.

Environment (workstation)

export GNSI_IMPORT="${GNSI_IMPORT:-$PWD}"
export XR="${XR:-198.51.100.1:57400}"
export XR_USER="${XR_USER:-cisco}"
export XR_PASS="${XR_PASS:-}"
GRPC_MD=(-H "username:${XR_USER}" -H "password:${XR_PASS}")
PROTO_ACCTZ="acctz/acctz.proto"

Lab only: All examples below use -plaintext (no TLS). In production, replace -plaintext with -cacert, -cert, and -key flags. See the Certz article for TLS setup.

Configure the router (IOS XR CLI)

Enable AAA Accounting (if not already enabled) and tune the Acctz buffer:

configure
 aaa accounting commands default start-stop group tacacs+
 aaa accounting exec default start-stop group tacacs+
 grpc aaa accounting queue-size 100
 grpc aaa accounting history-memory 80
 commit
end

Verify gNSI is active:

show gnsi state

Expected output:

----------------GNSI state--------------
Global:
Main Thread cerrno: Success
Acctz Thread cerrno: Success
State: Active
RDSFS State: Active

List Services (Reflection)

Check which Acctz Services the device exports:

grpcurl -plaintext \
 -import-path "$GNSI_IMPORT" \
 -proto "$PROTO_ACCTZ" \
 "${GRPC_MD[@]}" \
 "${XR}" \
 list gnsi.acctz.v1.Acctz
gnsi.acctz.v1.Acctz.RecordSubscribe
grpcurl -plaintext \
 -import-path "$GNSI_IMPORT" \
 -proto "$PROTO_ACCTZ" \
 "${GRPC_MD[@]}" \
 "${XR}" \
 list gnsi.acctz.v1.AcctzStream
gnsi.acctz.v1.AcctzStream.RecordSubscribe

Both services may appear in Reflection, but on current images only AcctzStream is implemented. The deprecated Acctz (bidirectional) returns Unimplemented when called. Always use AcctzStream.

Describe the RPC

Inspect the RecordSubscribe method:

grpcurl -plaintext \
 -import-path "$GNSI_IMPORT" \
 -proto "$PROTO_ACCTZ" \
 "${GRPC_MD[@]}" \
 "${XR}" \
 describe gnsi.acctz.v1.AcctzStream.RecordSubscribe

Router response:

gnsi.acctz.v1.AcctzStream.RecordSubscribe is a method:
rpc RecordSubscribe (.gnsi.acctz.v1.RecordRequest) returns ( stream.gnsi.acctz.v1.RecordResponse);

Print a template for the request message:

grpcurl -import-path "$GNSI_IMPORT" \
 -proto "$PROTO_ACCTZ" \
 -msg-template describe gnsi.acctz.v1.RecordRequest

Router response:

Message template:
{
 "timestamp": "1970-01-01T00:00:00Z"
}

The timestamp field uses RFC 3339 format. "1970-01-01T00:00:00Z" (epoch zero) means “send all buffered history.”

grpcurl tip: the field is a google.protobuf.Timestamp, so {"timestamp":"0"} fails with a client-side parse error. Use {"timestamp":"1970-01-01T00:00:00Z"} or just {} — both replay the full buffer.

Subscribe: replay all buffered Records

Timestamp "1970-01-01T00:00:00Z" replays everything in the Circular Buffer:

grpcurl -plaintext \
 -import-path "$GNSI_IMPORT" \
 -proto "$PROTO_ACCTZ" \
 "${GRPC_MD[@]}" \
 -d '{"timestamp": "1970-01-01T00:00:00Z"}' \
 "${XR}" \
 gnsi.acctz.v1.AcctzStream.RecordSubscribe

This streams all buffered history, then continues with new Records in real time. Press Ctrl+C to stop. The first Record may include "historyIstruncated": true — meaning the Circular Buffer wrapped and older Records were lost:

{
 "sessionInfo": {
 "localAddress": "172.20.163.10",
 "localPort": 57344,
 "remoteAddress": "10.189.10.189",
 "remotePort": 58091,
 "ipProto": 6,
 "channelId": "0",
 "status": "SESSION_STATUS_OPERATION",
 "user": {
 "identity": "cisco"
 }
 },
 "timestamp": "2026-04-16T05:51:01.332142269Z",
 "historyIstruncated": true,
 "grpcService": {
 "serviceType": "GRPC_SERVICE_TYPE_GNSI",
 "rpcName": "/gnsi.credentialz.v1.Credentialz/GetPublicKeys",
 "authz": {
 "status": "AUTHZ_STATUS_PERMIT"
 },
 "protoVal": {
 "@type": "type.googleapis.com/google.protobuf.BytesValue",
 "value": null
 }
 }
}

Subscribe: stream only recent Records

To stream Records from the last hour (ISO 8601/RFC 3339 format):

# macOS:
SINCE=$(date -u -v-1H +"%Y-%m-%dT%H:%M:%SZ")
# Linux:
# SINCE=$(date -u -d "1 hour ago" +"%Y-%m-%dT%H:%M:%SZ")
grpcurl -plaintext \
 -import-path "$GNSI_IMPORT" \
 -proto "$PROTO_ACCTZ" \
 "${GRPC_MD[@]}" \
 -d "{\"timestamp\": \"${SINCE}\"}" \
 "${XR}" \
 gnsi.acctz.v1.AcctzStream.RecordSubscribe

To stream only new Records going forward (skip history):

NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
grpcurl -plaintext \
 -import-path "$GNSI_IMPORT" \
 -proto "$PROTO_ACCTZ" \
 "${GRPC_MD[@]}" \
 -d "{\"timestamp\": \"${NOW}\"}" \
 "${XR}" \
 gnsi.acctz.v1.AcctzStream.RecordSubscribe

Trigger activity and watch Records arrive

With the stream running in one terminal, open a second terminal and trigger some gRPC activity:

# Credentialz CanGenerateKey
grpcurl -plaintext \
 -import-path "$GNSI_IMPORT" \
 -proto "credentialz/credentialz.proto" \
 "${GRPC_MD[@]}" \
 -d '{"keyParams": "KEY_GEN_SSH_KEY_TYPE_EDDSA_ED25519"}' \
 "${XR}" \
 gnsi.credentialz.v1.Credentialz.CanGenerateKey
# Certz GetProfileList
grpcurl -plaintext \
 -import-path "$GNSI_IMPORT" \
 -proto "certz/certz.proto" \
 "${GRPC_MD[@]}" \
 -d '{}' \
 "${XR}" \
 gnsi.certz.v1.Certz.GetProfileList

Switch back to the Acctz stream terminal. New RecordResponse messages appear — one for each gRPC RPC executed.

On 25.3.1: every unary gRPC RPC (gNMI/Get, gNMI/Set, Pathz/Get, Credentialz/CanGenerateKey, Credentialz/GetPublicKeys) emits two records ~80 µs apart with identical grpcService.rpcName, authz, and protoVal. Plan collector capacity and per-RPC alerting around 2× fan-out for unary calls.

Read a RecordResponse

Here is a real gRPC Service Record captured from a Cisco 8000 router. This Record was generated when Credentialz.CanGenerateKey was called:

{
 "sessionInfo": {
 "localAddress": "172.20.163.10",
 "localPort": 57344,
 "remoteAddress": "10.189.10.189",
 "remotePort": 58913,
 "ipProto": 6,
 "channelId": "0",
 "status": "SESSION_STATUS_OPERATION",
 "user": {
 "identity": "cisco",
 "role": "root-lr, netadmin, cisco-support"
 }
 },
 "timestamp": "2026-04-16T06:01:47.028597996Z",
 "grpcService": {
 "serviceType": "GRPC_SERVICE_TYPE_GNSI",
 "rpcName": "/gnsi.credentialz.v1.Credentialz/CanGenerateKey",
 "authz": {
 "status": "AUTHZ_STATUS_PERMIT"
 },
 "protoVal": {
 "@type": "type.googleapis.com/google.protobuf.BytesValue",
 "value": "CAQ="
 }
 }
}

And a Record for Certz.GetProfileList — notice the payload is null (empty response body, not truncated):

{
 "sessionInfo": {
 "localAddress": "172.20.163.10",
 "localPort": 57344,
 "remoteAddress": "10.189.10.189",
 "remotePort": 58914,
 "ipProto": 6,
 "channelId": "0",
 "status": "SESSION_STATUS_OPERATION",
 "user": {
 "identity": "cisco"
 }
 },
 "timestamp": "2026-04-16T06:01:47.400770922Z",
 "grpcService": {
 "serviceType": "GRPC_SERVICE_TYPE_GNSI",
 "rpcName": "/gnsi.certz.v1.Certz/GetProfileList",
 "authz": {
 "status": "AUTHZ_STATUS_PERMIT"
 },
 "protoVal": {
 "@type": "type.googleapis.com/google.protobuf.BytesValue",
 "value": null
 }
 }
}

A Record for a streaming RPC like RotateHostParameters — note payloadIstruncated: true (large streaming payload is not fully captured):

{
 "sessionInfo": {
 "localAddress": "172.20.163.10",
 "localPort": 57344,
 "remoteAddress": "10.189.10.189",
 "remotePort": 58076,
 "ipProto": 6,
 "channelId": "0",
 "status": "SESSION_STATUS_OPERATION",
 "user": {
 "identity": "cisco"
 }
 },
 "timestamp": "2026-04-16T05:50:39.054469860Z",
 "grpcService": {
 "serviceType": "GRPC_SERVICE_TYPE_GNSI",
 "rpcName": "/gnsi.credentialz.v1.Credentialz/RotateHostParameters",
 "payloadIstruncated": true,
 "authz": {
 "status": "AUTHZ_STATUS_PERMIT"
 }
 }
}

And the Acctz stream itself generates a self-referential Record:

{
 "sessionInfo": {
 "localAddress": "172.20.163.10",
 "localPort": 57344,
 "remoteAddress": "10.189.10.189",
 "remotePort": 58880,
 "ipProto": 6,
 "channelId": "0",
 "status": "SESSION_STATUS_OPERATION",
 "user": {
 "identity": "cisco",
 "role": "root-lr, netadmin, cisco-support"
 }
 },
 "timestamp": "2026-04-16T06:01:22.999917154Z",
 "grpcService": {
 "serviceType": "GRPC_SERVICE_TYPE_GNSI",
 "rpcName": "/gnsi.acctz.v1.AcctzStream/RecordSubscribe",
 "payloadIstruncated": true,
 "authz": {
 "status": "AUTHZ_STATUS_PERMIT"
 }
 }
}

Key fields to look for:

Field What it tells
sessionInfo.user.identity Who — the username or SPIFFE ID
sessionInfo.user.role User groups — root-lr, netadmin, cisco-support etc. (may appear on some Records)
sessionInfo.remoteAddress From where — the client IP
sessionInfo.status Session type — SESSION_STATUS_OPERATION for RPC calls, SESSION_STATUS_LOGIN/LOGOUT for session events
grpcService.rpcName What — the full gRPC path (e.g., /gnsi.credentialz.v1.Credentialz/CanGenerateKey)
grpcService.serviceType Which gRPC family — GRPC_SERVICE_TYPE_GNSI, GRPC_SERVICE_TYPE_GNMI, etc.
grpcService.authz.status Allowed or denied — AUTHZ_STATUS_PERMIT or AUTHZ_STATUS_DENY
grpcService.payloadIstruncated Whether the payload was too large to include in full
grpcService.protoVal Payload snapshot (base64-encoded protobuf) — present for unary RPCs, absent for truncated streaming RPCs
cmdService.cmd + cmdArgs CLI command — expanded from abbreviation (for CommandService Records)
timestamp When — RFC 3339 with nanosecond precision
historyIstruncated Whether Records were lost before this point (buffer wrapped)

On 25.3.1: sessionInfo.user.role is inconsistent — back-to-back records for the same user on the same channel sometimes include "role": "root-lr, cisco-support" and sometimes omit the role key entirely. Correlate alerting rules by identity, not role, on this build.

On 25.3.1: failed gRPC auth is mislabeled. A wrong-password grpcurl call emits a SESSION_STATUS_LOGIN record tagged CMD_SERVICE_TYPE_CLI (despite no CLI being touched) plus a SESSION_STATUS_OPERATION / AUTHZ_STATUS_DENY record on ServerReflectionInfo. The LOGIN-side records also carry localAddress: "None" / remoteAddress: "None", so compliance pipelines that key off the 5-tuple cannot attribute the failed login.

Security-relevant on 25.3.1: when a gNMI/Set is denied by Pathz, the Acctz record’s protoVal still contains the full base64-encoded payload of what the caller tried to set. A denied Credentialz/RotateAccountCredentials.password or Certz/Rotate would therefore leak secret material into the audit log. Treat protoVal as sensitive and encrypt the audit pipe end-to-end — we observed no redaction on the denied path.

Verify on IOS XR

After running the stream, verify Records were produced and delivered:

show gnsi acctz statistics

Sample output (trimmed):

Acctz History Buffer:
 Total Records: 80029
 Total History Truncation: 0
 Grpc Service Records:
 GNMI: 80002
 GNOI: 0
 GNSI: 4
 GRIBI: 0
 P4RT: 0
 Cmd Service Records:
 Shell: 0
 Cli: 23
 Netconf: 0
 History snapshot:
 Max Memory Size: 80 MB
 Memory Used: 0 MB
 Max Number Of Records: 100
 Records Used: 30
gRPC Accounting Queue:
 Grpc services:
 GNMI: 80002 sent, 0 dropped, 0 truncated
 GNOI: 0 sent, 0 dropped, 0 truncated
 GNSI: 4 sent, 0 dropped, 4 truncated
...
Cmd Accounting Queue:
 Cmd services:
 Shell: 0 sent, 0 dropped, 0 truncated
 Cli: 23 sent, 0 dropped, 0 truncated
 Netconf: 0 sent, 0 dropped, 0 truncated
...
Client Stats:
 Number Of Clients: 1
 History Truncation Events: 0
 Record Requests: 4
 Record Responses: 80029
Collectors:
 Collector Statistics:
 IP: 192.168.122.1
 Port: 25906
 Total: Records: 80029, Drops: 0
 Grpc Service Records:
 gNMI: Records: 80002, Drops: 0
 gNSI: Records: 4, Drops: 0
 Cmd Service Records:
 CLI: Records: 23, Drops: 0

What to check:

Section What to look for
History Buffer Total Records should be non-zero. Total History Truncation should be 0 — non-zero means Records were lost.
Queue Counters dropped and truncated Counters per Service. Non-zero drops mean the queue overflowed.
Client Stats Number Of Clients ≥ 1. Record Responses should match what the Collector received.
Collector Statistics Per-Collector Records and Drops — confirms delivery to each connected Collector.

Also verify AAA-level Accounting:

show aaa accounting statistics

CLI auditing gaps on 25.3.1. Two related observations operators should plan around:

  • show aaa accounting statistics is decoupled from gNSI Acctz — the AAA view stays at GNMI/GNSI/GNOI/GRIBI/P4RT: 0 regardless of how many records AcctzStream emits. The only authoritative view of streamed records is show gnsi acctz statistics plus the stream itself.
  • SSH show commands do not produce CommandService records (only a single SESSION_STATUS_LOGIN per session). Per-command Acctz logging for SSH-driven CLI is a gap on this build; structured CLI auditing still relies on TACACS+ AAA.

Cleanup: stop the Collector

Press Ctrl+C in the grpcurl terminal to stop the stream. The server cleans up the Collector session automatically. Verify with:

show gnsi acctz statistics

Number Of Clients should drop by 1.

grpcurl Debugging Aids

# Describe the RPC and its streaming semantics
grpcurl -import-path "$GNSI_IMPORT" -proto "$PROTO_ACCTZ" \
 describe gnsi.acctz.v1.AcctzStream.RecordSubscribe
# Show the RecordRequest message template
grpcurl -import-path "$GNSI_IMPORT" -proto "$PROTO_ACCTZ" \
 -msg-template describe gnsi.acctz.v1.RecordRequest
# Show the RecordResponse message structure
grpcurl -import-path "$GNSI_IMPORT" -proto "$PROTO_ACCTZ" \
 -msg-template describe gnsi.acctz.v1.RecordResponse

Monitoring with YANG Telemetry

The OpenConfig YANG model openconfig-gnsi-acctz exposes Counters for both the Collector side and the Record production side.

On 25.3.1: the device advertises openconfig-gnsi-acctz, 0.2.0 (not the upstream 0.4.0), and every operational path under /system/aaa/acctz and /system/aaa/accounting returns NotFound. Use show gnsi acctz statistics until the gNMI side lands. The only programmatic signal currently available is the per-RPC counter row for /gnsi.acctz.v1.AcctzStream/RecordSubscribe under openconfig-gnsi-authz:authz-policy-counters.

Client Counters (base: /system/grpc-servers/grpc-server[name=<server>]/acctz/state/counters):

YANG path (relative to base) What it tells
.../record-requests Total RecordRequest messages received from Collectors
.../record-responses Total RecordResponse messages sent to Collectors
.../history-istruncated How many times a request hit a timestamp older than the buffer — Records were lost

Source Record Counters (base: /system/aaa/accounting/acctz/source-records/source-record[service=<SVC>][type=<TYPE>]/state/counters):

[service] [type] What it counts
CMD_SERVICE CLI CLI command Records
CMD_SERVICE SHELL Shell Records
CMD_SERVICE NETCONF NETCONF Records
CMD_SERVICE RESTCONF RESTCONF Records
CMD_SERVICE WEBUI WebUI Records
GRPC_SERVICE GNMI gNMI Records
GRPC_SERVICE GNOI gNOI Records
GRPC_SERVICE GNSI gNSI Records
GRPC_SERVICE GRIBI gRIBI Records
GRPC_SERVICE P4RT P4RT Records
SESSION_SERVICE LOGIN Login event Records
SESSION_SERVICE LOGOUT Logout event Records

Each combination exposes a .../records counter. Assemble the full path by substituting [service] and [type] into the base above.

Subscribe to history-istruncated to detect when the Circular Buffer is overflowing — that means Records are being lost before the Collector picks them up. If this Counter keeps climbing, increase grpc aaa accounting queue-size / grpc aaa accounting history-memory or add Collector capacity.

The Source Record Counters broken down by Service type give a real-time picture of which Management Plane interfaces are most active — useful for capacity planning and security monitoring.

Quick Telemetry check with gnmic

Verify the Acctz Counters with a gNMI Get (gnmic):

gnmic -a "${XR}" --insecure -u "${XR_USER}" -p "${XR_PASS}" \
 get --path "/system/grpc-servers/grpc-server/acctz/state/counters/record-responses" \
 --path "/system/grpc-servers/grpc-server/acctz/state/counters/history-istruncated"

Things to watch out for

  • Buffer overflow: the Circular Buffer is finite. When the Total History Truncation Counter in show gnsi acctz statistics is non-zero, the Collector fell behind. Increase queue-size and history-memory, or add Collector bandwidth. (On 25.3.1 NCS-540, history_istruncated in the response is not a reliable signal — see the History and the Circular Buffer section.)
  • Audit-log secrecy: denied gNMI Set records carry the full proposed payload as base64 protobuf in protoVal. Encrypt the Audit pipe end-to-end and assume any protoVal may contain secrets the caller tried to write before they were blocked.
  • Two records per unary RPC (25.3.1): every unary gRPC RPC produces two near-identical Acctz records. Plan Collector capacity and per-RPC alerting around 2× fan-out for unary RPCs (streaming RPCs still produce a single record with payloadIstruncated: true).
  • Record size: large gRPC payloads (gNMI Set with big configs, gRIBI AFT entries) produce large Records. Plan Collector storage accordingly. GRIBI logging is especially verbose — the proto comments recommend keeping it off by default in Authorization Policy.
  • Truncation fields: both cmd_istruncated and payload_istruncated signal that the originating service, middleware, or receiver hit an implementation limit. Automation should check these flags and log a warning.
  • Stuck Collectors: if the server’s output buffer to a Collector stays full for too long, the server disconnects that Collector (similar to BGP send-hold-timer behavior). New Records are dropped until the Collector reconnects and starts consuming again.
  • Command expansion: for CLI and Shell Records, abbreviated commands are expanded to their full form (sh ver det | gr foo → show version detail | grep foo). Arguments known to the lexicon are expanded; unrecognized tokens remain as-is.
  • Security: Collectors must be authenticated and network-isolated like any Management Plane traffic. Sensitive payloads (Private Keys rotated via Certz / Credentialz) are redacted in the Record.
  • Parallel channels: syslog and TACACS+ may remain as parallel Audit channels. Acctz does not replace them — it adds structured, gRPC-native Accounting alongside them.
  • AcctzStream vs Acctz: AcctzStream (server-streaming) is the working implementation. The deprecated bidirectional Acctz may appear in Reflection but returns Unimplemented when called. Always use AcctzStream.RecordSubscribe.

Conclusion

Acctz closes the visibility gap between traditional AAA Accounting and gRPC-first operations. Every CLI command, NETCONF edit, and gRPC RPC gets a structured Record with Session, Authentication, and Authorization detail — streamable in real time to remote Collectors. With Certz and Credentialz handling Authentication, Authz and Pathz handling Authorization, and Acctz completing Accounting, all three pillars of the AAA model are covered on IOS XR.

  • Acctz streams structured Accounting Records to Collectors — CLI, Shell, NETCONF, RESTCONF, WebUI, gNMI, gNOI, gNSI, gRIBI, P4RT, Login/Logout.
  • Timestamp 0 replays the entire Circular Buffer. Current time means “only new Records.”
  • RecordResponse carries Session metadata, Authentication details, Authorization outcome, and per-Service payloads (with sensitive material redacted).
  • Each Record includes AuthzDetail (PERMIT/DENY) — compliance pipelines can track Authorization decisions alongside the activity itself.
  • Configure buffer capacity with grpc aaa accounting queue-size (default 40, max 512) and grpc aaa accounting history-memory (default 40 MB, max 400 MB).
  • Prefer AcctzStream (24.4.1+) over the deprecated bidirectional Acctz; confirm on-device with Reflection.
  • YANG Telemetry via openconfig-gnsi-acctz tracks Collector consumption, buffer overflow, and per-Service Record production.

What to do next: All five gNSI services — Authentication (Certz, Credentialz), Authorization (Authz, Pathz), and Accounting (Acctz) — are now covered. Return to the series overview for the full picture.

References

  1. XRdocs — AcctZ
  2. OpenConfig — acctz.proto, acctz/README.md
  3. OpenConfig — openconfig-gnsi-acctz YANG model v0.4.0
  4. Cisco IOS XR — Programmability Configuration Guide: gRPC Applications and Configuration (gNSI Acctz Logging + AcctzStream sections)
  5. Cisco IOS XR — Programmability Command Reference: gRPC commands (grpc aaa accounting history-memory, grpc aaa accounting queue-size, show gnsi acctz statistics)
  6. Series — gNSI on IOS XR overview

Appendix: RecordResponse field tree

Full proto structure for reference. The RecordResponse Structure section above highlights the four key categories.

RecordSubscribe(RecordRequest) → stream RecordResponse
│
├── RecordRequest
│ └── timestamp: google.protobuf.Timestamp
│ (0 = "send all available Records")
│
└── stream RecordResponse
 ├── session_info (SessionInfo)
 │ ├── local_address / local_port
 │ ├── remote_address / remote_port
 │ ├── ip_proto (6=TCP, 17=UDP)
 │ ├── channel_id
 │ ├── tty (console0, vty0, pty0)
 │ ├── status (LOGIN, LOGOUT, ONCE, ENABLE, IDLE, OPERATION)
 │ ├── user (UserDetail)
 │ │ ├── identity (username, SPIFFE ID)
 │ │ ├── role (privilege level, user group)
 │ │ └── ssh_principal
 │ └── authn (AuthnDetail)
 │ ├── type (PASSWORD, SSHKEY, SSHCERT, TLSCERT, PAP, CHAP)
 │ ├── status (SUCCESS, FAIL, ERROR)
 │ └── cause
 ├── timestamp
 ├── history_istruncated: bool
 ├── service_request (oneof)
 │ ├── cmd_service (CommandService)
 │ │ ├── service_type (SHELL, CLI, WEBUI, RESTCONF, NETCONF)
 │ │ ├── cmd, cmd_istruncated
 │ │ ├── cmd_args[], cmd_args_istruncated
 │ │ └── authz (AuthzDetail: PERMIT/DENY + detail)
 │ └── grpc_service (GrpcService)
 │ ├── service_type (GNMI, GNOI, GNSI, GRIBI, P4RT)
 │ ├── rpc_name ("/gnmi.gNMI/Set", etc.)
 │ ├── payload (proto_val or string_val)
 │ ├── payload_istruncated
 │ └── authz (AuthzDetail: PERMIT/DENY + detail)
 ├── component_name
 └── task_ids[]

اشتراک‌گذاری:

دیدگاه خود را بنویسید