* * * * *
A leaky domain specific language
Continuing on about that article [1], there's yet another reason to be wary
of DSL (Domain Specific Language)s: The Law of Leaky Abstractions [2] (which
itself is an extension of the wrong mental model, to a degree).
Going back to my hypothetical SNMP (Simple Network Management Protocol)
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 [3] 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 (Management Information Base) 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 [4].
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.
[1]
gopher://gopher.conman.org/0Phlog:2007/01/23.1
[2]
http://www.joelonsoftware.com/articles/LeakyAbstractions.html
[3]
http://www.net-snmp.org/
[4]
http://en.wikipedia.org/wiki/Big_ball_of_mud
Email author at
[email protected]