/* * ifstat - InterFace STATistics * Copyright (c) 2001, Gaël Roualland * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: snmp.c,v 1.26 2003/05/06 23:30:02 gael Exp $ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "ifstat.h" #ifdef USE_SNMP #include #include #include #include #include #ifdef HAVE_NET_SNMP #include #include #else #include #include #endif #include "snmp.h" /* define this to use ifcount as a hint to discover interfaces does not work well with routers that do not number interfaces sequentially, but is faster that the ifIndex walk used otherwise */ #undef USE_SNMP_IFCOUNT static char *snmp_sess_errstring(struct snmp_session *ss) { char *res; snmp_error(ss, NULL, NULL, &res); return res; } #ifdef USE_SNMP_IFCOUNT /* report the value interfaces.ifNumber.0, actually the number of interfaces */ static int snmp_get_ifcount(struct snmp_session *ss) { int nifaces = -1; oid ifcount[] = { 1, 3, 6, 1, 2, 1, 2, 1, 0 }; struct snmp_pdu *pdu; struct snmp_pdu *response = NULL; int status; if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) == NULL) { ifstat_error("snmp_pdu_create: %s", snmp_api_errstring(snmp_errno)); return -1; } snmp_add_null_var(pdu, ifcount, sizeof(ifcount) / sizeof(oid)); if ((status = snmp_synch_response(ss, pdu, &response)) != STAT_SUCCESS || response->errstat != SNMP_ERR_NOERROR || response->variables == NULL || response->variables->type != ASN_INTEGER) { if (status == STAT_SUCCESS) ifstat_error("snmp: Error: %s", snmp_errstring(response->errstat)); else ifstat_error("snmpget(interfaces.ifNumber.0): %s", snmp_sess_errstring(ss)); if (response) snmp_free_pdu(response); return -1; } nifaces = *(response->variables->val.integer); snmp_free_pdu(response); if (nifaces < 0) return -1; return nifaces; } #endif static int snmp_get_nextif(struct snmp_session *ss, int index) { oid ifindex[] = { 1, 3, 6, 1, 2, 1, 2, 2, 1, 1, 0 }; int len = sizeof(ifindex) / sizeof(oid); struct snmp_pdu *pdu; struct snmp_pdu *response = NULL; struct variable_list *vars; int status; if (index >= 0) ifindex[len - 1] = index; if ((pdu = snmp_pdu_create(SNMP_MSG_GETNEXT)) == NULL) { ifstat_error("snmp_pdu_create: %s", snmp_api_errstring(snmp_errno)); return -1; } snmp_add_null_var(pdu, ifindex, (index < 0) ? len - 1 : len); if ((status = snmp_synch_response(ss, pdu, &response)) != STAT_SUCCESS || response->errstat != SNMP_ERR_NOERROR || response->variables == NULL) { if (status == STAT_SUCCESS) ifstat_error("snmp: Error: %s", snmp_errstring(response->errstat)); else ifstat_error("snmpgetnext(interfaces.ifTable.ifEntry.ifIndex...): %s", snmp_sess_errstring(ss)); if (response != NULL) snmp_free_pdu(response); return -1; } for(vars = response->variables; vars; vars = vars->next_variable) { /* check that the variable is under the base oid */ if (vars->name_length != len) continue; if (memcmp(ifindex, vars->name, sizeof(ifindex) - sizeof(oid)) != 0) continue; index = vars->name[vars->name_length - 1]; snmp_free_pdu(response); return index; } snmp_free_pdu(response); return -1; } #define S_IFNAMEMAX 64 struct ifsnmp { char name[S_IFNAMEMAX]; unsigned long bout, bin; int flags, index; }; #define S_UP 1 #define S_BIN 2 #define S_BOUT 4 #define S_LOOP 8 #define S_INVALID 16 #define S_NUMNAME 32 #define S_IFNAME 64 /* fill a struct ifsnmp buffer of selected information (flags) for interface index to (index + nifaces - 1). ifsnmp must be large enough, and nifaces shouldb'nt too large since some devices have limited capability for large responses... In case we get a unknown name answer and we're polling several interfaces at once, interfaces will be polled again individually to try to solve the problem. */ static int snmp_get_ifinfos(struct snmp_session *ss, int nifaces, int flags, struct ifsnmp * ifsnmp, int *toobig) { struct snmp_pdu *pdu, *response = NULL; oid ifinfo[] = { 1, 3, 6, 1, 2, 1, 2, 2, 1, 0, 0 }; /* interfaces.ifTable.ifEntry.x.n */ #define ifDescr 2 #define ifType 3 #define ifOperStatus 8 #define ifInOctets 10 #define ifOutOctets 16 struct variable_list *vars; int i, status; if (nifaces <= 0) return 0; if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) == NULL) { ifstat_error("snmp_pdu_create: %s", snmp_api_errstring(snmp_errno)); return 0; } for (i = 0; i < nifaces; i++) { ifsnmp[i].flags = 0; ifsnmp[i].name[0] = 0; /* set interface index */ ifinfo[10] = ifsnmp[i].index; if (flags & S_NUMNAME) { sprintf(ifsnmp[i].name, "if%d", ifsnmp[i].index); } else if (flags & S_IFNAME) { /* require descr */ ifinfo[9] = ifDescr; snmp_add_null_var(pdu, ifinfo, sizeof(ifinfo) / sizeof(oid)); } /* then optional data */ if (flags & S_UP) { ifinfo[9] = ifOperStatus; snmp_add_null_var(pdu, ifinfo, sizeof(ifinfo) / sizeof(oid)); } if (flags & S_BOUT) { ifinfo[9] = ifOutOctets; snmp_add_null_var(pdu, ifinfo, sizeof(ifinfo) / sizeof(oid)); } if (flags & S_BIN) { ifinfo[9] = ifInOctets; snmp_add_null_var(pdu, ifinfo, sizeof(ifinfo) / sizeof(oid)); } if (flags & S_LOOP) { ifinfo[9] = ifType; snmp_add_null_var(pdu, ifinfo, sizeof(ifinfo) / sizeof(oid)); } } if ((status = snmp_synch_response(ss, pdu, &response)) != STAT_SUCCESS || response->errstat != SNMP_ERR_NOERROR || response->variables == NULL) { if (status == STAT_SUCCESS) { if (response->errstat != SNMP_ERR_NOSUCHNAME && response->errstat != SNMP_ERR_TOOBIG) ifstat_error("snmp: Error: %s", snmp_errstring(response->errstat)); else if (nifaces > 1) { /* maybe only one of the interface is broken or too many interfaces polled at once -- repoll inetrface per interface */ if (response->errstat == SNMP_ERR_TOOBIG && toobig != NULL) (*toobig)++; if (response != NULL) snmp_free_pdu(response); status = 0; for (i = 0; i < nifaces; i++) { if (!snmp_get_ifinfos(ss, 1, flags, ifsnmp + i, NULL)) ifsnmp[i].flags |= S_INVALID; else status = 1; } return status; } } else ifstat_error("snmpget(interfaces.ifTable.ifEntry...): %s", snmp_sess_errstring(ss)); if (response != NULL) snmp_free_pdu(response); return 0; } for(vars = response->variables; vars; vars = vars->next_variable) { /* check that the variable is under the base oid */ if (memcmp(ifinfo, vars->name, sizeof(ifinfo) - 2 * sizeof(oid)) != 0) continue; for(i = 0; i < nifaces; i++) { if (ifsnmp[i].index == vars->name[10]) break; } if (i == nifaces) /* not found */ continue; switch (vars->name[9]) { case ifDescr: if (vars->type == ASN_OCTET_STR) { int count = vars->val_len; if (count >= sizeof(ifsnmp[i].name)) count = sizeof(ifsnmp[i].name) - 1; strncpy(ifsnmp[i].name, vars->val.string, count); ifsnmp[i].name[count] = '\0'; } break; case ifOperStatus: if (vars->type == ASN_INTEGER) { if (*(vars->val.integer) == 1) /* up */ ifsnmp[i].flags |= S_UP; } break; case ifType: if (vars->type == ASN_INTEGER) { if (*(vars->val.integer) == 24) /* softwareLoopback */ ifsnmp[i].flags |= S_LOOP; } break; case ifInOctets: if (vars->type == ASN_INTEGER || vars->type == ASN_COUNTER) { ifsnmp[i].flags |= S_BIN; ifsnmp[i].bin = *(vars->val.integer); } break; case ifOutOctets: if (vars->type == ASN_INTEGER || vars->type == ASN_COUNTER) { ifsnmp[i].flags |= S_BOUT; ifsnmp[i].bout = *(vars->val.integer); } break; } } snmp_free_pdu(response); return 1; } /* driver API */ struct snmp_driver_data { struct snmp_session *session; int num_ifnames, num_ifsreqs; struct ifsnmp *ifsnmp; }; /* maximum number of interfaces to poll at once, min 1 */ #define MAXIFSREQS 64 #define DEFIFSREQS 8 /* initiailise the snmp driver, strings syntax is [comm@][#]host*/ int snmp_open_driver(struct ifstat_driver *driver, char *options) { char *host; char *community; struct snmp_session session; struct snmp_driver_data *data; if ((data = malloc(sizeof(struct snmp_driver_data))) == NULL) { ifstat_perror("malloc"); return 0; } if (options == NULL) options = "localhost"; if ((host = strchr(options, '@')) != NULL) { *host++ = '\0'; community = options; } else { host = options; community = "public"; } if (*host == '#') { host++; data->num_ifnames = 1; /* numeric interface names */ } else data->num_ifnames = 0; if ((options = strchr(host, '/')) != NULL) { *options++ = '\0'; data->num_ifsreqs = atoi(options); if (data->num_ifsreqs < 1) { ifstat_error("snmp: bad number of interface requests: %s; ignored.", options); data->num_ifsreqs = DEFIFSREQS; } else if (data->num_ifsreqs > MAXIFSREQS) { ifstat_error("snmp: number of interface requests too large: %d; using %d instead.", data->num_ifsreqs, MAXIFSREQS); data->num_ifsreqs = MAXIFSREQS; } } else data->num_ifsreqs = DEFIFSREQS; if ((data->ifsnmp = calloc(sizeof(struct ifsnmp), data->num_ifsreqs)) == NULL) { ifstat_perror("malloc"); free(data); return 0; } init_snmp(ifstat_progname); snmp_sess_init(&session); session.peername = host; session.version = SNMP_VERSION_1; session.community = community; session.community_len = strlen(community); if ((data->session = snmp_open(&session)) == NULL) { ifstat_error("snmp_open: %s", snmp_api_errstring(snmp_errno)); free(data->ifsnmp); free(data); return 0; } driver->data = (void *) data; return 1; } /* cleanups session */ void snmp_close_driver(struct ifstat_driver *driver) { struct snmp_driver_data *data = driver->data; snmp_close(data->session); free(data->ifsnmp); free(data); } #ifdef USE_SNMP_IFCOUNT static int snmp_map_ifs(struct ifstat_driver *driver, int (*mapf)(struct ifstat_driver *driver, int nifaces, void *pdata), void *pdata) { struct snmp_driver_data *data = driver->data; struct ifsnmp *ifsnmp = data->ifsnmp; int ifaces, nifaces, index, i; if ((ifaces = snmp_get_ifcount(data->session)) <= 0) { ifstat_error("snmp: no interfaces returned."); return 0; } for(index = 0; index <= (ifaces /data->num_ifsreqs); index++) { nifaces = ifaces - index * data->num_ifsreqs; if (nifaces > data->num_ifsreqs) nifaces = data->num_ifsreqs; for (i = 0; i < nifaces; i++) ifsnmp[i].index = index * data->num_ifsreqs + i + 1; if(!mapf(driver, nifaces, pdata)) return 0; } return 1; } #else static int snmp_map_ifs(struct ifstat_driver *driver, int (*mapf)(struct ifstat_driver *driver, int nifaces, void *pdata), void *pdata) { struct snmp_driver_data *data = driver->data; struct ifsnmp *ifsnmp = data->ifsnmp; int ifaces, nifaces, index; index = -1; nifaces = ifaces = 0; while((index = snmp_get_nextif(data->session, index)) >= 0) { ifaces++; ifsnmp[nifaces++].index = index; if (nifaces >= data->num_ifsreqs) { if(!mapf(driver, nifaces, pdata)) return 0; nifaces = 0; } } if (nifaces > 0) return mapf(driver, nifaces, pdata); return (ifaces != 0); } #endif static int snmp_map_scan(struct ifstat_driver *driver, int nifaces, void *pdata) { struct snmp_driver_data *data = driver->data; struct ifsnmp *ifsnmp = data->ifsnmp; struct ifstat_list *ifs = pdata; struct ifstat_data *iface; int i; if (!snmp_get_ifinfos(data->session, nifaces, S_UP | S_LOOP | (data->num_ifnames ? S_NUMNAME : S_IFNAME), ifsnmp, NULL)) return 1; for (i=0; i < nifaces; i++) { if (ifsnmp[i].flags & S_INVALID) continue; if ((ifsnmp[i].flags & S_LOOP) && !(ifs->flags & IFSTAT_LOOPBACK)) continue; if ((ifsnmp[i].flags & S_UP) || (ifs->flags & IFSTAT_DOWN)) { if ((iface = ifstat_get_interface(ifs, ifsnmp[i].name)) != NULL) { if (!ifstat_quiet && !(iface->flags & IFSTAT_HASSTATS)) { ifstat_error("warning: multiple interfaces detected with same name (%s); " "you should enable numeric mode by prepending # to the hostname.", ifsnmp[i].name); iface->flags |= IFSTAT_HASSTATS; } } else ifstat_add_interface(ifs, ifsnmp[i].name, 0); } } return 1; } int snmp_scan_interfaces(struct ifstat_driver *driver, struct ifstat_list *ifs) { return snmp_map_ifs(driver, &snmp_map_scan, (void *) ifs); } static void snmp_toobig(struct ifstat_driver *driver) { struct snmp_driver_data *data = driver->data; if (data->num_ifsreqs > 1) { data->num_ifsreqs >>= 1; if (!ifstat_quiet) ifstat_error("warning: changing poll grouping to %d to avoid \"too big\" errors", data->num_ifsreqs); } } static int snmp_map_stats(struct ifstat_driver *driver, int nifaces, void *pdata) { struct snmp_driver_data *data = driver->data; struct ifsnmp *ifsnmp = data->ifsnmp; struct ifstat_list *ifs = pdata; struct ifstat_data *cur; int i, toobig = 0; if (!snmp_get_ifinfos(data->session, nifaces, S_BIN | S_BOUT | (data->num_ifnames ? S_NUMNAME : S_IFNAME), ifsnmp, &toobig)) return 1; if (toobig) snmp_toobig(driver); for (i=0; i < nifaces; i++) { if (ifsnmp[i].flags & S_INVALID) continue; if (!(ifsnmp[i].flags & S_BIN && ifsnmp[i].flags & S_BOUT)) continue; /* overwrite if name if needed */ if ((cur = ifstat_get_interface(ifs, ifsnmp[i].name)) != NULL) { ifstat_set_interface_stats(cur, ifsnmp[i].bin, ifsnmp[i].bout); ifstat_set_interface_index(cur, ifsnmp[i].index); } } return 1; } int snmp_get_stats(struct ifstat_driver *driver, struct ifstat_list *ifs) { struct snmp_driver_data *data = driver->data; int ifaces, i, toobig = 0; struct ifstat_data *cur, *block; struct ifsnmp *ifsnmp = data->ifsnmp; if (ifs->flags & IFSTAT_HASINDEX) { /* poll by index */ cur = ifs->first; while (cur != NULL) { /* init as many interface as possible */ block = cur; ifaces = 0; while (ifaces < data->num_ifsreqs && cur != NULL) { ifsnmp[ifaces++].index = cur->index; cur = cur->next; } /* poll them */ if (!snmp_get_ifinfos(data->session, ifaces, S_BIN | S_BOUT | (data->num_ifnames ? 0 : S_IFNAME), ifsnmp, &toobig)) continue; if (toobig) snmp_toobig(driver); for (i = 0; i < ifaces; i++) { if (ifsnmp[i].flags & S_INVALID) continue; if (!(ifsnmp[i].flags & S_BIN && ifsnmp[i].flags & S_BOUT)) continue; if (!data->num_ifnames && strcmp(ifsnmp[i].name, block->name)) continue; /* interface changed of index... */ ifstat_set_interface_stats(block, ifsnmp[i].bin, ifsnmp[i].bout); ifstat_set_interface_index(block, ifsnmp[i].index); block = block->next; } } return 1; } return snmp_map_ifs(driver, &snmp_map_stats, (void *) ifs); } #endif