}
install_dnsdist() {
- printf ""
+ # recursor test requirements / setup
+ run "sudo apt-get -qq --no-install-recommends install \
+ snmpd \
+ libsnmp-dev"
+ run "sudo sed -i \"s/agentxperms 0700 0755 dnsdist/agentxperms 0700 0755 ${USER}/g\" regression-tests.dnsdist/snmpd.conf"
+ run "sudo cp -f regression-tests.dnsdist/snmpd.conf /etc/snmp/snmpd.conf"
+ run "sudo service snmpd restart"
+ # fun story, the directory perms are only applied if it doesn't exist yet, and it is created by the init script, so..
+ run "sudo chmod 0755 /var/agentx"
}
build_auth() {
--- /dev/null
+-- -*- snmpv2 -*-
+-- ----------------------------------------------------------------------
+-- MIB file for dnsdist
+-- ----------------------------------------------------------------------
+
+DNSDIST-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ OBJECT-TYPE, MODULE-IDENTITY, enterprises,
+ Counter64, Unsigned32, NOTIFICATION-TYPE
+ FROM SNMPv2-SMI
+ CounterBasedGauge64
+ FROM HCNUM-TC
+ Float64TC
+ FROM FLOAT-TC-MIB
+ OBJECT-GROUP, MODULE-COMPLIANCE, NOTIFICATION-GROUP
+ FROM SNMPv2-CONF
+ InetAddressType
+ FROM INET-ADDRESS-MIB
+ TEXTUAL-CONVENTION, DisplayString
+ FROM SNMPv2-TC;
+
+dnsdist MODULE-IDENTITY
+ LAST-UPDATED "201611080000Z"
+ ORGANIZATION "PowerDNS BV"
+ CONTACT-INFO "support@powerdns.com"
+ DESCRIPTION
+ "This MIB module describes information gathered through dnsdist."
+
+ REVISION "201611080000Z"
+ DESCRIPTION "Initial revision."
+
+ ::= { powerdns 3 }
+
+powerdns OBJECT IDENTIFIER ::= { enterprises 43315 }
+
+stats OBJECT IDENTIFIER ::= { dnsdist 1 }
+
+queries OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries received"
+ ::= { stats 1 }
+
+responses OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of responses received"
+ ::= { stats 2 }
+
+servfailResponses OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of servfail responses received"
+ ::= { stats 3 }
+
+aclDrops OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries dropped because of the ACL"
+ ::= { stats 4 }
+
+blockFilters OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries dropped because of the block filters"
+ ::= { stats 5 }
+
+ruleDrop OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries dropped because of a rule"
+ ::= { stats 6 }
+
+ruleNXDomain OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of NXDomain responses returned because of a rule"
+ ::= { stats 7 }
+
+ruleRefused OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of Refused responses returned because of a rule"
+ ::= { stats 8 }
+
+selfAnswered OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of self-answered responses"
+ ::= { stats 9 }
+
+downstreamTimeouts OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of downstream timeouts"
+ ::= { stats 10 }
+
+downstreamSendErrors OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of downstream send errors"
+ ::= { stats 11 }
+
+truncFailures OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of errors while truncating a response"
+ ::= { stats 12 }
+
+noPolicy OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries dropped because no server was available"
+ ::= { stats 13 }
+
+latency01 OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries answered in less than 1 ms"
+ ::= { stats 14 }
+
+latency110 OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries answered in 1-10 ms"
+ ::= { stats 15 }
+
+latency1050 OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries answered in 10-50 ms"
+ ::= { stats 16 }
+
+latency50100 OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries answered in 50-100 ms"
+ ::= { stats 17 }
+
+latency1001000 OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries answered in 100-1000 ms"
+ ::= { stats 18 }
+
+latencySlow OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries answered in more than 1s"
+ ::= { stats 19 }
+
+latencyAVG100 OBJECT-TYPE
+ SYNTAX Float64TC
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Average latency over the last 100 queries"
+ ::= { stats 20 }
+
+latencyAVG1000 OBJECT-TYPE
+ SYNTAX Float64TC
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Average latency over the last 1000 queries"
+ ::= { stats 21 }
+
+latencyAVG10000 OBJECT-TYPE
+ SYNTAX Float64TC
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Average latency over the last 10000 queries"
+ ::= { stats 22 }
+
+latencyAVG1000000 OBJECT-TYPE
+ SYNTAX Float64TC
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Average latency over the last 1000000 queries"
+ ::= { stats 23 }
+
+uptime OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Uptime of the dnsdist process, in seconds"
+ ::= { stats 24 }
+
+realMemoryUsage OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Memory usage"
+ ::= { stats 25 }
+
+nonCompliantQueries OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries dropped as non-compliant"
+ ::= { stats 26 }
+
+nonCompliantResponses OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of responses dropped as non-compliant"
+ ::= { stats 27 }
+
+rdQueries OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries with the RD flag set"
+ ::= { stats 28 }
+
+emptyQueries OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of empty queries received"
+ ::= { stats 29 }
+
+cacheHits OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of cache hits"
+ ::= { stats 30 }
+
+cacheMisses OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of cache misses"
+ ::= { stats 31 }
+
+cpuUserMSec OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "CPU Usage (user)"
+ ::= { stats 32 }
+
+cpuSysMSec OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "CPU Usage (sys)"
+ ::= { stats 33 }
+
+fdUsage OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of file descriptors"
+ ::= { stats 34 }
+
+dynBlocked OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries dropped because of a dynamic block"
+ ::= { stats 35 }
+
+dynBlockNMGSize OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Dynamic blocks (NMG) size"
+ ::= { stats 36 }
+
+backendStatTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BackendStatEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION "Statistics for backends"
+ ::= { dnsdist 2 }
+
+backendStatEntry OBJECT-TYPE
+ SYNTAX BackendStatEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION "Statistics for one backend"
+ INDEX { backendId }
+ ::= { backendStatTable 1 }
+
+BackendStatEntry ::= SEQUENCE {
+ backendId Unsigned32,
+ backendName DisplayString,
+ backendLatency CounterBasedGauge64,
+ backendWeight CounterBasedGauge64,
+ backendOutstanding CounterBasedGauge64,
+ backendQPSLimit CounterBasedGauge64,
+ backendReused Counter64,
+ backendState DisplayString,
+ backendAddress OCTET STRING,
+ backendPools DisplayString,
+ backendQPS CounterBasedGauge64,
+ backendQueries Counter64,
+ backendOrder CounterBasedGauge64
+}
+
+backendId OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Backend ID"
+ ::= { backendStatEntry 1 }
+
+backendName OBJECT-TYPE
+ SYNTAX DisplayString
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend name"
+ ::= { backendStatEntry 2 }
+
+backendLatency OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend latency"
+ ::= { backendStatEntry 3 }
+
+backendWeight OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend weight"
+ ::= { backendStatEntry 4 }
+
+backendOutstanding OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend outstanding queries"
+ ::= { backendStatEntry 5 }
+
+backendQPSLimit OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend QPS limit"
+ ::= { backendStatEntry 6 }
+
+backendReused OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend reused slots"
+ ::= { backendStatEntry 7 }
+
+backendState OBJECT-TYPE
+ SYNTAX DisplayString
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend state"
+ ::= { backendStatEntry 8 }
+
+backendAddress OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE (2..24))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend address"
+ ::= { backendStatEntry 9 }
+
+backendPools OBJECT-TYPE
+ SYNTAX DisplayString
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "List of pools this backend belongs to"
+ ::= { backendStatEntry 10 }
+
+backendQPS OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend QPS"
+ ::= { backendStatEntry 11 }
+
+backendQueries OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of queries sent to this backend"
+ ::= { backendStatEntry 12 }
+
+backendOrder OBJECT-TYPE
+ SYNTAX CounterBasedGauge64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Backend order"
+ ::= { backendStatEntry 13 }
+
+---
+--- Textual Conventions
+---
+
+SocketProtocolType ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "A value that represents a type of socket protocol."
+ SYNTAX INTEGER {
+ unknown(0),
+ udp(1),
+ tcp(2)
+ }
+
+DNSQueryType ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "A value that represents a type of DNS query (question or response)."
+ SYNTAX INTEGER {
+ unknown(0),
+ question(1),
+ response(2)
+ }
+
+---
+--- Traps / Notifications
+---
+
+trap OBJECT IDENTIFIER ::= { dnsdist 10 }
+traps OBJECT IDENTIFIER ::= { trap 0 } --- reverse-mappable
+trapObjects OBJECT IDENTIFIER ::= { dnsdist 11 }
+
+socketFamily OBJECT-TYPE
+ SYNTAX InetAddressType
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Socket family type"
+ ::= { trapObjects 1 }
+
+socketProtocol OBJECT-TYPE
+ SYNTAX SocketProtocolType
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Socket protocol type"
+ ::= { trapObjects 2 }
+
+fromAddress OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE (2..24))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Requestor address"
+ ::= { trapObjects 3 }
+
+toAddress OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE (2..24))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Responder address"
+ ::= { trapObjects 4 }
+
+queryType OBJECT-TYPE
+ SYNTAX DNSQueryType
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Query / Response"
+ ::= { trapObjects 5 }
+
+querySize OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Size in bytes"
+ ::= { trapObjects 6 }
+
+queryID OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "DNS query ID"
+ ::= { trapObjects 7 }
+
+qName OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE (0..255))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "DNS qname"
+ ::= { trapObjects 8 }
+
+qClass OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "DNS query class"
+ ::= { trapObjects 9 }
+
+qType OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "DNS query type"
+ ::= { trapObjects 10 }
+
+trapReason OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Reason for this trap"
+ ::= { trapObjects 11 }
+
+backendStatusChangeTrap NOTIFICATION-TYPE
+ OBJECTS {
+ backendName,
+ backendAddress,
+ backendState
+ }
+ STATUS current
+ DESCRIPTION "Backend status changed"
+ ::= { traps 1 }
+
+actionTrap NOTIFICATION-TYPE
+ OBJECTS {
+ socketFamily,
+ socketProtocol,
+ fromAddress,
+ toAddress,
+ queryType,
+ querySize,
+ queryID,
+ qName,
+ qClass,
+ qType,
+ trapReason
+ }
+ STATUS current
+ DESCRIPTION "Trap sent by SNMPTrapAction"
+ ::= { traps 2 }
+
+customTrap NOTIFICATION-TYPE
+ OBJECTS {
+ trapReason
+ }
+ STATUS current
+ DESCRIPTION "Trap sent by sendCustomTrap"
+ ::= { traps 3 }
+
+---
+--- Conformance
+---
+
+dnsdistConformance OBJECT IDENTIFIER ::= { dnsdist 100 }
+
+dnsdistCompliances MODULE-COMPLIANCE
+ STATUS current
+ DESCRIPTION "dnsdist compliance statement"
+ MODULE
+ MANDATORY-GROUPS {
+ dnsdistGroup,
+ dnsdistTrapsGroup
+ }
+ ::= { dnsdistConformance 1 }
+
+dnsdistGroup OBJECT-GROUP
+ OBJECTS {
+ queries,
+ responses,
+ servfailResponses,
+ aclDrops,
+ blockFilters,
+ ruleDrop,
+ ruleNXDomain,
+ ruleRefused,
+ selfAnswered,
+ downstreamTimeouts,
+ downstreamSendErrors,
+ truncFailures,
+ noPolicy,
+ latency01,
+ latency110,
+ latency1050,
+ latency50100,
+ latency1001000,
+ latencySlow,
+ latencyAVG100,
+ latencyAVG1000,
+ latencyAVG10000,
+ latencyAVG1000000,
+ uptime,
+ realMemoryUsage,
+ nonCompliantQueries,
+ nonCompliantResponses,
+ rdQueries,
+ emptyQueries,
+ cacheHits,
+ cacheMisses,
+ cpuUserMSec,
+ cpuSysMSec,
+ fdUsage,
+ dynBlocked,
+ dynBlockNMGSize,
+ backendName,
+ backendLatency,
+ backendWeight,
+ backendOutstanding,
+ backendQPSLimit,
+ backendReused,
+ backendState,
+ backendAddress,
+ backendPools,
+ backendQPS,
+ backendQueries,
+ backendOrder,
+ socketFamily,
+ socketProtocol,
+ fromAddress,
+ toAddress,
+ queryType,
+ querySize,
+ queryID,
+ qName,
+ qClass,
+ qType,
+ trapReason
+ }
+ STATUS current
+ DESCRIPTION "Objects conformance group for dnsdist"
+ ::= { dnsdistConformance 2 }
+
+dnsdistTrapsGroup NOTIFICATION-GROUP
+ NOTIFICATIONS {
+ actionTrap,
+ customTrap,
+ backendStatusChangeTrap
+ }
+ STATUS current
+ DESCRIPTION "Traps conformance group for dnsdist"
+ ::= { dnsdistConformance 3 }
+
+END
--- /dev/null
+AC_DEFUN([PDNS_WITH_NET_SNMP], [
+ AC_MSG_CHECKING([if we need to link in Net SNMP])
+ AC_ARG_WITH([net-snmp],
+ AS_HELP_STRING([--with-net-snmp],[enable net snmp support @<:@default=auto@:>@]),
+ [with_net_snmp=$withval],
+ [with_net_snmp=auto],
+ )
+ AC_MSG_RESULT([$with_net_snmp])
+
+ AS_IF([test "x$with_net_snmp" != "xno"], [
+ AS_IF([test "x$with_net_snmp" = "xyes" -o "x$with_net_snmp" = "xauto"], [
+ AC_CHECK_PROG([NET_SNMP_CFLAGS], [net-snmp-config], [`net-snmp-config --cflags`])
+ AC_CHECK_PROG([NET_SNMP_LIBS], [net-snmp-config], [`net-snmp-config --agent-libs`])
+ ])
+ ])
+ AS_IF([test "x$with_net_snmp" = "xyes"], [
+ AS_IF([test x"$NET_SNMP_LIBS" = "x"], [
+ AC_MSG_ERROR([Net SNMP requested but libraries were not found])
+ ])
+ ])
+ AM_CONDITIONAL([HAVE_NET_SNMP], [test x"$NET_SNMP_LIBS" != "x"])
+ AS_IF([test x"$NET_SNMP_LIBS" != "x"], [AC_DEFINE([HAVE_NET_SNMP], [1], [Define if using Net SNMP.])])
+])
* Skip the cache, if any
* Log query content to a remote server (RemoteLogAction)
* Alter the EDNS Client Subnet parameters (DisableECSAction, ECSOverrideAction, ECSPrefixLengthAction)
+ * Send an SNMP trap (SNMPTrapAction)
Current response actions are:
* Delay a response by n milliseconds (DelayResponseAction), over UDP only
* Drop (DropResponseAction)
* Log response content to a remote server (RemoteLogResponseAction)
+ * Send an SNMP trap (SNMPTrapResponseAction)
Rules can be added via:
* Log
* MacAddr
* No Recurse
+ * SNMP Trap
* and of course None
A convenience function `makeRule()` is supplied which will make a NetmaskGroupRule for you or a SuffixMatchNodeRule
This feature has been successfully tested on Arch Linux, Arch Linux ARM,
Fedora Core 23 and Ubuntu Xenial.
+SNMP support
+------------
+`dnsdist` supports exporting statistics and sending traps over SNMP when compiled
+with `Net SNMP` support, acting as an `AgentX` subagent.
+`SNMP` support is enabled via the `snmpAgent(enableTraps [, masterSocket])` directive,
+where `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket`
+is an optional string specifying how to connect to the master agent. The default for this
+last parameter is to use an Unix socket, but others options are available, such as TCP: `tcp:localhost:705`
+
+By default, the only traps sent when `enableTraps` is set to `true` are backend status change notifications, but traps can also be sent:
+
+ * from Lua, with `sendCustomTrap(string)` and `dq:sendTrap(string)`
+ * for selected queries and responses, using `SNMPTrapAction([string])` and `SNMPTrapResponseAction([string])`
+
+`Net SNMP snmpd` doesn't accept subagent connections by default, so to use the `SNMP`
+features of `dnsdist` the following line should be added to the `snmpd.conf` configuration
+file:
+
+```
+master agentx
+```
+
+In addition to that, the permissions on the resulting socket might need to be adjusted
+so that the `dnsdist` user can write to it. This can be done with the following lines in
+`snmpd.conf` (assuming `dnsdist` is running as `dnsdist:dnsdist`):
+
+```
+agentxperms 0700 0700 dnsdist dnsdist
+```
+
+In order to allow the retrieval of statistics via `SNMP`, `snmpd`'s access control
+has to configured. A very simple `SNMPv2c` setup only needs the configuration of
+a read-only community in `snmpd.conf`:
+
+```
+rocommunity dnsdist42
+```
+
+`snmpd` also supports more secure `SNMPv3` setup, using for example the `createUser` and
+`rouser` directives:
+
+```
+createUser myuser SHA "my auth key" AES "my enc key"
+rouser myuser
+```
+
+`snmpd` can be instructed to send `SNMPv2` traps to a remote `SNMP` trap receiver by adding the
+following directive to the `snmpd.conf` configuration file:
+
+```
+trap2sink 192.0.2.1
+```
+
+The description of `dnsdist`'s `SNMP MIB` is available in `DNSDIST-MIB.txt`.
+
All functions and types
-----------------------
Within `dnsdist` several core object types exist:
* `RemoteLogAction(RemoteLogger [, alterFunction])`: send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes
* `RemoteLogResponseAction(RemoteLogger [,alterFunction [,includeCNAME]])`: send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records
* `SkipCacheAction()`: don't lookup the cache for this query, don't store the answer
+ * `SNMPTrapAction([reason])`: send an SNMP trap, adding the optional `reason` string as the query description
+ * `SNMPTrapResponseAction([reason])`: send an SNMP trap, adding the optional `reason` string as the response description
* `SpoofAction(ip[, ip])` or `SpoofAction({ip, ip, ..}): forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in
* `SpoofCNAMEAction(cname)`: forge a response with the specified CNAME value
* `TCAction()`: create answer to query with TC and RD bits set, to move to TCP
* member `qtype`: QType (as an unsigned integer) of this question
* member `remoteaddr`: ComboAddress of the remote client
* member `rcode`: RCode of this question
+ * member `sendTrap([reason])`: send a trap containing the description of the query, and the optional `reason` string
* member `size`: the total size of the buffer starting at `dh`
* member `skipCache`: whether to skip cache lookup / storing the answer for this question (settable)
* member `tcp`: whether this question was received over a TCP socket
* function `unregisterDynBPFFilter(DynBPFFilter)`: unregister this dynamic BPF filter
* RemoteLogger related:
* `newRemoteLogger(address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1])`: create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`
+ * SNMP related:
+ * `snmpAgent(enableTraps [, masterSocket])`: enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent
+ * `sendCustomTrap(str)`: send a custom `SNMP` trap from Lua, containing the `str` string
All hooks
---------
{ "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
{ "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
{ "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
+ { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
{ "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
{ "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
{ "setDNSSECPool", true, "pool name", "move queries requesting DNSSEC processing to this pool" },
{ "showTCPStats", true, "", "show some statistics regarding TCP" },
{ "showVersion", true, "", "show the current version" },
{ "shutdown", true, "", "shut down `dnsdist`" },
+ { "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
+ { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
+ { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
{ "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
{ "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
{ "testCrypto", true, "", "test of the crypto all works" },
int counter=0;
auto states = g_dstates.getCopy();
for(const auto& s : states) {
- string status;
- if(s->availability == DownstreamState::Availability::Up)
- status = "UP";
- else if(s->availability == DownstreamState::Availability::Down)
- status = "DOWN";
- else
- status = (s->upStatus ? "up" : "down");
-
+ string status = s->getStatus();
string pools;
for(auto& p : s->pools) {
if(!pools.empty())
g_lua.registerFunction<bool(DNSQuestion::*)()>("getDO", [](const DNSQuestion& dq) {
return getEDNSZ((const char*)dq.dh, dq.len) & EDNS_HEADER_FLAG_DO;
});
+ g_lua.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+ if (g_snmpAgent && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendDNSTrap(dq, reason ? *reason : "");
+ }
+#endif /* HAVE_NET_SNMP */
+ });
/* LuaWrapper doesn't support inheritance */
g_lua.registerMember<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
g_lua.registerFunction<void(DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](const DNSResponse& dr, std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc) {
editDNSPacketTTL((char*) dr.dh, dr.len, editFunc);
});
+ g_lua.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+ if (g_snmpAgent && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendDNSTrap(dr, reason ? *reason : "");
+ }
+#endif /* HAVE_NET_SNMP */
+ });
g_lua.writeFunction("setMaxTCPClientThreads", [](uint64_t max) {
if (!g_configurationDone) {
g_useTCPSinglePipe = flag;
});
+ g_lua.writeFunction("snmpAgent", [](bool enableTraps, boost::optional<std::string> masterSocket) {
+#ifdef HAVE_NET_SNMP
+ if (g_configurationDone) {
+ errlog("snmpAgent() cannot be used at runtime!");
+ g_outputBuffer="snmpAgent() cannot be used at runtime!\n";
+ return;
+ }
+
+ if (g_snmpEnabled) {
+ errlog("snmpAgent() cannot be used twice!");
+ g_outputBuffer="snmpAgent() cannot be used twice!\n";
+ return;
+ }
+
+ g_snmpEnabled = true;
+ g_snmpTrapsEnabled = enableTraps;
+ g_snmpAgent = new DNSDistSNMPAgent("dnsdist", masterSocket ? *masterSocket : std::string());
+#else
+ errlog("NET SNMP support is required to use snmpAgent()");
+ g_outputBuffer="NET SNMP support is required to use snmpAgent()\n";
+#endif /* HAVE_NET_SNMP */
+ });
+
+ g_lua.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+ return std::shared_ptr<DNSAction>(new SNMPTrapAction(reason ? *reason : ""));
+#else
+ throw std::runtime_error("NET SNMP support is required to use SNMPTrapAction()");
+#endif /* HAVE_NET_SNMP */
+ });
+
+ g_lua.writeFunction("SNMPTrapResponseAction", [](boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+ return std::shared_ptr<DNSResponseAction>(new SNMPTrapResponseAction(reason ? *reason : ""));
+#else
+ throw std::runtime_error("NET SNMP support is required to use SNMPTrapResponseAction()");
+#endif /* HAVE_NET_SNMP */
+ });
+
+ g_lua.writeFunction("sendCustomTrap", [](const std::string& str) {
+#ifdef HAVE_NET_SNMP
+ if (g_snmpAgent && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendCustomTrap(str);
+ }
+#endif /* HAVE_NET_SNMP */
+ });
}
--- /dev/null
+
+#include "dnsdist-snmp.hh"
+#include "dolog.hh"
+
+#ifdef HAVE_NET_SNMP
+
+#define DNSDIST_OID 1, 3, 6, 1, 4, 1, 43315, 3
+#define DNSDIST_STATS_OID DNSDIST_OID, 1
+#define DNSDIST_STATS_TABLE_OID DNSDIST_OID, 2
+#define DNSDIST_TRAPS_OID DNSDIST_OID, 10, 0
+#define DNSDIST_TRAP_OBJECTS_OID DNSDIST_OID, 11
+
+static const oid queriesOID[] = { DNSDIST_STATS_OID, 1 };
+static const oid responsesOID[] = { DNSDIST_STATS_OID, 2 };
+static const oid servfailResponsesOID[] = { DNSDIST_STATS_OID, 3 };
+static const oid aclDropsOID[] = { DNSDIST_STATS_OID, 4 };
+static const oid blockFilterOID[] = { DNSDIST_STATS_OID, 5 };
+static const oid ruleDropOID[] = { DNSDIST_STATS_OID, 6 };
+static const oid ruleNXDomainOID[] = { DNSDIST_STATS_OID, 7 };
+static const oid ruleRefusedOID[] = { DNSDIST_STATS_OID, 8 };
+static const oid selfAnsweredOID[] = { DNSDIST_STATS_OID, 9 };
+static const oid downstreamTimeoutsOID[] = { DNSDIST_STATS_OID, 10 };
+static const oid downstreamSendErrorsOID[] = { DNSDIST_STATS_OID, 11 };
+static const oid truncFailOID[] = { DNSDIST_STATS_OID, 12 };
+static const oid noPolicyOID[] = { DNSDIST_STATS_OID, 13 };
+static const oid latency0_1OID[] = { DNSDIST_STATS_OID, 14 };
+static const oid latency1_10OID[] = { DNSDIST_STATS_OID, 15 };
+static const oid latency10_50OID[] = { DNSDIST_STATS_OID, 16 };
+static const oid latency50_100OID[] = { DNSDIST_STATS_OID, 17 };
+static const oid latency100_1000OID[] = { DNSDIST_STATS_OID, 18 };
+static const oid latencySlowOID[] = { DNSDIST_STATS_OID, 19 };
+static const oid latencyAvg100OID[] = { DNSDIST_STATS_OID, 20 };
+static const oid latencyAvg1000OID[] = { DNSDIST_STATS_OID, 21 };
+static const oid latencyAvg10000OID[] = { DNSDIST_STATS_OID, 22 };
+static const oid latencyAvg1000000OID[] = { DNSDIST_STATS_OID, 23 };
+static const oid uptimeOID[] = { DNSDIST_STATS_OID, 24 };
+static const oid realMemoryUsageOID[] = { DNSDIST_STATS_OID, 25 };
+static const oid nonCompliantQueriesOID[] = { DNSDIST_STATS_OID, 26 };
+static const oid nonCompliantResponsesOID[] = { DNSDIST_STATS_OID, 27 };
+static const oid rdQueriesOID[] = { DNSDIST_STATS_OID, 28 };
+static const oid emptyQueriesOID[] = { DNSDIST_STATS_OID, 29 };
+static const oid cacheHitsOID[] = { DNSDIST_STATS_OID, 30 };
+static const oid cacheMissesOID[] = { DNSDIST_STATS_OID, 31 };
+static const oid cpuUserMSecOID[] = { DNSDIST_STATS_OID, 32 };
+static const oid cpuSysMSecOID[] = { DNSDIST_STATS_OID, 33 };
+static const oid fdUsageOID[] = { DNSDIST_STATS_OID, 34 };
+static const oid dynBlockedOID[] = { DNSDIST_STATS_OID, 35 };
+static const oid dynBlockedNMGSizeOID[] = { DNSDIST_STATS_OID, 36 };
+
+static std::unordered_map<oid, DNSDistStats::entry_t> s_statsMap;
+
+/* We are never called for a GETNEXT if it's registered as a
+ "instance", as it's "magically" handled for us. */
+/* a instance handler also only hands us one request at a time, so
+ we don't need to loop over a list of requests; we'll only get one. */
+
+static int handleCounter64Stats(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ if (reqinfo->mode != MODE_GET) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+ return SNMP_ERR_GENERR;
+ }
+
+ const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+ if (it == s_statsMap.end()) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (const auto& val = boost::get<DNSDistStats::stat_t*>(&it->second)) {
+ return DNSDistSNMPAgent::setCounter64Value(requests, (*val)->load());
+ }
+
+ return SNMP_ERR_GENERR;
+}
+
+static void registerCounter64Stat(const char* name, const oid statOID[], size_t statOIDLength, std::atomic<uint64_t>* ptr)
+{
+ if (statOIDLength != OID_LENGTH(queriesOID)) {
+ errlog("Invalid OID for SNMP Counter64 statistic %s", name);
+ return;
+ }
+
+ if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
+ errlog("OID for SNMP Counter64 statistic %s has already been registered", name);
+ return;
+ }
+
+ s_statsMap[statOID[statOIDLength - 1]] = ptr;
+ netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+ handleCounter64Stats,
+ statOID,
+ statOIDLength,
+ HANDLER_CAN_RONLY));
+}
+
+static int handleFloatStats(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ if (reqinfo->mode != MODE_GET) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+ return SNMP_ERR_GENERR;
+ }
+
+ const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+ if (it == s_statsMap.end()) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (const auto& val = boost::get<double*>(&it->second)) {
+ std::string str(std::to_string(**val));
+ snmp_set_var_typed_value(requests->requestvb,
+ ASN_OCTET_STR,
+ str.c_str(),
+ str.size());
+ return SNMP_ERR_NOERROR;
+ }
+
+ return SNMP_ERR_GENERR;
+}
+
+static void registerFloatStat(const char* name, const oid statOID[], size_t statOIDLength, double* ptr)
+{
+ if (statOIDLength != OID_LENGTH(queriesOID)) {
+ errlog("Invalid OID for SNMP Float statistic %s", name);
+ return;
+ }
+
+ if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
+ errlog("OID for SNMP Float statistic %s has already been registered", name);
+ return;
+ }
+
+ s_statsMap[statOID[statOIDLength - 1]] = ptr;
+ netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+ handleFloatStats,
+ statOID,
+ statOIDLength,
+ HANDLER_CAN_RONLY));
+}
+
+static int handleGauge64Stats(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ if (reqinfo->mode != MODE_GET) {
+ return SNMP_ERR_GENERR;
+ }
+
+ if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+ return SNMP_ERR_GENERR;
+ }
+
+ const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+ if (it == s_statsMap.end()) {
+ return SNMP_ERR_GENERR;
+ }
+
+ std::string str;
+ uint64_t value = (*boost::get<DNSDistStats::statfunction_t>(&it->second))(str);
+ return DNSDistSNMPAgent::setCounter64Value(requests, value);
+}
+
+static void registerGauge64Stat(const char* name, const oid statOID[], size_t statOIDLength, DNSDistStats::statfunction_t ptr)
+{
+ if (statOIDLength != OID_LENGTH(queriesOID)) {
+ errlog("Invalid OID for SNMP Gauge64 statistic %s", name);
+ return;
+ }
+
+ if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
+ errlog("OID for SNMP Gauge64 statistic %s has already been registered", name);
+ return;
+ }
+
+ s_statsMap[statOID[statOIDLength - 1]] = ptr;
+ netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+ handleGauge64Stats,
+ statOID,
+ statOIDLength,
+ HANDLER_CAN_RONLY));
+}
+
+/* column number definitions for table backendStatTable */
+#define COLUMN_BACKENDID 1
+#define COLUMN_BACKENDNAME 2
+#define COLUMN_BACKENDLATENCY 3
+#define COLUMN_BACKENDWEIGHT 4
+#define COLUMN_BACKENDOUTSTANDING 5
+#define COLUMN_BACKENDQPSLIMIT 6
+#define COLUMN_BACKENDREUSED 7
+#define COLUMN_BACKENDSTATE 8
+#define COLUMN_BACKENDADDRESS 9
+#define COLUMN_BACKENDPOOLS 10
+#define COLUMN_BACKENDQPS 11
+#define COLUMN_BACKENDQUERIES 12
+#define COLUMN_BACKENDORDER 13
+
+static const oid backendStatTableOID[] = { DNSDIST_STATS_TABLE_OID };
+static const oid backendNameOID[] = { DNSDIST_STATS_TABLE_OID, 1, 2 };
+static const oid backendStateOID[] = { DNSDIST_STATS_TABLE_OID, 1, 8};
+static const oid backendAddressOID[] = { DNSDIST_STATS_TABLE_OID, 1, 9};
+
+static const oid socketFamilyOID[] = { DNSDIST_TRAP_OBJECTS_OID, 1, 0 };
+static const oid socketProtocolOID[] = { DNSDIST_TRAP_OBJECTS_OID, 2, 0 };
+static const oid fromAddressOID[] = { DNSDIST_TRAP_OBJECTS_OID, 3, 0 };
+static const oid toAddressOID[] = { DNSDIST_TRAP_OBJECTS_OID, 4, 0 };
+static const oid queryTypeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 5, 0 };
+static const oid querySizeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 6, 0 };
+static const oid queryIDOID[] = { DNSDIST_TRAP_OBJECTS_OID, 7, 0 };
+static const oid qNameOID[] = { DNSDIST_TRAP_OBJECTS_OID, 8, 0 };
+static const oid qClassOID[] = { DNSDIST_TRAP_OBJECTS_OID, 9, 0 };
+static const oid qTypeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 10, 0 };
+static const oid trapReasonOID[] = { DNSDIST_TRAP_OBJECTS_OID, 11, 0 };
+
+static const oid backendStatusChangeTrapOID[] = { DNSDIST_TRAPS_OID, 1 };
+static const oid actionTrapOID[] = { DNSDIST_TRAPS_OID, 2 };
+static const oid customTrapOID[] = { DNSDIST_TRAPS_OID, 3 };
+
+static servers_t s_servers;
+static size_t s_currentServerIdx = 0;
+
+static netsnmp_variable_list* backendStatTable_get_next_data_point(void** loop_context,
+ void** my_data_context,
+ netsnmp_variable_list* put_index_data,
+ netsnmp_iterator_info* mydata)
+{
+ if (s_currentServerIdx >= s_servers.size()) {
+ return NULL;
+ }
+
+ *my_data_context = (void*) (s_servers[s_currentServerIdx]).get();
+ snmp_set_var_typed_integer(put_index_data, ASN_UNSIGNED, s_currentServerIdx);
+ s_currentServerIdx++;
+
+ return put_index_data;
+}
+
+static netsnmp_variable_list* backendStatTable_get_first_data_point(void** loop_context,
+ void** data_context,
+ netsnmp_variable_list* put_index_data,
+ netsnmp_iterator_info* data)
+{
+ s_currentServerIdx = 0;
+
+ /* get a copy of the shared_ptrs so they are not
+ destroyed while we process the request */
+ const auto& dstates = g_dstates.getCopy();
+ s_servers.clear();
+ s_servers.reserve(dstates.size());
+ for (const auto& server : dstates) {
+ s_servers.push_back(server);
+ }
+
+ return backendStatTable_get_next_data_point(loop_context,
+ data_context,
+ put_index_data,
+ data);
+}
+
+static int backendStatTable_handler(netsnmp_mib_handler* handler,
+ netsnmp_handler_registration* reginfo,
+ netsnmp_agent_request_info* reqinfo,
+ netsnmp_request_info* requests)
+{
+ netsnmp_request_info* request;
+
+ switch (reqinfo->mode) {
+ case MODE_GET:
+ for (request = requests; request; request = request->next) {
+ netsnmp_table_request_info* table_info = netsnmp_extract_table_info(request);
+ const DownstreamState* server = (const DownstreamState*) netsnmp_extract_iterator_context(request);
+
+ if (!server) {
+ continue;
+ }
+
+ switch (table_info->colnum) {
+ case COLUMN_BACKENDNAME:
+ snmp_set_var_typed_value(request->requestvb,
+ ASN_OCTET_STR,
+ server->name.c_str(),
+ server->name.size());
+ break;
+ case COLUMN_BACKENDLATENCY:
+ DNSDistSNMPAgent::setCounter64Value(request,
+ server->latencyUsec/1000.0);
+ break;
+ case COLUMN_BACKENDWEIGHT:
+ DNSDistSNMPAgent::setCounter64Value(request,
+ server->weight);
+ break;
+ case COLUMN_BACKENDOUTSTANDING:
+ DNSDistSNMPAgent::setCounter64Value(request,
+ server->outstanding);
+ break;
+ case COLUMN_BACKENDQPSLIMIT:
+ DNSDistSNMPAgent::setCounter64Value(request,
+ server->qps.getRate());
+ break;
+ case COLUMN_BACKENDREUSED:
+ DNSDistSNMPAgent::setCounter64Value(request, server->reuseds);
+ break;
+ case COLUMN_BACKENDSTATE:
+ {
+ std::string state(server->getStatus());
+ snmp_set_var_typed_value(request->requestvb,
+ ASN_OCTET_STR,
+ state.c_str(),
+ state.size());
+ break;
+ }
+ case COLUMN_BACKENDADDRESS:
+ {
+ std::string addr(server->remote.toStringWithPort());
+ snmp_set_var_typed_value(request->requestvb,
+ ASN_OCTET_STR,
+ addr.c_str(),
+ addr.size());
+ break;
+ }
+ case COLUMN_BACKENDPOOLS:
+ {
+ std::string pools;
+ for(auto& p : server->pools) {
+ if(!pools.empty())
+ pools+=" ";
+ pools+=p;
+ }
+ snmp_set_var_typed_value(request->requestvb,
+ ASN_OCTET_STR,
+ pools.c_str(),
+ pools.size());
+ break;
+ }
+ case COLUMN_BACKENDQPS:
+ DNSDistSNMPAgent::setCounter64Value(request, server->queryLoad);
+ break;
+ case COLUMN_BACKENDQUERIES:
+ DNSDistSNMPAgent::setCounter64Value(request, server->queries);
+ break;
+ case COLUMN_BACKENDORDER:
+ DNSDistSNMPAgent::setCounter64Value(request, server->order);
+ break;
+ default:
+ netsnmp_set_request_error(reqinfo,
+ request,
+ SNMP_NOSUCHOBJECT);
+ break;
+ }
+ }
+ break;
+ }
+ return SNMP_ERR_NOERROR;
+}
+#endif /* HAVE_NET_SNMP */
+
+bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const std::shared_ptr<DownstreamState> dss)
+{
+#ifdef HAVE_NET_SNMP
+ const string backendAddress = dss->remote.toStringWithPort();
+ const string backendStatus = dss->getStatus();
+ netsnmp_variable_list* varList = nullptr;
+
+ snmp_varlist_add_variable(&varList,
+ snmpTrapOID,
+ snmpTrapOIDLen,
+ ASN_OBJECT_ID,
+ backendStatusChangeTrapOID,
+ OID_LENGTH(backendStatusChangeTrapOID) * sizeof(oid));
+
+
+ snmp_varlist_add_variable(&varList,
+ backendNameOID,
+ OID_LENGTH(backendNameOID),
+ ASN_OCTET_STR,
+ dss->name.c_str(),
+ dss->name.size());
+
+ snmp_varlist_add_variable(&varList,
+ backendAddressOID,
+ OID_LENGTH(backendAddressOID),
+ ASN_OCTET_STR,
+ backendAddress.c_str(),
+ backendAddress.size());
+
+ snmp_varlist_add_variable(&varList,
+ backendStateOID,
+ OID_LENGTH(backendStateOID),
+ ASN_OCTET_STR,
+ backendStatus.c_str(),
+ backendStatus.size());
+
+ return sendTrap(d_trapPipe[1], varList);
+#endif /* HAVE_NET_SNMP */
+ return true;
+}
+
+bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason)
+{
+#ifdef HAVE_NET_SNMP
+ netsnmp_variable_list* varList = nullptr;
+
+ snmp_varlist_add_variable(&varList,
+ snmpTrapOID,
+ snmpTrapOIDLen,
+ ASN_OBJECT_ID,
+ customTrapOID,
+ OID_LENGTH(customTrapOID) * sizeof(oid));
+
+ snmp_varlist_add_variable(&varList,
+ trapReasonOID,
+ OID_LENGTH(trapReasonOID),
+ ASN_OCTET_STR,
+ reason.c_str(),
+ reason.size());
+
+ return sendTrap(d_trapPipe[1], varList);
+#endif /* HAVE_NET_SNMP */
+ return true;
+}
+
+bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dq, const std::string& reason)
+{
+#ifdef HAVE_NET_SNMP
+ std::string local = dq.local->toString();
+ std::string remote = dq.remote->toString();
+ std::string qname = dq.qname->toStringNoDot();
+ const uint32_t socketFamily = dq.remote->isIPv4() ? 1 : 2;
+ const uint32_t socketProtocol = dq.tcp ? 2 : 1;
+ const uint32_t queryType = dq.dh->qr ? 2 : 1;
+ const uint32_t querySize = (uint32_t) dq.len;
+ const uint32_t queryID = (uint32_t) ntohs(dq.dh->id);
+ const uint32_t qType = (uint32_t) dq.qtype;
+ const uint32_t qClass = (uint32_t) dq.qclass;
+
+ netsnmp_variable_list* varList = nullptr;
+
+ snmp_varlist_add_variable(&varList,
+ snmpTrapOID,
+ snmpTrapOIDLen,
+ ASN_OBJECT_ID,
+ actionTrapOID,
+ OID_LENGTH(actionTrapOID) * sizeof(oid));
+
+ snmp_varlist_add_variable(&varList,
+ socketFamilyOID,
+ OID_LENGTH(socketFamilyOID),
+ ASN_INTEGER,
+ (u_char *) &socketFamily,
+ sizeof(socketFamily));
+
+ snmp_varlist_add_variable(&varList,
+ socketProtocolOID,
+ OID_LENGTH(socketProtocolOID),
+ ASN_INTEGER,
+ (u_char *) &socketProtocol,
+ sizeof(socketProtocol));
+
+ snmp_varlist_add_variable(&varList,
+ fromAddressOID,
+ OID_LENGTH(fromAddressOID),
+ ASN_OCTET_STR,
+ remote.c_str(),
+ remote.size());
+
+ snmp_varlist_add_variable(&varList,
+ toAddressOID,
+ OID_LENGTH(toAddressOID),
+ ASN_OCTET_STR,
+ local.c_str(),
+ local.size());
+
+ snmp_varlist_add_variable(&varList,
+ queryTypeOID,
+ OID_LENGTH(queryTypeOID),
+ ASN_INTEGER,
+ (u_char *) &queryType,
+ sizeof(queryType));
+
+ snmp_varlist_add_variable(&varList,
+ querySizeOID,
+ OID_LENGTH(querySizeOID),
+ ASN_UNSIGNED,
+ (u_char *) &querySize,
+ sizeof(querySize));
+
+ snmp_varlist_add_variable(&varList,
+ queryIDOID,
+ OID_LENGTH(queryIDOID),
+ ASN_UNSIGNED,
+ (u_char *) &queryID,
+ sizeof(queryID));
+
+ snmp_varlist_add_variable(&varList,
+ qNameOID,
+ OID_LENGTH(qNameOID),
+ ASN_OCTET_STR,
+ qname.c_str(),
+ qname.size());
+
+ snmp_varlist_add_variable(&varList,
+ qClassOID,
+ OID_LENGTH(qClassOID),
+ ASN_UNSIGNED,
+ (u_char *) &qClass,
+ sizeof(qClass));
+
+ snmp_varlist_add_variable(&varList,
+ qTypeOID,
+ OID_LENGTH(qTypeOID),
+ ASN_UNSIGNED,
+ (u_char *) &qType,
+ sizeof(qType));
+
+ snmp_varlist_add_variable(&varList,
+ trapReasonOID,
+ OID_LENGTH(trapReasonOID),
+ ASN_OCTET_STR,
+ reason.c_str(),
+ reason.size());
+
+ return sendTrap(d_trapPipe[1], varList);
+#endif /* HAVE_NET_SNMP */
+ return true;
+}
+
+DNSDistSNMPAgent::DNSDistSNMPAgent(const std::string& name, const std::string& masterSocket): SNMPAgent(name, masterSocket)
+{
+#ifdef HAVE_NET_SNMP
+
+ registerCounter64Stat("queries", queriesOID, OID_LENGTH(queriesOID), &g_stats.queries);
+ registerCounter64Stat("responses", responsesOID, OID_LENGTH(responsesOID), &g_stats.responses);
+ registerCounter64Stat("servfailResponses", servfailResponsesOID, OID_LENGTH(servfailResponsesOID), &g_stats.servfailResponses);
+ registerCounter64Stat("aclDrops", aclDropsOID, OID_LENGTH(aclDropsOID), &g_stats.aclDrops);
+ registerCounter64Stat("blockFilter", blockFilterOID, OID_LENGTH(blockFilterOID), &g_stats.blockFilter);
+ registerCounter64Stat("ruleDrop", ruleDropOID, OID_LENGTH(ruleDropOID), &g_stats.ruleDrop);
+ registerCounter64Stat("ruleNXDomain", ruleNXDomainOID, OID_LENGTH(ruleNXDomainOID), &g_stats.ruleNXDomain);
+ registerCounter64Stat("ruleRefused", ruleRefusedOID, OID_LENGTH(ruleRefusedOID), &g_stats.ruleRefused);
+ registerCounter64Stat("selfAnswered", selfAnsweredOID, OID_LENGTH(selfAnsweredOID), &g_stats.selfAnswered);
+ registerCounter64Stat("downstreamTimeouts", downstreamTimeoutsOID, OID_LENGTH(downstreamTimeoutsOID), &g_stats.downstreamTimeouts);
+ registerCounter64Stat("downstreamSendErrors", downstreamSendErrorsOID, OID_LENGTH(downstreamSendErrorsOID), &g_stats.downstreamSendErrors);
+ registerCounter64Stat("truncFail", truncFailOID, OID_LENGTH(truncFailOID), &g_stats.truncFail);
+ registerCounter64Stat("noPolicy", noPolicyOID, OID_LENGTH(noPolicyOID), &g_stats.noPolicy);
+ registerCounter64Stat("latency0_1", latency0_1OID, OID_LENGTH(latency0_1OID), &g_stats.latency0_1);
+ registerCounter64Stat("latency1_10", latency1_10OID, OID_LENGTH(latency1_10OID), &g_stats.latency1_10);
+ registerCounter64Stat("latency10_50", latency10_50OID, OID_LENGTH(latency10_50OID), &g_stats.latency10_50);
+ registerCounter64Stat("latency50_100", latency50_100OID, OID_LENGTH(latency50_100OID), &g_stats.latency50_100);
+ registerCounter64Stat("latency100_1000", latency100_1000OID, OID_LENGTH(latency100_1000OID), &g_stats.latency100_1000);
+ registerCounter64Stat("latencySlow", latencySlowOID, OID_LENGTH(latencySlowOID), &g_stats.latencySlow);
+ registerCounter64Stat("nonCompliantQueries", nonCompliantQueriesOID, OID_LENGTH(nonCompliantQueriesOID), &g_stats.nonCompliantQueries);
+ registerCounter64Stat("nonCompliantResponses", nonCompliantResponsesOID, OID_LENGTH(nonCompliantResponsesOID), &g_stats.nonCompliantResponses);
+ registerCounter64Stat("rdQueries", rdQueriesOID, OID_LENGTH(rdQueriesOID), &g_stats.rdQueries);
+ registerCounter64Stat("emptyQueries", emptyQueriesOID, OID_LENGTH(emptyQueriesOID), &g_stats.emptyQueries);
+ registerCounter64Stat("cacheHits", cacheHitsOID, OID_LENGTH(cacheHitsOID), &g_stats.cacheHits);
+ registerCounter64Stat("cacheMisses", cacheMissesOID, OID_LENGTH(cacheMissesOID), &g_stats.cacheMisses);
+ registerCounter64Stat("dynBlocked", dynBlockedOID, OID_LENGTH(dynBlockedOID), &g_stats.dynBlocked);
+ registerFloatStat("latencyAvg100", latencyAvg100OID, OID_LENGTH(latencyAvg100OID), &g_stats.latencyAvg100);
+ registerFloatStat("latencyAvg1000", latencyAvg1000OID, OID_LENGTH(latencyAvg1000OID), &g_stats.latencyAvg1000);
+ registerFloatStat("latencyAvg10000", latencyAvg10000OID, OID_LENGTH(latencyAvg10000OID), &g_stats.latencyAvg10000);
+ registerFloatStat("latencyAvg1000000", latencyAvg1000000OID, OID_LENGTH(latencyAvg1000000OID), &g_stats.latencyAvg1000000);
+ registerGauge64Stat("uptime", uptimeOID, OID_LENGTH(uptimeOID), &uptimeOfProcess);
+ registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID), &getRealMemoryUsage);
+ registerGauge64Stat("cpuUserMSec", cpuUserMSecOID, OID_LENGTH(cpuUserMSecOID), &getCPUTimeUser);
+ registerGauge64Stat("cpuSysMSec", cpuSysMSecOID, OID_LENGTH(cpuSysMSecOID), &getCPUTimeSystem);
+ registerGauge64Stat("fdUsage", fdUsageOID, OID_LENGTH(fdUsageOID), &getOpenFileDescriptors);
+ registerGauge64Stat("dynBlockedNMGSize", dynBlockedNMGSizeOID, OID_LENGTH(dynBlockedNMGSizeOID), [](const std::string&) { return g_dynblockNMG.getLocal()->size(); });
+
+
+ netsnmp_table_registration_info* table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
+ netsnmp_table_helper_add_indexes(table_info,
+ ASN_GAUGE, /* index: backendId */
+ 0);
+ table_info->min_column = COLUMN_BACKENDNAME;
+ table_info->max_column = COLUMN_BACKENDORDER;
+ netsnmp_iterator_info* iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
+ iinfo->get_first_data_point = backendStatTable_get_first_data_point;
+ iinfo->get_next_data_point = backendStatTable_get_next_data_point;
+ iinfo->table_reginfo = table_info;
+
+ netsnmp_register_table_iterator(netsnmp_create_handler_registration("backendStatTable",
+ backendStatTable_handler,
+ backendStatTableOID,
+ OID_LENGTH(backendStatTableOID),
+ HANDLER_CAN_RONLY),
+ iinfo);
+
+#endif /* HAVE_NET_SNMP */
+}
--- /dev/null
+#ifndef DNSDIST_SNMP_HH
+#define DNSDIST_SNMP_HH
+
+#pragma once
+
+#include "snmp-agent.hh"
+
+class DNSDistSNMPAgent;
+
+#include "dnsdist.hh"
+
+class DNSDistSNMPAgent: public SNMPAgent
+{
+public:
+ DNSDistSNMPAgent(const std::string& name, const std::string& masterSocket);
+ bool sendBackendStatusChangeTrap(const std::shared_ptr<DownstreamState>);
+ bool sendCustomTrap(const std::string& reason);
+ bool sendDNSTrap(const DNSQuestion&, const std::string& reason="");
+};
+
+#endif /* DNSDIST_SNMP_HH */
vector<ClientState *> g_frontends;
GlobalStateHolder<pools_t> g_pools;
+bool g_snmpEnabled{false};
+bool g_snmpTrapsEnabled{false};
+DNSDistSNMPAgent* g_snmpAgent{nullptr};
+
/* UDP: the grand design. Per socket we listen on for incoming queries there is one thread.
Then we have a bunch of connected sockets for talking to downstream servers.
We send directly to those sockets.
dss->upStatus = newState;
dss->currentCheckFailures = 0;
+ if (g_snmpAgent && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendBackendStatusChangeTrap(dss);
+ }
}
}
if(g_locals.empty())
g_locals.push_back(std::make_tuple(ComboAddress("127.0.0.1", 53), true, false, 0));
-
g_configurationDone = true;
/* this need to be done _after_ dropping privileges */
g_delay = new DelayPipe<DelayedPacket>();
+ if (g_snmpAgent) {
+ g_snmpAgent->run();
+ }
+
g_tcpclientthreads = std::make_shared<TCPClientCollection>(g_maxTCPClientThreads, g_useTCPSinglePipe);
for(auto& t : todo)
}
return name + " (" + remote.toStringWithPort()+ ")";
}
+ string getStatus() const
+ {
+ string status;
+ if(availability == DownstreamState::Availability::Up)
+ status = "UP";
+ else if(availability == DownstreamState::Availability::Down)
+ status = "DOWN";
+ else
+ status = (upStatus ? "up" : "down");
+ return status;
+ }
void reconnect();
};
using servers_t =vector<std::shared_ptr<DownstreamState>>;
int handleDnsCryptQuery(DnsCryptContext* ctx, char* packet, uint16_t len, std::shared_ptr<DnsCryptQuery>& query, uint16_t* decryptedQueryLen, bool tcp, std::vector<uint8_t>& response);
bool encryptResponse(char* response, uint16_t* responseLen, size_t responseSize, bool tcp, std::shared_ptr<DnsCryptQuery> dnsCryptQuery);
#endif
+
+#include "dnsdist-snmp.hh"
+
+extern bool g_snmpEnabled;
+extern bool g_snmpTrapsEnabled;
+extern DNSDistSNMPAgent* g_snmpAgent;
--- /dev/null
+../../docs/MIBS/DNSDIST-MIB.txt
\ No newline at end of file
-AM_CPPFLAGS += $(SYSTEMD_CFLAGS) $(LUA_CFLAGS) $(LIBEDIT_CFLAGS) $(YAHTTP_CFLAGS) $(SANITIZER_FLAGS) -DSYSCONFDIR=\"${sysconfdir}\"
+AM_CPPFLAGS += $(SYSTEMD_CFLAGS) $(LUA_CFLAGS) $(LIBEDIT_CFLAGS) $(YAHTTP_CFLAGS) $(SANITIZER_FLAGS) $(NET_SNMP_CFLAGS) -DSYSCONFDIR=\"${sysconfdir}\"
ACLOCAL_AMFLAGS = -I m4
lua_hpp.mk \
bpf-filter.main.ebpf \
bpf-filter.qname.ebpf \
- bpf-filter.ebpf.src
+ bpf-filter.ebpf.src \
+ DNSDIST-MIB.txt
bin_PROGRAMS = dnsdist
dnsdist-lua2.cc \
dnsdist-protobuf.cc dnsdist-protobuf.hh \
dnsdist-rings.cc \
+ dnsdist-snmp.cc dnsdist-snmp.hh \
dnsdist-tcp.cc \
dnsdist-web.cc \
dnslabeltext.cc \
qtype.cc qtype.hh \
remote_logger.cc remote_logger.hh \
sholder.hh \
+ snmp-agent.cc snmp-agent.hh \
sodcrypto.cc sodcrypto.hh \
sstuff.hh \
statnode.cc statnode.hh \
$(YAHTTP_LIBS) \
$(LIBSODIUM_LIBS) \
$(SANITIZER_FLAGS) \
- $(SYSTEMD_LIBS)
+ $(SYSTEMD_LIBS) \
+ $(NET_SNMP_LIBS)
if HAVE_RE2
dnsdist_LDADD += $(RE2_LIBS)
PDNS_CHECK_RE2
DNSDIST_ENABLE_DNSCRYPT
PDNS_WITH_EBPF
+PDNS_WITH_NET_SNMP
AX_AVAILABLE_SYSTEMD
AM_CONDITIONAL([HAVE_SYSTEMD], [ test x"$systemd" = "xy" ])
[AC_MSG_NOTICE([re2: yes])],
[AC_MSG_NOTICE([re2: no])]
)
+AS_IF([test "x$NET_SNMP_LIBS" != "x"],
+ [AC_MSG_NOTICE([SNMP: yes])],
+ [AC_MSG_NOTICE([SNMP: no])]
+)
AC_MSG_NOTICE([])
--- /dev/null
+../dnsdist-snmp.cc
\ No newline at end of file
--- /dev/null
+../dnsdist-snmp.hh
\ No newline at end of file
--- /dev/null
+../../../m4/pdns_with_net_snmp.m4
\ No newline at end of file
--- /dev/null
+../snmp-agent.cc
\ No newline at end of file
--- /dev/null
+../snmp-agent.hh
\ No newline at end of file
boost::optional<std::function<void(const DNSQuestion&, DNSDistProtoBufMessage*)> > d_alterFunc;
};
+class SNMPTrapAction : public DNSAction
+{
+public:
+ SNMPTrapAction(const std::string& reason): d_reason(reason)
+ {
+ }
+ DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+ {
+ if (g_snmpAgent && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendDNSTrap(*dq, d_reason);
+ }
+
+ return Action::None;
+ }
+ string toString() const override
+ {
+ return "send SNMP trap";
+ }
+private:
+ std::string d_reason;
+};
+
class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable
{
public:
private:
int d_msec;
};
+
+class SNMPTrapResponseAction : public DNSResponseAction
+{
+public:
+ SNMPTrapResponseAction(const std::string& reason): d_reason(reason)
+ {
+ }
+ DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override
+ {
+ if (g_snmpAgent && g_snmpTrapsEnabled) {
+ g_snmpAgent->sendDNSTrap(*dr, d_reason);
+ }
+
+ return Action::None;
+ }
+ string toString() const override
+ {
+ return "send SNMP trap";
+ }
+private:
+ std::string d_reason;
+};
--- /dev/null
+#include "snmp-agent.hh"
+#include "misc.hh"
+
+#ifdef HAVE_NET_SNMP
+
+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::handleTraps()
+{
+ 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);
+}
+#endif /* HAVE_NET_SNMP */
+
+void SNMPAgent::worker()
+{
+#ifdef HAVE_NET_SNMP
+ int numfds = 0;
+ int block = 1;
+ fd_set fdset;
+ struct timeval timeout = { 0, 0 };
+
+ while(true) {
+ numfds = FD_SETSIZE;
+
+ FD_ZERO(&fdset);
+ FD_SET(d_trapPipe[0], &fdset);
+ snmp_select_info(&numfds, &fdset, &timeout, &block);
+
+ int res = select(FD_SETSIZE, &fdset, NULL, NULL, NULL);
+
+ if (res == 2) {
+ FD_CLR(d_trapPipe[0], &fdset);
+ snmp_read(&fdset);
+ handleTraps();
+ }
+ else if (res == 1)
+ {
+ if (FD_ISSET(d_trapPipe[0], &fdset)) {
+ handleTraps();
+ } else {
+ snmp_read(&fdset);
+ }
+ }
+ else if (res == 0) {
+ snmp_timeout();
+ }
+ }
+#endif /* HAVE_NET_SNMP */
+}
+
+SNMPAgent::SNMPAgent(const std::string& name, const std::string& masterSocket)
+{
+#ifdef HAVE_NET_SNMP
+ netsnmp_enable_subagent();
+ snmp_disable_log();
+ if (!masterSocket.empty()) {
+ netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID,
+ NETSNMP_DS_AGENT_X_SOCKET,
+ masterSocket.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());
+ 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 */
+}
--- /dev/null
+#ifndef SNMP_AGENT_HH
+#define SNMP_AGENT_HH
+
+#include "config.h"
+
+#include <string>
+#include <thread>
+#include <unistd.h>
+
+#ifdef HAVE_NET_SNMP
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+#undef INET6 /* SRSLY? */
+#endif /* HAVE_NET_SNMP */
+
+class SNMPAgent
+{
+public:
+ SNMPAgent(const std::string& name, const std::string& masterSocket);
+ virtual ~SNMPAgent()
+ {
+#ifdef HAVE_NET_SNMP
+ close(d_trapPipe[0]);
+ close(d_trapPipe[1]);
+#endif /* HAVE_NET_SNMP */
+ }
+
+ void run()
+ {
+#ifdef HAVE_NET_SNMP
+ d_thread = std::move(std::thread(&SNMPAgent::worker, this));
+#endif /* HAVE_NET_SNMP */
+ }
+
+#ifdef HAVE_NET_SNMP
+ static int setCounter64Value(netsnmp_request_info* request,
+ uint64_t value);
+#endif /* HAVE_NET_SNMP */
+protected:
+#ifdef HAVE_NET_SNMP
+ /* OID for snmpTrapOID.0 */
+ static const oid snmpTrapOID[];
+ static const size_t snmpTrapOIDLen;
+
+ static bool sendTrap(int fd,
+ netsnmp_variable_list* varList);
+
+ void handleTraps();
+
+ int d_trapPipe[2] = { -1, -1};
+#endif /* HAVE_NET_SNMP */
+private:
+ void worker();
+
+ std::thread d_thread;
+};
+
+#endif /* SNMP_AGENT_HH */
libnacl>=1.4.3
requests>=2.1.0
protobuf>=2.5,<3.0
+pysnmp>=4.3.2
--- /dev/null
+
+# act as an Agent X master so that dnsdist can export SNMP statistics
+master agentx
+
+# allow dnsdist to connect to the Agent X master socket
+agentxperms 0700 0755 dnsdist
+
+# SNMPv2c community
+rocommunity secretcommunity
+
+# SNMPv3 user
+createUser secretuser SHA "mysecretauthkey" AES "mysecretenckey"
+rouser secretuser
--- /dev/null
+#!/usr/bin/env python
+import dns
+import time
+
+from pysnmp.hlapi import *
+from dnsdisttests import DNSDistTest
+
+class TestSNMP(DNSDistTest):
+
+ _snmpTimeout = 2.0
+ _snmpServer = '127.0.0.1'
+ _snmpPort = 161
+ _snmpV2Community = 'secretcommunity'
+ _snmpV3User = 'secretuser'
+ _snmpV3AuthKey = 'mysecretauthkey'
+ _snmpV3EncKey = 'mysecretenckey'
+ _snmpOID = '1.3.6.1.4.1.43315.3'
+ _queriesSent = 0
+ _config_template = """
+ newServer{address="127.0.0.1:%s", name="servername"}
+ snmpAgent(true)
+ """
+
+ def _checkStatsValues(self, results, queriesCountersValue):
+ for i in range(1, 20) + range(24, 35) + [ 35 ] :
+ oid = self._snmpOID + '.1.' + str(i) + '.0'
+ self.assertTrue(oid in results)
+ self.assertTrue(isinstance(results[oid], Counter64))
+
+ for i in range(20, 23):
+ oid = self._snmpOID + '.1.' + str(i) + '.0'
+ self.assertTrue(isinstance(results[oid], OctetString))
+
+ # check uptime > 0
+ self.assertGreater(results['1.3.6.1.4.1.43315.3.1.24.0'], 0)
+ # check memory usage > 0
+ self.assertGreater(results['1.3.6.1.4.1.43315.3.1.25.0'], 0)
+
+ # check that the queries, responses and rdQueries counters are now at queriesCountersValue
+ for i in [1, 2, 28]:
+ oid = self._snmpOID + '.1.' + str(i) + '.0'
+ self.assertEquals(results[oid], queriesCountersValue)
+
+ # the others counters (except for latency ones) should still be at 0
+ for i in range(3, 14) + [26, 27, 29, 30, 31, 35, 36]:
+ oid = self._snmpOID + '.1.' + str(i) + '.0'
+ self.assertEquals(results[oid], 0)
+
+ # check the backend stats
+ print(results)
+
+ ## types
+ for i in [3, 4, 5, 6, 7, 11, 12, 13]:
+ oid = self._snmpOID + '.2.1.' + str(i) + '.0'
+ self.assertTrue(isinstance(results[oid], Counter64))
+ for i in [2, 8, 9, 10]:
+ oid = self._snmpOID + '.2.1.' + str(i) + '.0'
+ self.assertTrue(isinstance(results[oid], OctetString))
+
+ ## name
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.2.0'], "servername")
+ ## weight
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.4.0'], 1)
+ ## outstanding
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.5.0'], 0)
+ ## qpslimit
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.6.0'], 0)
+ ## reused
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.7.0'], 0)
+ ## state
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.8.0'], "up")
+ ## address
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.9.0'], ("127.0.0.1:%s" % (self._testServerPort)))
+ ## pools
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.10.0'], "")
+ ## queries
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.12.0'], queriesCountersValue)
+ ## order
+ self.assertEquals(results['1.3.6.1.4.1.43315.3.2.1.13.0'], 1)
+
+ def _getSNMPStats(self, auth):
+ results = {}
+ for (errorIndication, errorStatus, errorIndex, varBinds) in nextCmd(SnmpEngine(),
+ auth,
+ UdpTransportTarget((self._snmpServer, self._snmpPort), timeout=self._snmpTimeout),
+ ContextData(),
+ ObjectType(ObjectIdentity(self._snmpOID)),
+ lookupMib=False):
+ self.assertFalse(errorIndication)
+ self.assertFalse(errorStatus)
+ self.assertTrue(varBinds)
+ for key, value in varBinds:
+ keystr = key.prettyPrint()
+ if not keystr.startswith(self._snmpOID):
+ continue
+ results[keystr] = value
+
+ return results
+
+ def _checkStats(self, auth, name):
+ # wait 1s so that the uptime is > 0
+ time.sleep(1)
+
+ results = self._getSNMPStats(auth)
+ self._checkStatsValues(results, self.__class__._queriesSent)
+
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ # send a query
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+ self.__class__._queriesSent = self.__class__._queriesSent + 1
+
+ results = self._getSNMPStats(auth)
+ self._checkStatsValues(results, self.__class__._queriesSent)
+
+ def testSNMPv2Stats(self):
+ """
+ SNMP: Retrieve statistics via SNMPv2c
+ """
+
+ auth = CommunityData(self._snmpV2Community, mpModel=1)
+ name = 'simplea.snmpv2c.tests.powerdns.com.'
+ self._checkStats(auth, name)
+
+ def testSNMPv3Stats(self):
+ """
+ SNMP: Retrieve statistics via SNMPv3
+ """
+
+ auth = UsmUserData(self._snmpV3User,
+ authKey=self._snmpV3AuthKey,
+ privKey=self._snmpV3EncKey,
+ authProtocol=usmHMACSHAAuthProtocol,
+ privProtocol=usmAesCfb128Protocol)
+ name = 'simplea.snmpv2.tests.powerdns.com.'
+ self._checkStats(auth, name)