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] /boston/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

Gemini Mention this post

Contact the author