Avatar

Exordium

In the previous installment of the onePK series, you received a crash course on Cisco’s onePK. In this article, you’ll take the next step with a fun little exposé on onePK’s C API. You will learn how to write a simple program to reach out and connect to a network element. This is staple onePK functionality and is the foundation upon which most onePK applications are built.

Preambling Details

The following short program “ophw” (onePK Hello World), is a fully functional onePK application that will connect to a network element, query its system description, and then disconnect. It doesn’t do anything beyond that, but it does highlight some lynchpin onePK code: network element connection and session handle instantiation. This is the foundational stuff every onePK application needs before useful work can get done.

For this example, I will use the End-Node Hosting model of deployment and build and run the application from a Linux machine on the same network as the onePK-enabled router.

Required Items

Before you can reach out to a network element, you’ll need three pieces of information:

  • Network Element IP Address: This is the IP address of the switch or router with running an IOS image that supports onePK
  • Network Element Username: A previously established username on the network element
  • Network Element Password: The password for the username on the network element

Give Me a Fish

Let’s fire away and see what happens:

cisco@ubuntu:~/onePK-sdk-0.7.0.503/c/sample-apps/HelloWorld$ ./bin/ophw -a 10.10.10.130 -u cisco -p cisco
Connecting to 10.10.10.130...
Connected! Hello router!
System Description: Cisco IOS Software, vios Software (vios-ADVENTERPRISEK9-M), Version 15.3(1.2.31)PI21, EARLY DEPLOYMENT ENGINEERING WEEKLY BUILD, synced to  V153_2_T
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2013 by Cisco Systems, Inc.
Compiled Sun 02-Jun-13 23:27 by nmcgill
Disconnecting from 10.10.10.130...

Sweet. Looks like our program connected to a onePK-enabled router and got some system information, then disconnected. Let’s move on and see how this works…

Teach Me to Fish

What follows is a fully functional onePK program. If you cut the code from the scroll boxes, paste it into a .c file, and drop it into a properly setup onePK development environment, this program will go. Let’s break it down, section by section, and see what’s really go on here.

Common C Program Artifacts

First and foremost you can gloss over the boring preamble contact info, legalese boilerplate, include directives, and function prototypes. A few things to notice here are the onePK specific includes (you’ll need these in your programs) and the TRY macro. A very common construct in onePK programs is the call, check return code, if error; report and bail sequence of events. This macro does just that and makes for tidy code.

/*
 * Copyright (c) 2013, Cisco Systems, Inc.
 * Applied Security Intelligence
 * Mike Schiffman <mschiffm@cisco.com>
 *
 * THIS SAMPLE CODE IS PROVIDED "AS IS" WITHOUT ANY EXPRESS OR IMPLIED WARRANTY
 * BY CISCO SOLELY FOR THE PURPOSE of PROVIDING PROGRAMMING EXAMPLES.
 * CISCO SHALL NOT BE HELD LIABLE FOR ANY USE OF THE SAMPLE CODE IN ANY
 * APPLICATION.
 *
 * Redistribution and use of the sample code, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * Redistributions of source code must retain the above disclaimer.
 */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>

