Friday, January 26, 2007
No, I haven't forgotten about it
But I have been neglecting it for the past few months. Partly due to the Holiday Season, and partly because I was (or did) run out of ideas to write about at the Saltmine Chronicles.
And all this is just to inform those that are interested that I just posted—an entry about good and bad web traffic.
A leaky domain specific language
Continuing on about that article, there's yet another reason to be wary of DSLs: The Law of Leaky Abstractions (which itself is an extension of the wrong mental model, to a degree).
Going back to my hypothetical SNMP extensions:
IPAddress destination[]; IPAddress mask[]; IPAddress nexthop[]; int protocol[]; int age[]; int metric[]; int type[]; OID sys = SNMPv2-MIB::sysObjectID.0; if (sys == SNMPv2-SMI::enterprises.5567.1.1) /* riverstone */ { destination = IP-MIB::ip.24.4.1.1; mask = IP-MIB::ip.24.4.1.2; nexthop = IP-MIB::ip.24.4.1.4; protocol = IP-MIB::ip.24.4.1.7; age = IP-MIB::ip.24.4.1.8; metric = IP-MIB::ip.24.4.1.11; type = IP-MIB::ip.24.4.1.6; } else if (sys == SNMPv2-SMI::enterprises.9.1) /* cisco */ { destination = RFC1213-MIB::ipRouteDest; mask = RFC1213-MIB::ipRouteMask; nexthop = RFC1213-MIB::ipRouteNextHop; protocol = RFC1213-MIB::ipRouteProto; age = RFC1213-MIB::ipRouteAge; metric = RFC1213-MIB::ipRouteMetric1; type = RFC1213-MIB::ipRouteType; } /* skip the printing part */
This time, I'll be concentrating on just querying one SNMP variable:
OID sys = SNMPv2-MIB::sysObjectID.0; if (sys == SNMPv2-SMI::enterprises.5567.1.1) printf("Riverstone\n"); else if (sys == SNMPv2-SMI::enterprises.9.1) printf("Cisco\n"); else printf("Unknown\n");
I thought it would be instructive to write the minimum amount of code that actually does this, using the net-snmp library. And yes, it was instructive.
/******************************************** * to compile, install net-snmp, then * * gcc -o sysid sysid.c -lsnmp -lcrypto * *********************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <net-snmp/net-snmp-config.h> #include <net-snmp/utilities.h> #include <net-snmp/net-snmp-includes.h> /*************************************************************************/ int main(int argc,char *argv[]) { static oid sysObjectId[] = { 1,3,6,1,2,1,1,2,0}; static oid rs[] = { 1,3,6,1,4,1,5567 }; static oid cisco[] = { 1,3,6,1,4,1,9 }; netsnmp_session session; netsnmp_session *ss; netsnmp_pdu *pdu; netsnmp_pdu *response; int status; snmp_parse_args(argc,argv,&session,"",NULL); ss = snmp_open(&session); if (ss == NULL) exit(1); pdu = snmp_pdu_create(SNMP_MSG_GET); snmp_add_null_var(pdu,sysObjectId,9); status = snmp_synch_response(ss,pdu,&response); if ((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR)) { if (memcmp(response->variables->val.objid,rs,sizeof(rs)) == 0) printf("Riverstone\n"); else if (memcmp(response->variables->val.objid,cisco,sizeof(cisco)) == 0) printf("Cisco\n"); else printf("Unknown\n"); } snmp_free_pdu(response); snmp_close(ss); return(EXIT_SUCCESS); }
So, where are the leaks?
Well, there's creating the actual session, or some other way of
specifying which router we're attempting to query and the credientials we
need to actually obtain the information (and there are three versions of the
SNMP protocol,
so we need to specify that as well somewhere). In my code, that's
hidden behind the call to snmp_parse_args()
, which expects to
parse the command line and creates a template “session” for us (and
believe me, I tried to see if there was any other way to construct this
template “session” and I couldn't find it in the hour or so I looked).
This is leak number one.
Then there's the actual call itself. You create the message, fill it with the variable(s) you want, then send the query and get a response back. But, there are several things that can go wrong, which is leak number two.
The first thing is that we never get a response back—the SNMP library simply
times out (is the router down? A bad network cable? Who knows?). That's
reflected in the return code from snmp_synch_response()
. But
even if we get a response back, the far side, the device we're
querying via SNMP, can return an error—perhaps it doesn't support
the MIB we
requesting. Or the response itself got corrupted on the way back. And this
is still part of leak number two.
The primary line I'm trying to implement:
OID sys = SNMPv2-MIB::sysObjectID.0;
So far, there are three things (at least, possibly more) that could go wrong: I never get a reponse back, the response I get back is an error, or I get the actual data I requested. What now?
Well, my hypothetical langauge extension could just set sys to
NULL
, indicating an error, but what error? Well, I could just
stuff something into errno
(if I'm extending C, for
example):
string contact = SNMPv2-MIB::sysContact.0; string name = SNMPv2-MIB::sysName.0; string location = SNMPv2-MIB::sysLocation.0; if ((contact == NULL) || (name == NULL) || (location == NULL)) { if (errno == ETIMEOUT) /* timeout */ ... else if (errno == EMIBEXIST) /* MIB not supported */ ... else ... }
But that's error detection, and that's a whole other ball of mud.
And setting aside I still haven't specified which device I'm querying, this has another leak—I should bundle these three queries into a single SNMP query for better (faster, less bandwidth intensive) performance. I suppose the “compiler” could recognize this and do the bundling for us, but that's some sophisticated programming for just a seemingly simple DSL.