Chapter 12. Notifications

Table of Contents

12.1. ConfD Asynchronous Events
12.2. Audit Messages
12.3. Syslog Messages
12.4. Commit Events
12.5. Commit Failure Events
12.6. Confirmed Commit Events
12.7. Commit Progress Events
12.8. User Sessions
12.9. High Availability - Cluster Events
12.10. Subagent Events
12.11. SNMP Agent Audit Log
12.12. Forwarding Events
12.13. In-service Upgrade Events
12.14. Heartbeat and Health Check Events
12.15. Notification stream Events

12.1. ConfD Asynchronous Events

ConfD can deliver various classes of events to subscribing applications. The architecture is based on notification sockets. The application(s) connect a notifications socket to ConfD. The application provides a bit mask indicating which types of events the application is interested in. The application polls the socket and invokes the API function confd_read_notification() whenever the socket is ready to read. The API function populates a struct confd_notification structure.

The following is a list of the different asynchronous event classes that can be delivered from ConfD to the application(s). See also the confd_lib_events(3) manual page. The program misc/notifications/confd_notifications.c in the examples collection illustrates subscription and processing for all these events, and can also be used standalone in a development environment to monitor ConfD events.

  • CONFD_NOTIF_AUDIT - Audit events.

  • CONFD_NOTIF_AUDIT_SYNC - Indicates that audit notifications (CONFD_NOTIF_AUDIT) must be synced by the application.

  • CONFD_NOTIF_DAEMON - Syslog events that also go to /confdConf/logs/confdLog.

  • CONFD_NOTIF_NETCONF - Syslog events that also go to /confdConf/logs/netconfLog.

  • CONFD_NOTIF_DEVEL - Syslog events that also go to /confdConf/logs/developerLog.

  • CONFD_NOTIF_TAKEOVER_SYSLOG - Syslog control.

  • CONFD_NOTIF_COMMIT_SIMPLE - Commit message.

  • CONFD_NOTIF_COMMIT_DIFF - A complete diff compared to previous configuration.

  • CONFD_NOTIF_COMMIT_FAILED - Possible data inconsistency event.

  • CONFD_NOTIF_CONFIRMED_COMMIT - Events concerning confirmed commit processing.

  • CONFD_NOTIF_COMMIT_PROGRESS - Events with commit progress information.

  • CONFD_NOTIF_USER_SESSION - Whenever a user session is started or stopped.

  • CONFD_NOTIF_HA_INFO - Changes in ConfD's perception of the cluster configuration.

  • CONFD_NOTIF_HA_INFO_SYNC - Indicates that HA notifications (CONFD_NOTIF_HA_INFO) must be synced by the application.

  • CONFD_NOTIF_SUBAGENT_INFO - Subagent related events.

  • CONFD_NOTIF_SNMPA - SNMP agent audit log.

  • CONFD_NOTIF_FORWARD_INFO - Events related to forwarding (proxying) of northbound agents.

  • CONFD_NOTIF_UPGRADE_EVENT - Events generated for in-service upgrade.

  • CONFD_NOTIF_HEARTBEAT - Heartbeat events.

  • CONFD_NOTIF_HEALTH_CHECK - Health check events.

  • CONFD_NOTIF_STREAM_EVENT - Notification stream events.

12.2. Audit Messages

Many applications need explicit control over where and in which format the various audit messages are sent. By audit messages here we mean any message related to user login/logout/reconfig activity. The list of different audit messages that are possible to receive can be found in the file confd_logsyms.h

In order to receive the audit message we must first connect a notifications socket.

Example 12.1. Creating a notification socket

