get_object() passes the wrong keys when I expose both key names

    container arpentries {
        config false;
        list arpe {
            key "ip ifname";
            max-elements 1024;
            leaf ip {
                tailf:cli-expose-key-name;
                type inet:ip-address;
            }   
            leaf ifname {
                tailf:cli-expose-key-name;
                type string;
            }   
            leaf hwaddr {
                type string;
                mandatory true;
            }   
            leaf permanent {
                type boolean;
                mandatory true;
            }   
            leaf published {
                type boolean;
                mandatory true; 
           }   
    }   

As the yang model show, I expose both of the key names by tailf:cli-expose-key-name. And the I write the get_object() callback as below:

static int get_arpe_object(struct confd_trans_ctx *tctx, confd_hkeypath_t *keypath)
{
    confd_value_t vs[10];
    int n = 0;
    struct arpdata *dp;
    struct aentry *ae;

    dp = malloc(sizeof(struct arpdata));
    memset(dp, 0, sizeof(struct arpdata));
    if (run_arp(dp) == CONFD_ERR) {
        free(dp);
        return CONFD_ERR;
    }   

    ae = find_ae(keypath, dp);
    CONFD_SET_IPV4(&vs[n], ae->ip4);n++;
    CONFD_SET_STR(&vs[n], ae->iface);n++;
    CONFD_SET_STR(&vs[n], ae->hwaddr);n++;
    CONFD_SET_BOOL(&vs[n], ae->perm);n++; 
    CONFD_SET_BOOL(&vs[n], ae->pub);n++;

    confd_data_reply_value_array(tctx, vs, n); 
    free(dp);
    return CONFD_OK;
}

The problem is when I show as below, get_object() called twice, with the wrong key /arpentries/arpe{172.16.25.142 ifname}, however when the key is not exposed it works fine.

no matter whether the key is exposed, command

localhost# show arpentries arpe

works correctly.

localhost# show arpentries arpe ip 172.16.25.142 ifname eno16777736
......
TRACE CALL trans init(thandle=10,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_object(thandle=10,/arpentries/arpe{172.16.25.142 eno16777736}) --> CONFD_OK
TRACE CALL data get_object(thandle=10,/arpentries/arpe{172.16.25.142 ifname})

Is this a bug?

I believe the issue is with your get_arpe_object( ) implementation. You can refer to an earlier posting on how to write get_object( ) for this example as a reference.

The code at bottom of this reply is full of confd-6.1/examples.confd/intro/5-c_stats/arpstat.c
I modified the file following the guide of the earlier posting

Then I show the list with both key given

localhost# show arpentries arpe 172.16.25.142 eno16777736          
IP             IFNAME       HWADDR             PERMANENT  PUBLISHED  
---------------------------------------------------------------------
172.16.25.142  eno16777736  08:00:27:6e:4a:ef  false      false      

localhost# 

Look at the log, get_object() called only once with the correct key value {172.16.25.142 eno16777736}, and only get_object() called.

TRACE New user session: 11 for user:admin ctx:cli --> CONFD_OK
TRACE CALL trans init(thandle=6,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_object(thandle=6,/arpentries/arpe{172.16.25.142 eno16777736}) --> CONFD_OK

And then I modify the arpe.yang to expose both key name:

      leaf ip {
        tailf:cli-expose-key-name; // modified to expose
        type inet:ip-address;
      }   
      leaf ifname {
        tailf:cli-expose-key-name; // modified to expose 
        type string;
      }   

Again show list with both keys given, which different from previous is we should explicitily give the key name ip and ifname, the CLI output is the same as previous,

localhost# show arpentries arpe ip 172.16.25.142 ifname eno16777736
IP             IFNAME       HWADDR             PERMANENT  PUBLISHED  
---------------------------------------------------------------------
172.16.25.142  eno16777736  08:00:27:6e:4a:ef  false      false      

but look at the TRACE log, get_object() called three times, get_next() called two times, and the 2nd call of get_object() passes the wrong key value {172.16.25.142 ifname}, ifname is the key name, not the key value. And this is the point my post care about.

Because my own code does not deal with the invalid key values, it can’t give any further output. After trying the example code, I’ve got another questions:

  1. Why get_object() called 3 times?
  2. Why get_next() called with both key values given, which not called while the key name is not exposed?

Hope I express myself clearly, thank you.

TRACE New user session: 11 for user:admin ctx:cli --> CONFD_OK
TRACE CALL trans init(thandle=6,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_object(thandle=6,/arpentries/arpe{172.16.25.142 eno16777736}) --> CONFD_OK
TRACE CALL data get_object(thandle=6,/arpentries/arpe{172.16.25.142 ifname}) (NOEXISTS)  --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, 10172080) --> CONFD_OK
TRACE CALL data get_object(thandle=6,/arpentries/arpe{172.16.25.142 eno16777736}) --> CONFD_OK

/*********************************************************************
 * ConfD Stats intro example
 * Implements an operational data provider
 *
 * (C) 2005-2007 Tail-f Systems
 * Permission to use this code as a starting point hereby granted
 *
 * See the README file for more information
 ********************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>

#ifdef __QNX__
/* too hard to figure out which #defines
   are needed for this to get included */
extern char *strdup(const char *string);
#endif

#include <confd_lib.h>
#include <confd_dp.h>
#include "arpe.h"

#if !defined(Linux) && !defined(__NetBSD__) && !defined(__FreeBSD__) && \
    !defined(__QNX__) && !defined(Darwin)
#warning "arpstat: Not tested on this OS"
#endif

/********************************************************************/

/* Our daemon context as a global variable */
static struct confd_daemon_ctx *dctx;
static int ctlsock;
static int workersock;


struct aentry {
    struct in_addr ip4;
    char *hwaddr;
    int perm;
    int pub;
    char *iface;
    struct aentry *next;
};

struct arpdata {
    struct aentry *arp_entries;
    struct timeval lastparse;
};


/********************************************************************/

/* do we need to rerun the arp command */
int need_arp(struct arpdata *dp)
{
    struct timeval now;

    gettimeofday(&now, NULL);
    if ((dp->lastparse.tv_sec == 0) ||
        (now.tv_sec > dp->lastparse.tv_sec + 2) ||
        (dp->arp_entries == NULL))
        return 1;
    return 0;
}

static void free_arp(struct arpdata *dp)
{
    struct aentry *ae = dp->arp_entries;

    while (ae) {
        struct aentry *next = ae->next;
        if(ae->hwaddr) free(ae->hwaddr);
        if(ae->iface) free(ae->iface);
        free(ae);
        ae = next;
    }
    dp->arp_entries = NULL;
}

/* add an entry, keeping the list ordered */
static void add_aentry(struct aentry **first, struct aentry *new)
{
    struct aentry **prev = first;

    while (*prev != NULL &&
           (memcmp(&new->ip4, &(*prev)->ip4, sizeof(struct in_addr)) > 0 ||
            (memcmp(&new->ip4, &(*prev)->ip4, sizeof(struct in_addr)) == 0 &&
             strcmp(new->iface, (*prev)->iface) > 0))) {
        prev = &(*prev)->next;
    }
    new->next = *prev;
    *prev = new;
}

/* parse output fom arp -an for this transaction */
static int run_arp(struct arpdata *dp)
{
    char *sep = " ?()<>\n";
    struct aentry *ae = NULL;
    FILE *fp;
    char buf[BUFSIZ];

    free_arp(dp);

    if ((fp = popen("arp -an", "r")) == NULL)
        return CONFD_ERR;
    while (fgets(&buf[0], BUFSIZ, fp) != NULL) {
        char *cp = strtok(&buf[0], sep);

        if ((ae = (struct aentry*) malloc(sizeof(struct aentry))) == NULL) {
            pclose(fp);
            return CONFD_ERR;
        }
        memset((void*)ae, 0, sizeof(struct aentry));

        /* Now lazy parse lines like */
        /* ? (192.168.1.1) at 00:0F:B5:EF:11:00 [ether] on eth0 */
        /* slightly different arp output on Linux and BSD */

        ae->ip4.s_addr = inet_addr(cp);
        /* skip "at" */
        assert(strcmp(strtok(NULL, sep), "at") == 0);
        cp = strtok(NULL, sep);

        if ((strcmp(cp, "incomplete") == 0)) {
            assert(strcmp(strtok(NULL, sep), "on") == 0);
            cp = strtok(NULL, sep);
        } else if ((strcmp(cp, "<from_interface>") == 0)) {
            cp = strtok(NULL, sep);
            while (cp) {
                if (strcmp(cp, "on") == 0) {
                    cp = strtok(NULL, sep);
                    break;
                }
                cp = strtok(NULL, sep);
            }
        } else {
            /* some common error cases handled, get real hw addr */
            ae->hwaddr = strdup(cp);

            while (1) {
                cp = strtok(NULL, sep);
                if (cp == NULL)
                    break;
                else if (strcmp(cp, "PERM") == 0)
                    ae->perm = 1;
                else if (strcmp(cp, "PUB") == 0)
                    ae->pub = 1;
                else if (strcmp(cp, "[ether]") == 0)
                    ;
                else if (strcmp(cp, "on") == 0) {
                    cp = strtok(NULL, sep);
                    break;
                }
            }
        }

        /* cp should now point to the interface name
           - this is required since it is a key */
        if (cp) {
            ae->iface = strdup(cp);

            /* Some OSes have perm/pub after interface name */
            while ((cp = strtok(NULL, sep)) != NULL) {
                if (strcmp(cp, "permanent") == 0)
                    ae->perm = 1;
                else if (strcmp(cp, "published") == 0)
                    ae->pub = 1;
            }

            add_aentry(&dp->arp_entries, ae);
        } else {
            /* skip this entry */
            free(ae);
        }
    }
    /* remember the time when we did this */
    gettimeofday(&dp->lastparse, NULL);
    pclose(fp);
    return CONFD_OK;
}

/********************************************************************/

static int s_init(struct confd_trans_ctx *tctx)
{
    struct arpdata *dp;

    if ((dp = malloc(sizeof(struct arpdata))) == NULL)
        return CONFD_ERR;
    memset(dp, 0, sizeof(struct arpdata));
    if (run_arp(dp) == CONFD_ERR) {
        free(dp);
        return CONFD_ERR;
    }
    tctx->t_opaque = dp;
    confd_trans_set_fd(tctx, workersock);
    return CONFD_OK;
}

static int s_finish(struct confd_trans_ctx *tctx)
{
    struct arpdata *dp = tctx->t_opaque;

    if (dp != NULL) {
        free_arp(dp);
        free(dp);
    }
    return CONFD_OK;
}

/********************************************************************/

static int get_next(struct confd_trans_ctx *tctx,
                         confd_hkeypath_t *keypath,
                         long next)
{
    struct arpdata *dp = tctx->t_opaque;
    struct aentry *curr;
    confd_value_t v[2];

    if (next == -1) {  /* first call */
        if (need_arp(dp)) {
            if (run_arp(dp) == CONFD_ERR)
                return CONFD_ERR;
        }
        curr = dp->arp_entries;
    } else {
        curr = (struct aentry *)next;
    }
    if (curr == NULL) {
        confd_data_reply_next_key(tctx, NULL, -1, -1);
        return CONFD_OK;
    }

    /* 2 keys */
    CONFD_SET_IPV4(&v[0], curr->ip4);
    CONFD_SET_STR(&v[1], curr->iface);
    confd_data_reply_next_key(tctx, &v[0], 2, (long)curr->next);
    return CONFD_OK;
}

/* set pos to 0 if called from get_object( ) as the 0th [0][]
 *    elements of the 2 dimensional hkeypath array contain the 2 keys */
/* set pos to 1 if called from get_elem( ) as the 1st [1][]
 *    elements of the 2 dimensional hkeypath array contain the 2 keys */
static struct aentry *find_ae(confd_hkeypath_t *keypath, struct arpdata *dp, int pos)
{
    struct in_addr ip = CONFD_GET_IPV4(&keypath->v[pos][0]);
    char *iface = (char*)CONFD_GET_BUFPTR(&keypath->v[pos][1]);

    struct aentry *ae = dp->arp_entries;

    while (ae != NULL) {
        if (ip.s_addr == ae->ip4.s_addr &&
                (strcmp(ae->iface, iface) == 0) )
            return ae;
        ae=ae->next;
    }
    return NULL;
}

static int get_object(struct confd_trans_ctx *tctx, confd_hkeypath_t *keypath)
{
    confd_value_t v[5];

    struct aentry *ae = find_ae(keypath, tctx->t_opaque, 0);
    if (ae == NULL) {
        confd_data_reply_not_found(tctx);
        return CONFD_OK;
    }

    CONFD_SET_IPV4(&v[0], ae->ip4);
    CONFD_SET_STR(&v[1], ae->iface);
    if (ae->hwaddr == NULL) {
        CONFD_SET_NOEXISTS(&v[2]);
    } else {
        CONFD_SET_STR(&v[2], ae->hwaddr);
    }
    CONFD_SET_BOOL(&v[3], ae->perm);
    CONFD_SET_BOOL(&v[4], ae->pub);

    confd_data_reply_value_array(tctx, v, 5);

    return CONFD_OK;
}

/* Keypath example */
/* /arpentries/arpe{192.168.1.1 eth0}/hwaddr */
/*    3         2         1             0    */

static int get_elem(struct confd_trans_ctx *tctx,
                    confd_hkeypath_t *keypath)
{
    confd_value_t v;

    struct aentry *ae = find_ae(keypath, tctx->t_opaque, 1);
    if (ae == NULL) {
        confd_data_reply_not_found(tctx);
        return CONFD_OK;
    }
    switch (CONFD_GET_XMLTAG(&(keypath->v[0][0]))) {
    case arpe_hwaddr:
        if (ae->hwaddr == NULL) {
            confd_data_reply_not_found(tctx);
            return CONFD_OK;
        }
        CONFD_SET_STR(&v, ae->hwaddr);
        break;
    case arpe_permanent:
        CONFD_SET_BOOL(&v, ae->perm);
        break;
    case arpe_published:
        CONFD_SET_BOOL(&v, ae->pub);
        break;
    case arpe_ip:
        CONFD_SET_IPV4(&v, ae->ip4);
        break;
    case arpe_ifname:
        CONFD_SET_STR(&v, ae->iface);
        break;
    default:
        return CONFD_ERR;
    }
    confd_data_reply_value(tctx, &v);
    return CONFD_OK;
}

/********************************************************************/

int main(int argc, char *argv[])
{
    struct sockaddr_in addr;
    int debuglevel = CONFD_TRACE;
    struct confd_trans_cbs trans;
    struct confd_data_cbs data;

    memset(&trans, 0, sizeof (struct confd_trans_cbs));
    trans.init = s_init;
    trans.finish = s_finish;

    memset(&data, 0, sizeof (struct confd_data_cbs));
    data.get_elem = get_elem;
    data.get_next = get_next;
    data.get_object = get_object;
    strcpy(data.callpoint, arpe__callpointid_arpe);

    /* initialize confd library */
    confd_init("arpe_daemon", stderr, debuglevel);

    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(CONFD_PORT);

    if (confd_load_schemas((struct sockaddr*)&addr,
                           sizeof (struct sockaddr_in)) != CONFD_OK)
        confd_fatal("Failed to load schemas from confd\n");

    if ((dctx = confd_init_daemon("arpe_daemon")) == NULL)
        confd_fatal("Failed to initialize confdlib\n");

    /* Create the first control socket, all requests to */
    /* create new transactions arrive here */

    if ((ctlsock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
        confd_fatal("Failed to open ctlsocket\n");
    if (confd_connect(dctx, ctlsock, CONTROL_SOCKET, (struct sockaddr*)&addr,
                      sizeof (struct sockaddr_in)) < 0)
        confd_fatal("Failed to confd_connect() to confd \n");

    /* Also establish a workersocket, this is the most simple */
    /* case where we have just one ctlsock and one workersock */

    if ((workersock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
        confd_fatal("Failed to open workersocket\n");
    if (confd_connect(dctx, workersock, WORKER_SOCKET,(struct sockaddr*)&addr,
                      sizeof (struct sockaddr_in)) < 0)
        confd_fatal("Failed to confd_connect() to confd \n");

    if (confd_register_trans_cb(dctx, &trans) == CONFD_ERR)
        confd_fatal("Failed to register trans cb \n");

    if (confd_register_data_cb(dctx, &data) == CONFD_ERR)
        confd_fatal("Failed to register data cb \n");

    if (confd_register_done(dctx) != CONFD_OK)
        confd_fatal("Failed to complete registration \n");

    while(1) {
        struct pollfd set[2];
        int ret;

        set[0].fd = ctlsock;
        set[0].events = POLLIN;
        set[0].revents = 0;

        set[1].fd = workersock;
        set[1].events = POLLIN;
        set[1].revents = 0;

        if (poll(set, sizeof(set)/sizeof(*set), -1) < 0) {
            perror("Poll failed:");
            continue;
        }

        /* Check for I/O */
        if (set[0].revents & POLLIN) {
            if ((ret = confd_fd_ready(dctx, ctlsock)) == CONFD_EOF) {
                confd_fatal("Control socket closed\n");
            } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
                confd_fatal("Error on control socket request: %s (%d): %s\n",
                     confd_strerror(confd_errno), confd_errno, confd_lasterr());
            }
        }
        if (set[1].revents & POLLIN) {
            if ((ret = confd_fd_ready(dctx, workersock)) == CONFD_EOF) {
                confd_fatal("Worker socket closed\n");
            } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
                confd_fatal("Error on worker socket request: %s (%d): %s\n",
                     confd_strerror(confd_errno), confd_errno, confd_lasterr());
            }
        }
    }
}

/********************************************************************/

Thanks for the further explanation. It does look like this may be a bug. Let us investigate it. We will address this issue in a future release of ConfD if this is indeed a bug.