#include "snmp-agent.hh" #include "misc.hh" #include "threadname.hh" #ifdef RECURSOR #include "logger.hh" #else #include "dolog.hh" #endif #ifdef HAVE_NET_SNMP #ifndef HAVE_SNMP_SELECT_INFO2 /* that's terrible, because it means we are going to have trouble with large FD numbers at some point.. */ # define netsnmp_large_fd_set fd_set # define snmp_read2 snmp_read # define snmp_select_info2 snmp_select_info # define netsnmp_large_fd_set_init(...) # define netsnmp_large_fd_set_cleanup(...) # define NETSNMP_LARGE_FD_SET FD_SET # define NETSNMP_LARGE_FD_CLR FD_CLR # define NETSNMP_LARGE_FD_ZERO FD_ZERO # define NETSNMP_LARGE_FD_ISSET FD_ISSET #else # include #endif const oid SNMPAgent::snmpTrapOID[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 }; const size_t SNMPAgent::snmpTrapOIDLen = OID_LENGTH(SNMPAgent::snmpTrapOID); int SNMPAgent::setCounter64Value(netsnmp_request_info* request, uint64_t value) { struct counter64 val64; val64.high = value >> 32; val64.low = value & 0xffffffff; snmp_set_var_typed_value(request->requestvb, ASN_COUNTER64, &val64, sizeof(val64)); return SNMP_ERR_NOERROR; } bool SNMPAgent::sendTrap(int fd, netsnmp_variable_list* varList) { ssize_t written = write(fd, &varList, sizeof(varList)); if (written != sizeof(varList)) { snmp_free_varbind(varList); return false; } return true; } void SNMPAgent::handleTrapsEvent() { netsnmp_variable_list* varList = nullptr; ssize_t got = 0; do { got = read(d_trapPipe[0], &varList, sizeof(varList)); if (got == sizeof(varList)) { send_v2trap(varList); snmp_free_varbind(varList); } } while (got > 0); } void SNMPAgent::handleSNMPQueryEvent(int fd) { netsnmp_large_fd_set fdset; netsnmp_large_fd_set_init(&fdset, FD_SETSIZE); NETSNMP_LARGE_FD_ZERO(&fdset); NETSNMP_LARGE_FD_SET(fd, &fdset); snmp_read2(&fdset); } void SNMPAgent::handleTrapsCB(int fd, FDMultiplexer::funcparam_t& var) { SNMPAgent** agent = boost::any_cast(&var); if (!agent || !*agent) throw std::runtime_error("Invalid value received in SNMP trap callback"); (*agent)->handleTrapsEvent(); } void SNMPAgent::handleSNMPQueryCB(int fd, FDMultiplexer::funcparam_t& var) { SNMPAgent** agent = boost::any_cast(&var); if (!agent || !*agent) throw std::runtime_error("Invalid value received in SNMP trap callback"); (*agent)->handleSNMPQueryEvent(fd); } #endif /* HAVE_NET_SNMP */ void SNMPAgent::worker() { #ifdef HAVE_NET_SNMP FDMultiplexer* mplexer = FDMultiplexer::getMultiplexerSilent(); if (mplexer == nullptr) { throw std::runtime_error("No FD multiplexer found for the SNMP agent!"); } #ifdef RECURSOR string threadName = "pdns-r/SNMP"; #else string threadName = "dnsdist/SNMP"; #endif setThreadName(threadName); int maxfd = 0; int block = 1; netsnmp_large_fd_set fdset; struct timeval timeout = { 0, 0 }; struct timeval now; /* we want to be notified if a trap is waiting to be sent */ mplexer->addReadFD(d_trapPipe[0], &handleTrapsCB, this); while(true) { netsnmp_large_fd_set_init(&fdset, FD_SETSIZE); NETSNMP_LARGE_FD_ZERO(&fdset); block = 1; timeout = { 0, 0 }; snmp_select_info2(&maxfd, &fdset, &timeout, &block); for (int fd = 0; fd < maxfd; fd++) { if (NETSNMP_LARGE_FD_ISSET(fd, &fdset)) { mplexer->addReadFD(fd, &handleSNMPQueryCB, this); } } /* run updates now */ int res = mplexer->run(&now, (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000)); /* we handle timeouts here, the rest has already been handled by callbacks */ if (res == 0) { snmp_timeout(); run_alarms(); } for (int fd = 0; fd < maxfd; fd++) { if (NETSNMP_LARGE_FD_ISSET(fd, &fdset)) { try { mplexer->removeReadFD(fd); } catch(const FDMultiplexerException& e) { /* we might get an exception when removing a closed file descriptor, just ignore it */ } } } } #endif /* HAVE_NET_SNMP */ } SNMPAgent::SNMPAgent(const std::string& name, const std::string& daemonSocket) { #ifdef HAVE_NET_SNMP netsnmp_enable_subagent(); snmp_disable_log(); if (!daemonSocket.empty()) { netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCKET, daemonSocket.c_str()); } /* no need to load any MIBS, and it causes import errors if some modules are not present */ setenv("MIBS", "", 1); init_agent(name.c_str()); /* we use select() so don't use SIGALARM to handle alarms. Note that we need to handle alarms for automatic reconnection to the daemon to work. */ netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_ALARM_DONT_USE_SIG, 1); init_snmp(name.c_str()); if (pipe(d_trapPipe) < 0) unixDie("Creating pipe"); if (!setNonBlocking(d_trapPipe[0])) { close(d_trapPipe[0]); close(d_trapPipe[1]); unixDie("Setting pipe non-blocking"); } if (!setNonBlocking(d_trapPipe[1])) { close(d_trapPipe[0]); close(d_trapPipe[1]); unixDie("Setting pipe non-blocking"); } #endif /* HAVE_NET_SNMP */ }