confd_init("Foobar", stderr, debuglevel);
if ((notsock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
    confd_fatal("Failed to open notsocket\n");

inet_aton("127.0.0.1", &in);
addr.sin_addr.s_addr = in.s_addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(CONFD_PORT);
dflag = CONFD_NOTIF_AUDIT;
if (confd_notifications_connect(
        notsock,
        (struct sockaddr*)&addr,
        sizeof (struct sockaddr_in), dflag) < 0 ) {
    confd_fatal("Failed to confd_connect() to confd \n");
}

The dflags argument is bit mask indicating which classes of notifications messages we wish to receive over the socket. It is possible to receive several different classes of notifications messages over the same socket.

Once we have the socket setup, we add it to our pollset and invoke confd_read_notification() once the socket is ready to read.

Example 12.2. reading the audit data

while (1) {
     struct pollfd set[1];
     struct confd_notification n;
     set[0].fd = notsock;
     set[0].events = POLLIN;
     set[0].revents = 0;

     if (poll(&set[0], 1, -1) < 0) {
         perror("Poll failed:");
         continue;
     }

     if (set[0].revents & POLLIN) {
         if (confd_read_notification(notsock, &n) != CONFD_OK)
             exit(1);
         switch(n.type) {
         case CONFD_NOTIF_AUDIT:
             printf("audit: sym=%d, user=%s/%d %s\n",
                    n.n.audit.logno,
                    n.n.audit.user, n.n.audit.usid, n.n.audit.msg);
             break;
             .......

The structure struct confd_notification is defined as:

enum confd_notification_type {
    CONFD_NOTIF_AUDIT                  = (1 << 0),
    CONFD_NOTIF_DAEMON                 = (1 << 1),
    CONFD_NOTIF_TAKEOVER_SYSLOG        = (1 << 2),
    CONFD_NOTIF_COMMIT_SIMPLE          = (1 << 3),
    CONFD_NOTIF_COMMIT_DIFF            = (1 << 4),
    CONFD_NOTIF_USER_SESSION           = (1 << 5),
    CONFD_NOTIF_HA_INFO                = (1 << 6),
    CONFD_NOTIF_SUBAGENT_INFO          = (1 << 7),
    CONFD_NOTIF_COMMIT_FAILED          = (1 << 8),
    CONFD_NOTIF_SNMPA                  = (1 << 9),
    CONFD_NOTIF_FORWARD_INFO           = (1 << 10),
    CONFD_NOTIF_NETCONF                = (1 << 11),
    CONFD_NOTIF_DEVEL                  = (1 << 12),
    CONFD_NOTIF_HEARTBEAT              = (1 << 13),
    CONFD_NOTIF_CONFIRMED_COMMIT       = (1 << 14),
    CONFD_NOTIF_UPGRADE_EVENT          = (1 << 15),
    CONFD_NOTIF_COMMIT_PROGRESS        = (1 << 16),
    CONFD_NOTIF_AUDIT_SYNC             = (1 << 17),
    CONFD_NOTIF_HEALTH_CHECK           = (1 << 18),
    CONFD_NOTIF_STREAM_EVENT           = (1 << 19),
    CONFD_NOTIF_HA_INFO_SYNC           = (1 << 20),
    NCS_NOTIF_PACKAGE_RELOAD           = (1 << 21),
    NCS_NOTIF_CQ_PROGRESS              = (1 << 22),
    CONFD_NOTIF_REOPEN_LOGS            = (1 << 23)
};

struct confd_notification {
    enum confd_notification_type type;
    union {
        struct confd_audit_notification audit;
        struct confd_syslog_notification syslog;
        struct confd_commit_notification commit;
        struct confd_commit_diff_notification commit_diff;
        struct confd_user_sess_notification user_sess;
        struct confd_ha_notification hnot;
        struct confd_subagent_notification subagent;
        struct confd_forward_notification forward;
        struct confd_commit_failed_notification cfail;
        struct confd_snmpa_notification snmpa;
        struct confd_confirmed_commit_notification confirm;
        struct confd_upgrade_notification upgrade;
        struct confd_progress_notification progress;
        struct confd_stream_notification stream;
        struct ncs_cq_progress_notification cq_progress;
    } n;
};

Where the field type indicates the type of the message. Depending on the type, one of the other union structures is populated by the confd_read_notification() API function

In our case with audit messages, we get a struct confd_audit_notification structure populated.

struct confd_audit_notification {
    int logno;   /* number from confd_logsyms.h */
    char user[MAXUSERNAMELEN];
    char msg[BUFSIZ];
    int usid;   /* session id (0 means - not applicable ) */
};

The logno is an integer which defines the event. All log and audit events generated by confd are enumerated and documented in the include file confd_logsyms.h.

If we have indicated that we want to synchronize audit messages with ConfD, we must call confd_sync_audit_notification() after receiving an audit message, to signal ConfD that it can continue processing.

12.3. Syslog Messages

Some applications have explicit requirements not only where to send syslog messages (this can be easily configured in confd.conf) but also how and on which format to send the syslog messages. By default, ConfD will simply invoke the standard libc syslog() function.

It is possible to subscribe to ConfD syslog messages and also at the same time suppress ConfD's own syslogging. To subscribe to syslog messages, the application needs to use one or more of the flags CONFD_NOTIF_DAEMON, CONFD_NOTIF_NETCONF, and CONFD_NOTIF_DEVEL in the mask given to confd_notifications_connect().

If the mask given to confd_notifications_connect() contains the flag CONFD_NOTIF_TAKEOVER_SYSLOG, ConfD will not invoke the regular syslog() function. Thus in this case, it is entirely up to the application to actually report the messages.

If all notifications subscribers that have requested the CONFD_NOTIF_TAKEOVER_SYSLOG feature close their notifications sockets, ConfD will revert to the behavior of invoking libc syslog(). Similarly, when ConfD is starting, before any application processes has connected and requested the CONFD_NOTIF_TAKEOVER_SYSLOG feature, ConfD will of course use the standard syslog() functionality

When subscribing to syslog messages we receive a populated struct confd_syslog_notification structure:

struct confd_syslog_notification {
    int prio;   /* from syslog.h */
    int logno;  /* number from confd_logsyms.h */
    char msg[BUFSIZ];
};

The logno is an integer which defines the event. All syslog and audit events generated by confd are enumerated and documented in the include file confd_logsyms.h.

12.4. Commit Events

There are two different types of commit events we can subscribe to. One really simple which just indicates that a commit from a north bound agent has occurred. This is achieved by setting the subscription bitmask to contain the flag: CONFD_NOTIF_COMMIT_SIMPLE. The message we receive contains a struct confd_commit_notification structure:

struct confd_commit_notification {
    enum confd_dbname database;
    int diff_available;
    struct confd_user_info uinfo;
    int flags;
};

This just provides information on which user committed to which database e.g. running or the candidate. The other commit notification is considerably more complex and it provides information on exactly which nodes were changed.

The flag value is CONFD_NOTIF_COMMIT_DIFF, and the structure we receive is:

struct confd_commit_diff_notification {
    enum confd_dbname database;
    struct confd_user_info uinfo;
    struct confd_trans_ctx  *tctx;
    int flags;
};

The structure contains a transaction context which we can choose to use with maapi_attach()and thus attach to the currently executing transaction. When the event is generated, this transaction has successfully been committed by all data providers, but the commit operation has not completed and it is hanging, waiting for the application to invoke confd_diff_notification_done().

maapi_attach() attaches a transaction context. We can then use that transaction context to read from the transaction. The transaction has a list of nodes which constitute the configuration changes in the transaction. We can traverse this list using the function maapi_diff_iterate() which will invoke a user supplied function for each and every modification in the transaction.

The purpose of this feature is not to be able to check the commit diff. All such checking should be done using the normal validation routines. The purpose is rather to be able to log diffs on a per commit basis.

Thus the first thing we need if we want to traverse the diff list is a function to be invoked for every diff item. Our example here will just format the data and print to stdout.

static enum maapi_iter_ret iter(confd_hkeypath_t *kp,
                                enum maapi_iter_op op,
                                confd_value_t *oldv,
                                confd_value_t *v,
                                void *state)
{
    char path[BUFSIZ];
    char value[BUFSIZ];
    char *opstr;
    struct confd_cs_node *node;
    confd_hkeypath_t *dkp;
    int i;

    confd_pp_kpath(path, sizeof(path), kp);
    value[0] = 0;
    switch (op) {
    case MOP_CREATED:
        opstr = "created";
        break;
    case MOP_DELETED:
        opstr = "deleted";
        break;
    case MOP_MODIFIED:
        opstr = "modified";
        break;
    case MOP_VALUE_SET:
        opstr = "value_set";
        node = confd_find_cs_node(kp, kp->len);
        confd_val2str(node->info.type, v, value, sizeof(value));
        break;
    case MOP_MOVED_AFTER:
        if (v == NULL) {
            opstr = "moved first";
        } else {
            opstr = "moved after";
            /* create+print a hkeypath for the entry this one was moved after */
            dkp = confd_hkeypath_dup(kp);
            for (i = 0; v[i].type != C_NOEXISTS; i++) {
                confd_free_value(&dkp->v[0][i]);
                confd_value_dup_to(&v[i], &dkp->v[0][i]);
            }
            confd_pp_kpath(value, sizeof(value), dkp);
            confd_free_hkeypath(dkp);
        }
        break;
    case MOP_ATTR_SET:
        if (v[1].type == C_NOEXISTS) {
            opstr = "attr_del";
            snprintf(value, sizeof(value), "%s", attr_str(&v[0]));
        } else {
            opstr = "attr_set";
            i = snprintf(value, sizeof(value), "%s -> ", attr_str(&v[0]));
            confd_pp_value(&value[i], sizeof(value) - i, &v[1]);
        }
        break;
    }
    printf ("ITER %s %s %s\n", path, opstr, value);
    return ITER_RECURSE;
}

The iteration function must return an enum maapi_iter_ret indicating to ConfD what to continue to do. We have the following possible return values:

  • ITER_STOP - Stop. Do not invoke the iteration function any more for this transaction

  • ITER_RECURSE - Iteration continues with all children of the modified node.

  • ITER_CONTINUE - Iteration ignores the children of the node and continues with the node's sibling.

The iteration function is called for each modified node in the configuration. See the description of maapi_diff_iterate() in confd_lib_maapi(3) for a detailed description of when the different op values MOP_CREATED, MOP_DELETED, MOP_MODIFIED, MOP_VALUE_SET, and MOP_MOVED_AFTER are used.

Finally we must have a function which is invoked whenever we receive a notification of type CONFD_NOTIF_COMMIT_DIFF. The function must use the supplied transaction context and attach, and when it is done traversing the diff it must call confd_diff_notification_done().

static void handle_diff_notif(struct confd_trans_ctx *tctx)
{
    /* first we need a maapi socket */
    int maapi_socket;

    if ((maapi_socket = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
        confd_fatal("Failed to open socket\n");

    if (maapi_connect(maapi_socket, (struct sockaddr*)&addr,
                      sizeof (struct sockaddr_in)) < 0)
        confd_fatal("Failed to confd_connect() to confd \n");

    /* no namespace needed for this */
    OK(maapi_attach(maapi_socket, -1, tctx));

    /* Now we can iterate through the currently hanging transaction */
    /* and read out all the diffs */
    OK(maapi_diff_iterate(maapi_socket, tctx->thandle, iter,
                          ITER_WANT_ATTR, NULL));

    /* and finally call done to release data and let  */
    /* the transaction finish */

    OK(confd_diff_notification_done(notif_socket, tctx));
    close(maapi_socket);
}

12.5. Commit Failure Events

The CONFD_NOTIF_COMMIT_FAILED event is generated when a data provider fails in its commit callback. ConfD executes a two-phase commit procedure towards all data providers when committing transactions. When a provider fails in commit, the system is an unknown state. See confd_lib_maapi(3) and the function maapi_get_running_db_status(). If the provider is "external", the name of the failing daemon is provided. If the provider is another NETCONF agent, the IP address and port of that agent is provided.

12.6. Confirmed Commit Events

When a a user has started a confirmed commit, when a confirming commit is issued, or when a confirmed commit is aborted, a CONFD_NOTIF_CONFIRMED_COMMIT event is generated. The application receives a struct confd_confirmed_commit_notification, which gives the specific action and user session info for the committer. For a confirmed commit, the timeout value is also given.

12.7. Commit Progress Events

By subscribing to the CONFD_NOTIF_COMMIT_PROGRESS event, the application can receive the same commit progress information that is reported when the commit | details CLI command is used. The application receives a struct confd_progress_notification structure.

12.8. User Sessions

We can get notifications on user sessions and on user session events. A user session corresponds to an actual user logging in to the system, for example a NETCONF manager

The struct confd_user_sess_notification structure is defined as:

enum confd_user_sess_type {
    CONFD_USER_SESS_START = 1,       /* a user session is started */
    CONFD_USER_SESS_STOP = 2,
    CONFD_USER_SESS_LOCK = 3,        /* a database is locked */
    CONFD_USER_SESS_UNLOCK = 4,
    CONFD_USER_SESS_START_TRANS = 5, /* a database transaction is started */
    CONFD_USER_SESS_STOP_TRANS = 6
};

struct confd_user_sess_notification {
    enum confd_user_sess_type type;
    struct confd_user_info uinfo;
    enum confd_dbname database;
};

This means that we can follow the progress of a user session, which databases are touched by the session etc.

12.9. High Availability - Cluster Events

ConfD HA capabilities are described in Chapter 24, High Availability. This section describes the various events that are asynchronously produced by ConfD when the cluster configuration is changed. These changes may be induced explicitly by the application through invocation of the various HA related API functions in libconfd or they may be induced by ConfD itself when the sockets between the HA nodes get closed. It is vital that the High-Availability-Framework (HAFW) subscribes to these messages and acts accordingly.

The struct confd_notification structure received by confd_read_notification() will populate the hnot field with a struct confd_ha_notification. This in its turn is yet another union structure with a type field.

struct confd_ha_notification {
    enum confd_ha_info_type type;
    /* additional info for various info types */
    union {
        int nomaster;                      /* CONFD_HA_INFO_NOMASTER  */
        struct confd_ha_node slave_died;   /* CONFD_HA_INFO_SLAVE_DIED */
        struct confd_ha_node slave_arrived;/* CONFD_HA_INFO_SLAVE_ARRIVED*/
        int cdb_initialized_by_copy;       /* CONFD_HA_INFO_SLAVE_INITIALIZED */
        int beslave_result;                /* CONFD_HA_INFO_BESLAVE_RESULT */
    } data;
};

We start with a listing of types of the different HA related events that ConfD can send to the subscribing application. The enum is defined as:

enum confd_ha_info_type {
    CONFD_HA_INFO_NOMASTER          = 1,   /* we have no master */
    CONFD_HA_INFO_SLAVE_DIED        = 2,   /* a slave disappeared */
    CONFD_HA_INFO_SLAVE_ARRIVED     = 3,   /* a slave arrived to us */
    CONFD_HA_INFO_SLAVE_INITIALIZED = 4,   /* CDB is initialized */
    CONFD_HA_INFO_IS_MASTER         = 5,   /* we are now master */
    CONFD_HA_INFO_IS_NONE           = 6,   /* we are now none */
    CONFD_HA_INFO_BESLAVE_RESULT    = 7    /* result of async beslave() */
};

Each of the different informational messages has additional data associated to it.

  • CONFD_HA_INFO_NOMASTER A node (which is a slave node) has lost contact with the master and is now in HA state CONFD_HA_STATE_NONE. Only sent on the slave node.

    Whenever we receive this message the nomaster field is populated. This is either the integer CONFD_ERR_HA_CLOSED if the slave lost contact with master due to the socket getting closed or the integer CONFD_ERR_HA_NOTICK if the slave has not received any live ticks from the master.

  • CONFD_HA_INFO_SLAVE_DIED A master node lost contact with a slave node. Only sent on the master node. The field slave_died is populated with a struct confd_ha_node indicating which particular slave died.

  • CONFD_HA_INFO_SLAVE_ARRIVED A master node was connected to by a slave node. Authentication was ok and the slave is initializing its CDB database. Only sent at the master node. The field slave_arrived is populated with a struct confd_ha_node indicating which slave arrived.

  • CONFD_HA_INFO_SLAVE_INITIALIZED A slave node has just finished its initialization and synchronization of the database. The slave is now fully operational. Only sent at slave nodes. The field cdb_initialized_by_copy is set to 1 if ConfD concluded that the entire CDB database has to be copied and 0 if a copy was avoided.

  • CONFD_HA_INFO_IS_MASTER The node has been successfully elevated to master. This is only sent at the master node, i.e. the node that just became master.

  • CONFD_HA_INFO_IS_NONE The node has been set to NONE mode.

  • CONFD_HA_INFO_BESLAVE_RESULT If we use asynchronous invocation of the confd_ha_beslave() function, i.e. with the parameter waitreply set to 0, this message is sent when the operation has completed. The field beslave_result is set to indicate the result which would have been returned by a synchronous invocation of confd_ha_beslave(). Thus if beslave_result is 0, the node has successfully become a slave, otherwise beslave_result is one of the confd_errno values that can be returned by synchronous invocation of confd_ha_beslave().

If we have indicated that we want to synchronize HA messages with ConfD, we must call confd_sync_ha_notification() after receiving a HA message, to signal ConfD that it can continue processing.

12.10. Subagent Events

The subagent mechanism is described in Chapter 26, Subagents and Proxies. This section describes the related events which ConfD generates when acting as a master agent.

When the notification type is CONFD_NOTIF_SUBAGENT_INFO, the struct confd_notification structure received by confd_read_notification() will populate the subagent field with a struct confd_subagent_notification.

struct confd_subagent_notification {
    enum confd_subagent_info_type type;
    char name[MAXAGENTNAMELEN];
};

The type field is one of the values CONFD_SUBAGENT_INFO_UP or CONFD_SUBAGENT_INFO_DOWN.

At first, each subagent is marked as being down. When ConfD successfully communicates with a subagent, it is marked as up, and a corresponding event is generated. A down event is generated only if ConfD tries to communicate with a subagent, but fails. Thus, if a subagent closes an idle connection to the master agent, it is not marked as down.

12.11. SNMP Agent Audit Log

The SNMP agent log is activated through the /confdCfg/logs/snmpLog element in the confd.conf configuration file.

The SNMP audit log messages can also be received and processed by an external C program over a notification socket. The application receives a struct confd_snmpa_notification structure. The structure contains a series of fields describing the sent or received SNMP PDU. It also contains a list of all varbinds in the PDU.

Each varbind contains a confd_value_t with the string representation of the SNMP value. Thus the type of the value in a varbind is always C_BUF. See confd_events.h include file for the details of the received structure.

The following code exemplifies how we write a program which establishes a notification socket and subscribes to all SNMP PDUs in and out of the system.

We start off with some auxiliary function to format the PDU type and the type of a "varbind"

char *vb_type(struct confd_snmp_varbind *vb) {
    switch (vb->vartype) {
    case CONFD_SNMP_NULL: return "NULL";
    case CONFD_SNMP_INTEGER: return "INTEGER";
    case CONFD_SNMP_Interger32: return "Integer32";
    case CONFD_SNMP_OCTET_STRING: return "OCTET STRING";
    case CONFD_SNMP_OBJECT_IDENTIFIER: return "OBJECT IDENTIFIER";
    case CONFD_SNMP_IpAddress: return "IpAddress";
    case CONFD_SNMP_Counter32: return "Counter32";
    case CONFD_SNMP_TimeTicks: return "TimeTicks";
    case CONFD_SNMP_Opaque: return "Opaque";
    case CONFD_SNMP_Counter64: return "Counter64";
    case CONFD_SNMP_Unsigned32: return "Unsigned32";
    }
    return "";
}

char *pdutype(struct confd_snmpa_notification *snmp) {
    switch (snmp->pdu_type) {
    case CONFD_SNMPA_PDU_V1TRAP: return("V1TRAP");
    case CONFD_SNMPA_PDU_V2TRAP: return("V2TRAP");
    case CONFD_SNMPA_PDU_INFORM: return("INFORM");
    case CONFD_SNMPA_PDU_GET_RESPONSE: return("GET_RESPONSE");
    case CONFD_SNMPA_PDU_GET_REQUEST: return("GET_REQUEST");
    case CONFD_SNMPA_PDU_GET_NEXT_REQUEST: return("GET_NEXT_REQUEST");
    case CONFD_SNMPA_PDU_REPORT: return("REPORT");
    case CONFD_SNMPA_PDU_GET_BULK_REQUEST: return("GET_BULK_REQUEST");
    case CONFD_SNMPA_PDU_SET_REQUEST: return("SET_REQUEST");
    default: return "";
    }
}

Following that we show the code which invokes confd_read_notification() and reads a C structure of the type struct confd_snmpa_notification

The structure contains the type of the PDU, various other fields and also the complete SNMP "varbind" lists in the PDU. The code prints the PDU type and then loops through all the varbinds and prints the value of each varbind.

    if (confd_read_notification(notsock, &n) != CONFD_OK)
        exit(1);
    switch(n.type) {
    case CONFD_NOTIF_SNMPA: {
        int i,j;

        char buf[BUFSIZ];
        buf[0] = 0;
        char *ptr = &buf[0];
        struct confd_snmpa_notification *snmp = &n.n.snmpa;
        ptr += sprintf(ptr, "%s ", pdutype(snmp));
        ptr += sprintf(ptr,"Id = %d ", snmp->request_id);
        struct confd_ip *ip = &(snmp->ip);
        ptr += sprintf(ptr, " %s:%d ",
                       inet_ntoa(ip->ip.v4),
                       snmp->port);
        if ((snmp->error_status !=0 || snmp->error_index != 0)) {
            ptr += sprintf(ptr, "ErrIx = %d ", snmp->error_index);
        }
        else if (snmp->pdu_type == CONFD_SNMPA_PDU_V1TRAP) {
            ptr += sprintf(ptr,"Generic=%d Specific=%d",
                           snmp->v1_trap->generic_trap,
                           snmp->v1_trap->specific_trap);
            struct confd_snmp_oid *enterp = &snmp->v1_trap->enterprise;
            ptr += sprintf(ptr, " Enterprise=");
            for(i=0; i < enterp->len; i++) {
                ptr += sprintf(ptr,".%d", enterp->oid[i]);
            }
        }
        for (i=0; i < snmp->num_variables; i++) {
            struct confd_snmp_varbind *vb = &snmp->vb[i];
            ptr += sprintf(ptr,"\n   ");
            switch (vb->type) {
            case CONFD_SNMP_VARIABLE:
                ptr += sprintf(ptr, " %s ", vb_type(vb));
                ptr += sprintf(ptr,"%s=", vb->var.name);
                break;
            case CONFD_SNMP_OID:
                ptr += sprintf(ptr, " %s ", vb_type(vb));
                for (j=0; j < vb->var.oid.len; j++) {
                    ptr += sprintf(ptr,"%d", vb->var.oid.oid[j]);
                    if (j != vb->var.oid.len-1)
                        ptr += sprintf(ptr,".");
                }
                break;
            case CONFD_SNMP_COL_ROW:
                ptr += sprintf(ptr, " %s ", vb_type(vb));
                ptr += sprintf(ptr, "%s", vb->var.cr.column);
                for(j=0; j<vb->var.cr.rowindex.len; j++) {
                    ptr += sprintf(ptr,".%d",
                                   vb->var.cr.rowindex.oid[j]);
                }
                break;
            }
            if (vb->val.type == C_BUF) {
                char buf2[BUFSIZ];
                confd_pp_value(buf2, BUFSIZ, &vb->val);
                ptr += sprintf(ptr, "=%s", buf2);
            }
        }
        printf("%s\n\n", buf);
        confd_free_notification(&n);
    }

12.12. Forwarding Events

ConfD can forward (proxy) connections from northbound agents. When forwarding starts, ends, or fails, a CONFD_NOTIF_FORWARD_INFO event is generated. The application receives a struct confd_forward_notification structure which gives the type of forwarding event, the name of the target for the forwarding, and user session information for the user that requested the forwarding.

12.13. In-service Upgrade Events

During in-service upgrade, the CONFD_NOTIF_UPGRADE_EVENT event is generated with different values for the enum confd_upgrade_event_type event. The events correspond to the different phases of the upgrade, see Chapter 13, In-service Data Model Upgrade and confd_lib_maapi(3) for a detailed description.

12.14. Heartbeat and Health Check Events

The CONFD_NOTIF_HEARTBEAT and CONFD_NOTIF_HEALTH_CHECK events can be used by applications that wish to monitor the health and liveness of ConfD itself. See confd_lib_events(3) for more details about this.

12.15. Notification stream Events

The CONFD_NOTIF_STREAM_EVENT event is generated for a notification stream, i.e. event notifications sent by an application as described in the section called “NOTIFICATION STREAMS” of confd_lib_dp(3). See confd_lib_events(3) for more details about this.