How to Programmatically Discover SNMP-Enabled Devices and Retrieve MAC Addresses Using Net-SNMP


2 views

When building network management tools, discovering SNMP-enabled devices is a fundamental requirement. The Simple Network Management Protocol (SNMP) provides a standardized way to query network devices, but the challenge lies in finding the right OIDs (Object Identifiers) that work across different vendors.

There are three main methods to discover SNMP devices:

  1. Broadcast ping using SNMP (not recommended for large networks)
  2. ARP table scanning combined with SNMP queries
  3. ICMP ping sweep followed by SNMP version detection

Here are the most important standard OIDs for basic device information:

sysDescr (1.3.6.1.2.1.1.1) - Device description
sysName (1.3.6.1.2.1.1.5) - System name
sysContact (1.3.6.1.2.1.1.4) - Contact information
sysLocation (1.3.6.1.2.1.1.6) - Physical location
ifPhysAddress (1.3.6.1.2.1.2.2.1.6) - MAC addresses

Here's a Python example using the PySNMP wrapper for Net-SNMP:


from pysnmp.hlapi import *

def get_snmp_info(target_ip, community='public'):
    errorIndication, errorStatus, errorIndex, varBinds = next(
        getCmd(SnmpEngine(),
               CommunityData(community),
               UdpTransportTarget((target_ip, 161)),
               ContextData(),
               ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0')),
               ObjectType(ObjectIdentity('1.3.6.1.2.1.1.5.0')),
               ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.6.2')))
    )

    if errorIndication:
        print(errorIndication)
    elif errorStatus:
        print('%s at %s' % (errorStatus.prettyPrint(),
                            errorIndex and varBinds[int(errorIndex)-1][0] or '?'))
    else:
        for varBind in varBinds:
            print(' = '.join([x.prettyPrint() for x in varBind]))

# Example usage
get_snmp_info('192.168.1.1')

The ifPhysAddress OID requires an interface index. The common practice is to first query ifDescr (1.3.6.1.2.1.2.2.1.2) to list all interfaces, then get MAC addresses for each interface.

For maximum compatibility, focus on these standard MIBs:

  • MIB-II (1.3.6.1.2.1)
  • IF-MIB (1.3.6.1.2.1.31) for interface information
  • ENTITY-MIB (1.3.6.1.2.1.47) for hardware details

For production environments, consider these optimizations:


# Parallel SNMP requests using threading
import concurrent.futures

def scan_network(subnet):
    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
        future_to_ip = {executor.submit(get_snmp_info, f"{subnet}.{i}"): f"{subnet}.{i}" for i in range(1, 255)}
        for future in concurrent.futures.as_completed(future_to_ip):
            ip = future_to_ip[future]
            try:
                future.result()
            except Exception as exc:
                print(f'{ip} generated an exception: {exc}')

Always implement proper error handling, especially for:

  1. SNMP version mismatches (v1 vs v2c vs v3)
  2. Community string failures
  3. Device timeouts
  4. OID not supported errors

When working with network management systems, discovering SNMP-enabled devices programmatically requires understanding several key components:


// Basic SNMP discovery approach
1. Network scanning to identify responsive SNMP devices
2. Querying standard MIBs for device information
3. Handling vendor-specific implementations
4. Processing the collected data

These standard OIDs will help identify devices regardless of vendor:


// System Information OIDs
SNMPv2-MIB::sysDescr.0      - Device description
SNMPv2-MIB::sysName.0       - Hostname
SNMPv2-MIB::sysContact.0    - Contact information
SNMPv2-MIB::sysLocation.0   - Physical location

The standard OID for MAC addresses is in the IF-MIB:


IF-MIB::ifPhysAddress       - Contains MAC addresses for interfaces

Example implementation using Net-SNMP:


#include 
#include 

void get_mac_address(const char *host) {
    netsnmp_session session, *ss;
    netsnmp_pdu *pdu, *response;
    oid anOID[MAX_OID_LEN];
    size_t anOID_len;
    
    init_snmp("get_mac");
    snmp_sess_init(&session);
    session.peername = strdup(host);
    session.version = SNMP_VERSION_2c;
    session.community = "public";
    session.community_len = strlen(session.community);
    
    ss = snmp_open(&session);
    if (!ss) {
        snmp_perror("snmp_open");
        return;
    }
    
    anOID_len = MAX_OID_LEN;
    if (!snmp_parse_oid(".1.3.6.1.2.1.2.2.1.6", anOID, &anOID_len)) {
        snmp_perror("snmp_parse_oid");
        return;
    }
    
    pdu = snmp_pdu_create(SNMP_MSG_GETBULK);
    pdu->non_repeaters = 0;
    pdu->max_repetitions = 100;
    snmp_add_null_var(pdu, anOID, anOID_len);
    
    int status = snmp_synch_response(ss, pdu, &response);
    if (status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR) {
        netsnmp_variable_list *vars;
        for(vars = response->variables; vars; vars = vars->next_variable) {
            print_variable(vars->name, vars->name_length, vars);
        }
    }
    
    snmp_close(ss);
}
  • Handle different SNMP versions (v1, v2c, v3)
  • Implement proper error handling and timeouts
  • Consider asynchronous operations for large networks
  • Cache results to avoid repeated queries

While standard OIDs work for most devices, some vendors implement proprietary MIBs. Here are common exceptions:


// Cisco devices
CISCO-VTP-MIB::vtpVlanState.1.100 = INTEGER: operational(1)

// HP printers
HOST-RESOURCES-MIB::hrPrinterStatus.1 = INTEGER: idle(3)

Here's a Python example using PySNMP for comprehensive discovery:


from pysnmp.hlapi import *

def snmp_discovery(network):
    for ip in network:
        errorIndication, errorStatus, errorIndex, varBinds = next(
            getCmd(SnmpEngine(),
                   CommunityData('public'),
                   UdpTransportTarget((ip, 161), timeout=2, retries=1),
                   ContextData(),
                   ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)),
                   ObjectType(ObjectIdentity('IF-MIB', 'ifPhysAddress')))
        )
        
        if errorIndication:
            print(f"{ip}: {errorIndication}")
        else:
            print(f"Device at {ip}:")
            for varBind in varBinds:
                print(f" = ".join([x.prettyPrint() for x in varBind]))
  • Use multithreading for parallel discovery
  • Implement result caching to avoid redundant queries
  • Adjust timeout values based on network conditions
  • Prioritize critical devices in discovery queue

When implementing SNMP discovery:

  • Use SNMPv3 for encrypted communication
  • Implement proper community string management
  • Restrict access to SNMP management interfaces
  • Monitor for unusual SNMP traffic patterns