gNSI Acctz — Management Plane Accounting on Cisco IOS XR
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 detbecomesshow 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
- A Collector calls RecordSubscribe.
- RecordRequest carries a timestamp (nanoseconds since epoch).
0means “send all buffered history.” Current time means “only new Records.” - The server streams RecordResponse messages as new Records are created.
- 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) andgrpc 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_istruncatedappears in aRecordResponse, the Collector requested a timestamp older than the buffer — some Records were lost.
On 25.3.1:
RecordResponse.history_istruncatedistrueon the first record of every replay, even on a fresh buffer withtimestamp = "1970-01-01T00:00:00Z". Treat the flag as informational on this build and cross-checkTotal History Truncationinshow gnsi acctz statisticsto 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-plaintextwith-cacert,-cert, and-keyflags. 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.”
grpcurltip: the field is agoogle.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 identicalgrpcService.rpcName,authz, andprotoVal. 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.roleis inconsistent — back-to-back records for the same user on the same channel sometimes include"role": "root-lr, cisco-support"and sometimes omit therolekey entirely. Correlate alerting rules byidentity, notrole, on this build.
On 25.3.1: failed gRPC auth is mislabeled. A wrong-password
grpcurlcall emits aSESSION_STATUS_LOGINrecord taggedCMD_SERVICE_TYPE_CLI(despite no CLI being touched) plus aSESSION_STATUS_OPERATION / AUTHZ_STATUS_DENYrecord onServerReflectionInfo. The LOGIN-side records also carrylocalAddress: "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/Setis denied by Pathz, the Acctz record’sprotoValstill contains the full base64-encoded payload of what the caller tried to set. A deniedCredentialz/RotateAccountCredentials.passwordorCertz/Rotatewould therefore leak secret material into the audit log. TreatprotoValas 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 statisticsis decoupled from gNSI Acctz — the AAA view stays atGNMI/GNSI/GNOI/GRIBI/P4RT: 0regardless of how many records AcctzStream emits. The only authoritative view of streamed records isshow gnsi acctz statisticsplus the stream itself.- SSH
showcommands do not produceCommandServicerecords (only a singleSESSION_STATUS_LOGINper 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 upstream0.4.0), and every operational path under/system/aaa/acctzand/system/aaa/accountingreturnsNotFound. Useshow gnsi acctz statisticsuntil the gNMI side lands. The only programmatic signal currently available is the per-RPC counter row for/gnsi.acctz.v1.AcctzStream/RecordSubscribeunderopenconfig-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 TruncationCounter inshow gnsi acctz statisticsis non-zero, the Collector fell behind. Increasequeue-sizeandhistory-memory, or add Collector bandwidth. (On 25.3.1 NCS-540,history_istruncatedin the response is not a reliable signal — see the History and the Circular Buffer section.) - Audit-log secrecy: denied
gNMI Setrecords carry the full proposed payload as base64 protobuf inprotoVal. Encrypt the Audit pipe end-to-end and assume anyprotoValmay 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_istruncatedandpayload_istruncatedsignal 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 bidirectionalAcctzmay appear in Reflection but returnsUnimplementedwhen called. Always useAcctzStream.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
0replays 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) andgrpc 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-acctztracks 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
- XRdocs — AcctZ
- OpenConfig —
acctz.proto,acctz/README.md - OpenConfig —
openconfig-gnsi-acctzYANG model v0.4.0 - Cisco IOS XR — Programmability Configuration Guide: gRPC Applications and Configuration (gNSI Acctz Logging + AcctzStream sections)
- Cisco IOS XR — Programmability Command Reference: gRPC commands (
grpc aaa accounting history-memory,grpc aaa accounting queue-size,show gnsi acctz statistics) - 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[]