#include "onep_core_services.h"
#include "onep_constants.h"
/** shorthand macro for very common onePK func call / check for error */
#define TRY(_rc, _expr, _eb, _fmt, _args...)                                \
    if (((_rc) = (_expr)) != ONEP_OK)                                       \
    {                                                                       \
            snprintf(_eb, BUFSIZ - 1, "%s:%d: error: %s: " _fmt,            \
            __FILE__, __LINE__, onep_strerror((_rc)), ##_args);             \
        return ((NULL));                                                    \
    }

#define APPNAME "OPHW"
#define DQ_STR_SIZE 16
void usage(char *);
session_handle_t *
simple_connect(char *, char *, char *, char *, network_element_t **, network_application_t **, char *);
void simple_disconnect(network_element_t **, session_handle_t **, network_application_t **);

Main

The main program body is delightfully straightforward. After initializing program variables with default values, the program descends into argument processing. Next it will ensure it has three user-supplied strings purporting to be legitimate onePK username, password, and target device. Now it’s time to get to work:

int
main(int argc, char* argv[])
{
    onep_status_t r;
    char opt, *sysdescr;
    network_element_t *ne;
    onep_username username;
    onep_password password;
    session_handle_t *onepk;
    element_property_t *property;
    network_application_t *n;
    char ne_addr[DQ_STR_SIZE], errbuf[BUFSIZ];

    ne        = NULL;
    onepk     = NULL;
    sysdescr  = NULL;
    memset(ne_addr,  0, DQ_STR_SIZE);
    memset(username, 0, ONEP_USERNAME_SIZE);
    memset(password, 0, ONEP_PASSWORD_SIZE);
    while ((opt = getopt(argc, argv, "a:u:p:")) >= 0)
    {
        switch (opt)
        {
            case 'a':
                strncpy(ne_addr, optarg, DQ_STR_SIZE - 1);
                break;
            case 'p':
                strncpy(password, optarg, ONEP_USERNAME_SIZE - 1);
                break;
            case 'u':
                strncpy(username, optarg, ONEP_USERNAME_SIZE - 1);
                break;
            default:
                usage(argv[0]);
                return -1;
        }
    }
    /** ensure I have a network element to connect to and a username/password */
    if (ne_addr[0] == 0 || password[0] == 0 || username[0] == 0)
    {
        usage(argv[0]);
        return -1;
    }

Reaching Out

We’ll explore simple_connect() in detail shortly, but for now suffice to say as the name implies, it makes a connection to the user-supplied IP address which we can assume is a network element running onePK. Important to note is that a successful call returns a session handle (aptly named onepk) and instantiates a network element variable, ne. Subsequent onePK methods will require one or both of these.

Next, the program will call the onep_element_get_property() method which, upon success, extracts so-called “static” properties of the element. These are unchanging attributes of the network element such as processor, product ID, serial number, and the one we’re interested in, system description. Note here that we use what ends up being a close equivalent of the TRY macro to check the return code and handle an error. This is because the TRY macro is meant to be called from within a function returning a pointer, not from main() which returns an integer. Were this a larger program, we would probably make the TRY macro more general and handle all cases.

The next onePK method extracts the system description and after checking for an error, prints it to the screen. Important to note about the onep_element_property_get* family of methods is that they allocate memory to hold the resultant strings. You need to free this to avoid memory leaks. Finally, we release the memory held by the element property variable.

    printf("Connecting to %s...\n", ne_addr);
    onepk = simple_connect(ne_addr, username, password, APPNAME, &ne, &n, errbuf);
    if (onepk == NULL)
    {
        fprintf(stderr, "Can't connect to network element: %s\n", errbuf);
        return -1;
    }

    printf("Connected! Hello router!\n");
    r = onep_element_get_property(ne, &property);
    if (r != ONEP_OK)
    {
        fprintf(stderr, "onep_element_get_property(): %s\n",  onep_strerror(r))
        goto done;
    }
    if (property)
    {
        r = onep_element_property_get_sys_descr(property, &sysdescr);
        if (r != ONEP_OK)
        {
            fprintf(stderr, "onep_element_property_get_sys_descr(): %s\n", onep_strerror(r))
            goto done;
        }
        printf("System Description: %s\n", sysdescr);
        if (sysdescr)
        {
            free(sysdescr);
        }
        onep_element_property_destroy(&property);
    }

done:
    printf("Disconnecting from %s...\n", ne_addr);
    simple_disconnect(&ne, &onepk);

    return 1;
}

What use are you?

No C program would be complete with a function to print a helpful usage blurb!

void
usage(char *name)
{
    printf("\nonePK Hello World (ophw)\n");
    printf("(c) Cisco Systems 2013, Inc\n");
    printf("Mike Schiffman <mschiffm@cisco.com>\n\n");
    printf("\nUsage: ophw [-aup]\n");
    printf(" -a network_element_IP -u username -p password\n");
}

Inside the Machine

Now we arrive at the connection function. This is where some serious onePK gears churn. The following things happen:

  1. onep_application_get_instance(): The first method called arranges some memory for a single onePK application instance.
  2. onep_application_set_name(): Next, the name of the application is set (originally sourced from the macro at the top).
  3. onep_application_get_network_element(): The next method instantiates a network element that will be addressed by the user-supplied IP address. Onepk expects the address in sockaddr structure, and we oblige. The new network element starts in the DISCONNECTED state, a situation we aim to change shortly.
  4. onep_element_connect(): This method reaches out and connects to the network element to be authenticated with the user-supplied username and password credentials. Successful connection and authentication to the network element returns a session handle which is used to refer to this particular onePK session. If something breaks the method will return NULL and the return code will contain an error specific code.

The username and password need to be pre-configured in the network element and be authenticated using the network element configured authentication mechanism. The specifics of which will depend on the site configuration policy and can be local router-based credentials or via RADIUS, or TACACS+.

While not used here, the method accepts an optional configuration datatype. This parameter allows the application to change defaults like the transport mode (socket or TLS), reconnect timer (used on the network element side to keep the session state information intact if the application becomes unreachable), and session rate limiting.

Upon success, the network element moves into the CONNECTED state and a valid onePK session handle is returned.

session_handle_t *
simple_connect(char *ne_addr, char *username, char *password, char *appname, 
network_element_t **ne, network_application_t **n, char *errbuf)
{
    onep_status_t r;
    session_handle_t *onepk;
    struct sockaddr_in addr_v4;

    /* get a singleton instance of network_application_t */
    TRY(r, onep_application_get_instance(n), errbuf, "app_get_instance()");
    /* set the application name */
    TRY(r, onep_application_set_name(*n, appname), errbuf, "set_app_name()");

    /* prepare a sockaddr_in struct using the supplied v4 IP address */
    memset(&addr_v4, 0, sizeof (struct sockaddr_in));
    addr_v4.sin_family = AF_INET;
    inet_pton(AF_INET, ne_addr, &(addr_v4.sin_addr));
    /*
     * Instantiate a network_element_t which will be addressed by the supplied element address represented in sockaddr format.
     */
    TRY(r, onep_application_get_network_element(*n, (struct sockaddr *)&addr_v4, ne), errbuf, "get_network_element()");
    /*
     * Connect to the network element, use supplied username and password to be authenticated. The network element 
     * should have been previously configured to support onePK and the username password should also have been summarily configured.
     */
    TRY(r, onep_element_connect(*ne, username, password, NULL, &onepk), errbuf, "connect_to_element()");

    return onepk;
}

Shut This Off

The following function shuts onePK down in an orderly fashion. The order of events is as follows:

  1. onep_element_disconnect(): This method simply disconnects the application from the network element. This will cause any session state on the network element side to be released.
  2. onep_element_destroy(): This guy releases the network element state held by the application.
  3. onep_session_handle_destroy(): This handsome devil does away with the session handle state quite nicely.
  4. onep_application_destroy(): This no nonsense chap shuts down the application and demolishes all associated state. Important to know that if this fella is called and the network element is still in the CONNECTED state, it will be first be disconnected and all network element resources will be freed.
void
simple_disconnect(network_element_t **ne, session_handle_t **onepk, network_application_t **n)
{
    onep_status_t r;

    if ((ne) && (*ne))
    {
        /* disconnect from the network element */
        r = onep_element_disconnect(*ne); 
        if (r != ONEP_OK)
        {
            fprintf(stderr, "onep_element_disconnect(): %s\n", onep_strerror(r));
        }
        /* Free the network element resource on presentation. */
        r = onep_element_destroy(ne);
        if (r != ONEP_OK)
        {
            fprintf(stderr, "onep_element_destroy(): %s\n", onep_strerror(r));
        }    
    }   
    /* free the session handle */
    if (onepk)
    { 
        r = onep_session_handle_destroy(onepk);
        if (r != ONEP_OK)
        {
            fprintf(stderr, "onep_session_handle_destroy(): %s\n", onep_strerror(r));
        }    
    }
    /* destroys the network_application_t and frees its memory resource. */
    r = onep_application_destroy(n);
    if (r != ONEP_OK)
    {
        fprintf(stderr, "onep_application_destroy(): %s\n", onep_strerror(r));
    }
}

Net/Net

While this might seem like a lot of code, it’s really only doing three simple things so let’s not lose focus of what we’re doing here:

The rest of the code is standard C scaffolding and good programming practice. While not covered in this blog series, onePK also boasts Java and Python APIs. Using a higher-level language like Python, because of its automatic garbage collection, high-level data types and lack of variable declarations, you can accomplish much of what the C code does in far fewer lines of code. A sample Python snippet might look like the following:

If Python is your Poison

from onep.element.NetworkElement import NetworkElement
ne = NetworkElement("10.10.10.130","ophw")
ne.connect("cisco", "cisco")
print ne.properties.sys_descr
ne.disconnect()

API Evolution

Things are moving fast in the world of onePK. In the last blog, 0.6.0 was the most recent version of the C API. For this blog article, version 0.7.0 was used. However, by the time onePK goes GA (version 1.0) some assembly could be required if the APIs change slightly. This could happen by late 2013 or early 2014.

In Closing

To recap, you learned how to use the onePK C API to write a standalone program that connects to a network element, prints its description, and then disconnects. This is important functionality that you’ll need for future onePK-enabled applications. In the next and final installment of this blog series, we’ll explore and break down a more useful onePK tool.

Finally, I’d also like to thank Shelly Cadora for all of her hard work on onePK, helpful reviews of my blogs, and writing the above Python script.