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 expose 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.
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.
Before you can reach out to a network element, you'll need three pieces of information:
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_TTechnical Support: http://www.cisco.com/techsupportCopyright (c) 1986-2013 by Cisco Systems, Inc.Compiled Sun 02-Jun-13 23:27 by nmcgillDisconnecting 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...
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.
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 theTRYmacro. 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 <[email protected]> * * 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_SIZE16
voidusage(char*);session_handle_t *simple_connect(char*,char*,char*,char*, network_element_t **, network_application_t **,char*);voidsimple_disconnect(network_element_t **, session_handle_t **, network_application_t **);
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:
intmain(intargc, char* argv[]){ onep_status_t r;charopt, *sysdescr; network_element_t *ne; onep_username username; onep_password password; session_handle_t *onepk; element_property_t *property; network_application_t *n;charne_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; }
We'll exploresimple_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 namedonepk) and instantiates a network element variable,ne. Subsequent onePK methods will require one or both of these.
Next, the program will call theonep_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 theTRYmacro to check the return code and handle an error. This is because theTRYmacro is meant to be called from within a function returning a pointer, not frommain()which returns an integer. Were this a larger program, we would probably make theTRYmacro 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 theonep_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))gotodone; }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))gotodone; } 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;}
No C program would be complete with a function to print a helpful usage blurb!
voidusage(char*name){ printf("\nonePK Hello World (ophw)\n"); printf("(c) Cisco Systems 2013, Inc\n"); printf("Mike Schiffman <[email protected]>\n\n"); printf("\nUsage: ophw [-aup]\n"); printf(" -a network_element_IP -u username -p password\n");}
Now we arrive at the connection function. This is where some serious onePK gears churn. The following things happen:
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;structsockaddr_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 (structsockaddr_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, (structsockaddr *)&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()"); returnonepk;}
The following function shuts onePK down in an orderly fashion. The order of events is as follows:
voidsimple_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)); }}
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:
fromonep.element.NetworkElementimportNetworkElementne = NetworkElement("10.10.10.130","ophw")ne.connect("cisco", "cisco")printne.properties.sys_descrne.disconnect()
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.
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.