]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
SNMP vv-test
authorVojtech Vilimek <vojtech.vilimek@nic.cz>
Wed, 21 Aug 2024 09:31:24 +0000 (11:31 +0200)
committerVojtech Vilimek <vojtech.vilimek@nic.cz>
Wed, 21 Aug 2024 20:31:52 +0000 (22:31 +0200)
The BIRD protocol SNMP makes it possible to retrieve management information
through SNMP. This is accomplished by implementing AgentX protocol. The BIRD
acts as an AgentX subagent, registers to master agent and provides management
information. Master agent handles SNMP communication and forwards request to
registered subagents. You will therefore need an additional component -- a SNMP
daemon capable of acting as AgentX master agent. In theory, the information
consumer don't have to support SNMP and could be very simple master agent for
logging/monitoring the BIRD state. For more detail see provided documentation.

This commit is squashed version of development history. Full development history
could be found on branch `proto-snmp'.

20 files changed:
configure.ac
doc/bird.sgml
doc/snmp.conf.example [new file with mode: 0644]
doc/snmpd.conf.example [new file with mode: 0644]
nest/protocol.h
proto/Doc
proto/snmp/Doc [new file with mode: 0644]
proto/snmp/Makefile [new file with mode: 0644]
proto/snmp/bgp4_mib.c [new file with mode: 0644]
proto/snmp/bgp4_mib.h [new file with mode: 0644]
proto/snmp/config.Y [new file with mode: 0644]
proto/snmp/mib_tree.c [new file with mode: 0644]
proto/snmp/mib_tree.h [new file with mode: 0644]
proto/snmp/snmp.c [new file with mode: 0644]
proto/snmp/snmp.h [new file with mode: 0644]
proto/snmp/snmp_test.c [new file with mode: 0644]
proto/snmp/snmp_utils.c [new file with mode: 0644]
proto/snmp/snmp_utils.h [new file with mode: 0644]
proto/snmp/subagent.c [new file with mode: 0644]
proto/snmp/subagent.h [new file with mode: 0644]

index 0ed4d2d74a93908939b7e3c50c26ce50539e46ff..65bbd20bf748c940b58c816d7dccf61ef16abcf2 100644 (file)
@@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then
   fi
 fi
 
-all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki static"
+all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki snmp static"
 
 all_protocols=`echo $all_protocols | sed 's/ /,/g'`
 
index e2050c13de61dc25dea8da419b935655d94e85c4..954fc7eddb88c5359aaa6a420e55b5f41eeabbbb 100644 (file)
@@ -6198,6 +6198,358 @@ protocol static {
 }
 </code>
 
+<sect>SNMP
+<label id="snmp">
+
+<p>The Simple Network Management Protocol is protocol for collecting and
+managing network devices. Managed information is divided into so called MIBs --
+Management Information Bases. Each MIB describe information and semantics it
+provides. The SNMP architecture is very flexible, some MIB are standartized by
+IETF, others published by independent third parties.
+
+<p>The BIRD SNMP support is achieved by an additional component -- a SNMP daemon
+with AgentX protocol support. The SNMP daemon acts as an AgentX master agent and
+deal with user authentication, access control and managing MIB regions in OID
+tree. BIRD instance acts as AgentX subagent, register configurated MIBs and
+provides queried data. The AgentX communication protocol between master agent
+and subagents does not consider security what so ever. It is therefore upon the
+user to use the AgentX in a secure way. This can be achieved either by using
+Unix Domain sockets on same host or by using secure tunnel. Note that following
+sections containing pieces of Net-SNMP configuraiton are only meant as a helper
+for cold start, or as a pointer what to search for, not as full reference. For
+full reference consult the original manpages.
+
+<sect1>SNMP Daemon configuration
+<p>We recommend you to use Net-SNMP implementation of daemon and utilities, quick
+guide below assume that. Net-SNMP implementation is quite popular so you should
+find it's packages inside your distribution package manager.
+
+<sect2>Example snmpd configuration
+<p>
+<code>
+# file /etc/snmp/snmpd.conf
+# minimal SNMPv3 config
+agentx master
+agentaddress udp:192.0.2.64
+agentXSocket tcp:198.51.100.2
+
+createUser snmp_name MD5 example_pass
+rwuser snmp_name noauth
+</code>
+
+<sect2>AgentX Enabling
+<p>
+<code>
+# File /etc/snmp/snmpd.conf
+agentx master
+agentXSocket [unix:|tcp:|tcp6:]&lt;address&gt;[,...]
+agentXPerms &lt;sockperms&gt; [&lt;dirperms&gt; [&lt;user&gt;|&lt;uid&gt; [&lt;group&gt;|&lt;gid&gt;]]]
+agentaddress [&lt;trasport-type&gt;:]&lt;trasport-address&gt;[,...]
+</code>
+
+<descrip>
+       <tag><label id="snmpd-agentx-master">agentx master</tag>
+       SNMP daemon will enable AgentX functionality and start listening on
+       configured AgentX address.
+
+
+       <tag><label id="snmpd-agentxsocket">agentXSocket
+       [unix:|tcp:|tcp6:]<m/trasport-address/[,...] </tag>
+
+       Define address to listen for AgentX subagent. Use one of <cf>unix:,
+       tcp:, tcp6:</cf> transport type. Other transport type are not supported
+       by BIRD and also not mentioned in AgentX RFC, see <rfc id="2741">.
+       Default: Unix Domain socket <file>/var/run/agentx/master</file>.
+
+
+       <tag><label id="snmpd-agentxperms">agentXPerms <m/sockperms/
+       [<m/dirperms/ [<m/user/|<m/uid/ [<m/group/|<m/gid/]]]</tag>
+
+       Define common permissions for AgentX listening Unix Domain sockets. Both
+       <m/sockperms/ and <m/dirperms/ must be octal digits like for
+       <m/chmod(1)/. Option <m/user/ is string and <m/uid/ is numeric user id.
+       Same for <m/group/ and <m/gid/.
+
+
+       <tag><label id="snmpd-agentaddress">agentaddress
+       [<m/transport-type/:]<m/trasport-address/[,...]</tag>
+
+       Define address, or list of addresses, to listen for SNMP requests (send
+       for example by <it/snmpwalk(1)/). You most likely want <m/trasport-type/ to
+       be one from <cf/udp:/, <cf/udp6:/, <cf/tcp:/, <cf/tcp6:/, <cf/unix:/,
+       <cf/ssh:/ but Net-SNMP support even more transport types.
+       Value <m/transport-address/ define address, Net-SNMP should be able to
+       derive <m/trasport-type/ from <m/transport-address/. Beware that for
+       Unix Domain socket derivation to work, the path must start with /. Also
+       note that the working directory of snmpd daemon is filesystem root.
+       Default: UDP on all IPv4 interfaces on port 161. (e.g.
+       <cf>agentaddress udp6:localhost:161</cf>,
+       <cf>agentaddress tcp:192.0.2.1</cf>,
+       <cf>agentaddress /var/run/mydir/agentx_master</cf>,
+       <cf>agentaddress localhost,/p/u1,/p/u2</cf>).
+</descrip>
+
+<sect2>Configure access
+<p>You can use the SNMPv3 USM module for user authorization, or use simpler older
+version SNMPv1/SNMPv2c with authentication by community. Other means of
+authorization are also possible (e.g. external Kerberos) but out of scope of
+this guide.
+
+<sect3>SNMPv3 USM
+<p>
+<code>
+# file /etc/snmp/snmpd.conf (continuation)
+createUser [-e &lt;engineid&gt;] &lt;username&gt; (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224) &lt;authpassphrase&gt; [DES|AES] [&lt;pass&gt;]
+rwuser [-s secmodel] &lt;user&gt; [noauth|auth|priv [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
+rouser [-s secmodel] &lt;user&gt; [noauth|auth|priv [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
+</code>
+
+<descrip>
+       <tag><label id="snmpd-createuser">createUser [-e <m/engineid/]
+       <m/username/ (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224)
+       <m/authpassphrase/ [DES|AES] [<m/privpassphrase/] </tag>
+
+       Create user with in order specified username, authentication type,
+       authentication password, private protocol and private password. If the
+       private password is not used, it fallbacks to same password as the
+       authentication one.
+
+
+       <tag><label id="snmpd-rwuser">rwuser [-s <m/secmodel/] <m/user/
+       [noauth|auth|priv [<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
+
+       Give user with selected security level read-write permissions
+       to the defined <m/oid/ OID subtree or <m/view/ view,
+       see <ref id="snmp_view" name="view definition">. Security level
+       <cf>noauth</cf> does not require authentication, <cf>auth</cf> requires
+       authentication and <cf>priv</cf> authentication with enforced message
+       encryption. View is a Net-SNMP construct to name and group set of OID
+       subtrees with optional context. Contexts are currently not supported by
+       BIRD.
+
+
+       <tag><label id="snmpd-rouser">rouser [-s <m/secmodel/] <m/user/
+       [noauth|auth|priv [<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
+
+       Same as <cf>rwuser</cf> but only with read permissions.
+</descrip>
+
+<sect3>SNMPv1/SNMPv2c community-based
+<p>
+<code>
+# file /etc/snmp/snmpd.conf (continuation)
+rwcommunity &lt;community&gt; [&lt;source&gt; [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
+rocommunity &lt;community&gt; [&lt;source&gt; [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
+rwcommunity6 &lt;community&gt; [&lt;source&gt; [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
+rocommunity6 &lt;community&gt; [&lt;source&gt; [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
+view &lt;vname&gt; (include|exclude) &lt;oid&gt; [&lt;mask&gt;]
+</code>
+
+<descrip>
+       <tag><label id="snmpd-rwcommunity">rwcommunity <m/community/ [<m/source/
+       [<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
+
+       Create a community with read-write permissions named <m/community/.
+       Option <m/source/ is used to restrict senders of request, use
+       <cf>"default"</cf> as a placeholder in doubts. You can also restrict
+       accessible regions of OID tree to OID subtree of <m/oid/ or named
+       <m/view/, otherwise the access is unrestricted to the whole OID tree.
+       Contexts are currently not supported by BIRD.
+
+
+       <tag><label id="snmpd-rocommunity">rocommunity <m/community/> [<m/source/
+       [<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
+
+       Same as <cf>rwcommunity</cf> but only with read permissions.
+
+
+       <tag><label id="snmpd-rwcommunity6">rwcommunity6 <m/community/
+       [<m/source/ [<m/oid/ | -V <m/view/ [<m/context/]]] </tag>
+
+       Is <cf>rwcommunity</cf> for packet received using IPv6.
+
+       <tag><label id="snmpd-rocommunity6">rocommunity6 <m/community/
+       [<m/source/ [<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
+
+       Same as <cf>rwcommunity6</cf> but only with read permissions.
+
+       <tag><label id="snmpd-view">view <m/vname/ (include|exclude) <m/oid/ [<m/mask/]</tag>
+       Define a named grouping of OIDs. May be used multiple times with same
+       <m/vname/.
+</descrip>
+
+
+<sect1>BIRD SNMP configuration
+<p>
+<code>
+protocol snmp [&lt;name&gt;] {
+       agentx master address (default|&lt;unix_path&gt;|&lt;ip&gt; [port &lt;port&gt;]);
+       subagent description &lt;text&gt;;
+       source address &lt;ip&gt;;
+       registration priority &lt;num&gt;;
+       message timeout &lt;time&gt;;
+       start delay time &lt;time&gt;;
+       verbose &lt;switch&gt;;
+
+       mib bgp4 {
+               local as &lt;num&gt;;
+               local router id &lt;ip4&gt;;
+               peer &lt;name&gt;;
+       };
+}
+</code>
+
+<descrip>
+       <tag><label id="snmp-master">agentx master address
+       (default|<m/unix_path/|<m/ip/ [port <m/port/])</tag>
+
+       Address of AgentX master. Default is <cf>"/var/run/agentx/master"</cf>.
+       String option <m/unix_path/ select transport over Unix Domain sockets
+       with selected path address. Option <m/ip/ select transport over TCP.
+
+       Default port for TCP transmission is 705.
+
+       <tag><label id="snmp-descr">subagent description <m/text/</tag>
+       Short string describing the subagent. Default: "bird".
+
+       <tag><label id="snmp-src-addr">source address <m/ip/</tag>
+       For TCP based AgentX communication sets socket's source address.
+
+       <tag><label id="snmp-reg-priority">registration priority <m/number/</tag>
+       Set AgentX registration priority for all MIBs. Lower values have higher
+       priority. Valid interval 0-255. Default: 127.
+
+       <tag><label id="snmp-msg-timeout">message timeout <m/time/ s|ms</tag>
+       Set timeout for all AgentX messages. With 1 second granurality with
+       values from interval 0-255. Default: 15 s.
+
+       <tag><label id="snmp-start-delay">start delay time <m/time/ s|ms</tag>
+       Wait <m/time/ before sending first packet after protocol start.
+
+       <tag><label id="snmp-verbose">verbose <m/switch/</tag>
+       Enable logging of events connected to AgentX master pinging. Default:
+       verbose logging is disabled.
+
+       <tag><label id="snmp-bgp4-mib">mib bgp4</tag>
+       Enable BGP4-MIB which is defined in <rfc id="4273">. The support is
+       limited to BGP4-MIB::bgpPeerTable; the BGP4-MIB::bgpRcvdPathAttrTable,
+       BGP4-MIB::bgpPathAttrTable, traps and notifications are not supported.
+
+       <tag><label id="snmp-bgp4-local-as">local as <m/number/</tag>
+       Specify Local As for BGP4 MIB (BGP4-MIB::bgpLocalAs.0). This option is
+       required.
+
+       <tag><label id="snmp-bgp4-router-id">local router id <m/IPv4 address/</tag>
+       Specify Router ID for BGP4 MIB (BGP4-MIB::bgpLocalIdentifier.0). This
+       option is required.
+
+       <tag><label id="snmp-bgp4-peer">peer <m/name/</tag>
+       Make information about BGP protocol <m/name/ accessible. This protocol
+       must over IPv4 (this limitation is introduced by the BGP4-MIB). May
+       be used multiple times.
+</descrip>
+
+<sect2>An example SNMP protocol configuration
+<p>
+<code>
+protocol bgp ibgp1 {
+       local as 2;
+       router id 192.0.2.1;
+       /* ... */
+}
+
+protocol bgp ibgp2 {
+       local as 4;
+       router id 192.0.2.128;
+       /* ... */
+}
+
+protocol snmp snmp1 {
+       agentx master address 198.51.100.2;
+
+       mib bgp4 {
+               local as 2;
+               local router id 192.0.2.1;
+               peer ibgp1;
+               peer ibgp2;
+       }
+}
+</code>
+
+<sect1>Accessing MIB data
+<p>To save some keystrokes and to avoid putting passwords in shell history, you
+could use common configuration file for all Net-SNMP command line utilities.
+Here is an example:
+
+<code>
+# file ~/.snmp/snmp.conf
+defVersion (1|2c|3)
+defCommunity &lt;community&gt;
+defSecurityName &lt;username&gt;
+defAuthType (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224)
+defAuthPassphrase &lt;authpass&gt;
+defPrivType (DES|AES)
+defPrivPassphrase &lt;privpass&gt;
+clientaddr [&lt;transport-type&gt;:]&lt;transport-address&gt;
+</code>
+
+<descrip>
+       <tag><label id="snmpcmd-version">defVersion (1|2c|3)</tag>
+       Select version of SNMP packets, must follow
+       <file>/etc/snmp/snmpd.conf</file>. Use only if community/view based
+       configuration.
+
+       <tag><label id="snmpcmd-community">defCommunity <m/community/</tag>
+       Use by default given community. Use only for SNMP version 1 and 2c as
+       community as kind of a username for these versions.
+
+       <tag><label id="snmpcmd-authtype">defAuthType
+       (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224)</tag>
+
+       Select authentication type.
+
+
+       <tag><label id="snmpcmd-authpass">defAuthPassphrase <m/authpass/</tag>
+       Set authentication password.
+
+       <tag><label id="snmpcmd-privtype">defPrivType (DES|AES)</tag>
+       Select private protocols.
+
+       <tag><label id="snmpcmd-privpass">defPrivPassphrase <m/privpass/</tag>
+       Set private password used for encryption.
+
+
+       <tag><label id="snmpcmd-clientaddr">clientaddr
+       [<m/transport-type/:]<m/transport-address/</tag>
+
+       SNMP command line utility equivalent to <cf>agentaddress</cf> daemon
+       option.
+</descrip>
+
+<p>For further information, see <it/snmp.conf(5)/.
+
+<p>Example of configuration for Net-SNMP utils.
+<code>
+# SNMPv3
+defVersion 3
+defSecurityName snmp_name
+defSecurityLevel noAuthNoPriv
+defAuthType MD5
+defAuthPassphrase example_pass
+</code>
+
+<code>
+$ # &lt;snmputil&gt; &lt;master-address&gt; &lt;oid&gt;[ &lt;oid&gt; [...]]
+$ snmpget     192.0.2.64 BGP4-MIB::bgpLocalAs.0
+$ snmpgetnext 192.0.2.64 BGP4-MIB::bgpPeerState BGP4-MIB::bgpPeerAdminStatus
+$ snmpwalk    192.0.2.64 BGP4-MIB::bgp
+$ snmptable   192.0.2.64 BGP4-MIB::bgpPeerTable
+</code>
+
+<p>We recommend you to check manpages for Net-SNMP utilities mentioned above,
+such as <it/snmp_config(5)/, <it/snmpd.conf(5)/, <it/snmpd(8)/,
+<it/snmp.conf(5)/, <it/snmpcmds(1)/, <it/snmpget(1)/, <it/snmpgetnext(1)/,
+<it/snmpbulkget(1)/, <it/snmpwalk(1)/, <it/snmptable(1)/.
 
 <chapt>Conclusions
 <label id="conclusion">
diff --git a/doc/snmp.conf.example b/doc/snmp.conf.example
new file mode 100644 (file)
index 0000000..36d9982
--- /dev/null
@@ -0,0 +1,5 @@
+defSecurityName snmp_name
+defAuthType MD5
+defAuthPassphrase test_pass
+defSecurityLevel noAuthNoPriv
+defVersion 3
diff --git a/doc/snmpd.conf.example b/doc/snmpd.conf.example
new file mode 100644 (file)
index 0000000..58066bd
--- /dev/null
@@ -0,0 +1,18 @@
+#
+#  snmpd configuration file
+#
+
+# SNMP connection address
+agentaddress  127.0.0.1
+
+# enable AgentX protocol
+master agentx
+
+# AgentX connection address
+agentXSocket tcp:localhost:705
+
+
+# Create example user
+createUser snmp_name MD5 test_pass
+rwuser snmp_name noauth
+
index 4f69a6a77b6fde735bd6a824580d9add9a77fd54..cb89f6128d766bb5273874a4b79e54d1afa7004b 100644 (file)
@@ -56,6 +56,7 @@ enum protocol_class {
   PROTOCOL_RADV,
   PROTOCOL_RIP,
   PROTOCOL_RPKI,
+  PROTOCOL_SNMP,
   PROTOCOL_STATIC,
   PROTOCOL__MAX
 };
@@ -105,9 +106,9 @@ void protos_dump_all(void);
  */
 
 extern struct protocol
-  proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
-  proto_ospf, proto_perf, proto_l3vpn, proto_aggregator,
-  proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
+  proto_device, proto_radv, proto_rip, proto_static, proto_mrt, proto_ospf,
+  proto_perf, proto_l3vpn, proto_aggregator, proto_pipe, proto_bgp, proto_bmp,
+  proto_bfd, proto_babel, proto_rpki, proto_snmp;
 
 /*
  *     Routing Protocol Instance
index 9de9eeec7acc6c327acf6e3f40d80a318b310d9d..07e1f48bfd49f2d7b498b1a0758dbe75116a4964 100644 (file)
--- a/proto/Doc
+++ b/proto/Doc
@@ -8,5 +8,6 @@ C pipe
 C radv
 C rip
 C rpki
+C snmp
 C static
 S ../nest/rt-dev.c
diff --git a/proto/snmp/Doc b/proto/snmp/Doc
new file mode 100644 (file)
index 0000000..a19894d
--- /dev/null
@@ -0,0 +1,5 @@
+S snmp.c
+S subagent.c
+S snmp_utils.c
+S mib_tree.c
+S bgp4_mib.c
diff --git a/proto/snmp/Makefile b/proto/snmp/Makefile
new file mode 100644 (file)
index 0000000..81055a5
--- /dev/null
@@ -0,0 +1,8 @@
+src := snmp.c snmp_utils.c subagent.c mib_tree.c bgp4_mib.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_src := snmp_test.c
+tests_targets := $(tests_targets) $(tests-target-files)
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/snmp/bgp4_mib.c b/proto/snmp/bgp4_mib.c
new file mode 100644 (file)
index 0000000..4ff61bc
--- /dev/null
@@ -0,0 +1,1009 @@
+/*
+ *     BIRD -- Simple Network Management Protocol (SNMP)
+ *        BGP4-MIB bgpPeerTable
+ *
+ *      (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ *      (c) 2022 CZ.NIC z.s.p.o
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include "snmp.h"
+#include "snmp_utils.h"
+#include "subagent.h"
+#include "bgp4_mib.h"
+#include "mib_tree.h"
+
+#include "nest/cli.h"
+
+/* hash table macros */
+#define SNMP_HASH_KEY(n)  n->peer_ip
+#define SNMP_HASH_NEXT(n) n->next
+#define SNMP_HASH_EQ(ip1, ip2) ip4_equal(ip1, ip2)
+#define SNMP_HASH_FN(ip)  ip4_hash(ip)
+
+#define SNMP_HASH_LESS4(ip1, ip2) ip4_less(ip1, ip2)
+#define SNMP_HASH_LESS6(ip1, ip2) ip6_less(ip1, ip2)
+
+/* hash table only store ip4 addresses */
+#define SNMP_HASH_LESS(ip1, ip2) SNMP_HASH_LESS4(ip1,ip2)
+
+#define DECLARE_BGP4(addr, proto, conn, stats, config) \
+  ip4_addr addr; \
+  const struct bgp_proto UNUSED *proto; \
+  const struct bgp_conn UNUSED *conn; \
+  const struct bgp_stats UNUSED *stats; \
+  const struct bgp_config UNUSED *config
+
+#define POPULATE_BGP4(addr, proto, conn, stats, config) populate_bgp4(c, &(addr), &(proto), &(conn), &(stats), &(config))
+
+static inline void ip4_to_oid(struct oid *oid, ip4_addr addr);
+static const STATIC_OID(2) bgp4_mib_oid = STATIC_OID_INITIALIZER(2, SNMP_MGMT, SNMP_MIB_2, SNMP_BGP4_MIB);
+
+static inline void
+snmp_hash_add_peer(struct snmp_proto *p, struct snmp_bgp_peer *peer)
+{
+  HASH_INSERT(p->bgp_hash, SNMP_HASH, peer);
+}
+
+static inline struct snmp_bgp_peer *
+snmp_hash_find(struct snmp_proto *p, ip4_addr key)
+{
+  return HASH_FIND(p->bgp_hash, SNMP_HASH, key);
+}
+
+static inline void
+snmp_bgp_last_error(const struct bgp_proto *bgp, char err[2])
+{
+  err[0] = bgp->last_error_code & 0x00FF0000 >> 16;
+  err[1] = bgp->last_error_code & 0x000000FF;
+}
+
+static void
+snmp_bgp_reg_failed(struct snmp_proto *p, const struct agentx_response *res, struct snmp_registration *reg)
+{
+  (void) res;
+  (void) reg;
+  snmp_reset(p);
+}
+
+/*
+ * snmp_bgp_fsm_state - extract BGP FSM state for SNMP BGP4-MIB
+ * @bgp_proto: BGP instance
+ *
+ * Return FSM state in BGP4-MIB encoding
+ */
+static inline uint
+snmp_bgp_fsm_state(const struct bgp_proto *bgp_proto)
+{
+  const struct bgp_conn *bgp_conn = bgp_proto->conn;
+  const struct bgp_conn *bgp_in = &bgp_proto->incoming_conn;
+  const struct bgp_conn *bgp_out = &bgp_proto->outgoing_conn;
+
+  if (bgp_conn)
+    return bgp_conn->state + 1;
+
+  if (MAX(bgp_in->state, bgp_out->state) == BS_CLOSE &&
+      MIN(bgp_in->state, bgp_out->state) != BS_CLOSE)
+    return MIN(bgp_in->state, bgp_out->state) + 1;
+  if (MIN(bgp_in->state, bgp_out->state) == BS_CLOSE)
+    return BS_IDLE;
+
+  return MAX(bgp_in->state, bgp_out->state) + 1;
+}
+
+
+/*
+ * snmp_bgp_notify_common - common functionaly for BGP4-MIB notifications
+ * @p: SNMP protocol instance
+ * @type: type of notification send - either established or backward transition
+ * @ip4: IPv4 remote addr
+ * @last_error: 2 bytes of BGP last error
+ * @state_val: BGP peer state as defined in MIB
+ */
+static void
+snmp_bgp_notify_common(struct snmp_proto *p, uint type, ip4_addr ip4, char last_error[], uint state_val)
+{
+  uint sz = (uint) (snmp_varbind_size_from_len(9, AGENTX_IP_ADDRESS, 0)
+      + snmp_varbind_size_from_len(9, AGENTX_OCTET_STRING, 2)
+      + snmp_varbind_size_from_len(9, AGENTX_INTEGER, 0));
+
+  u32 trap_ids[] = { 1, 0, type };
+  STATIC_ASSERT(ARRAY_SIZE(trap_ids) == 3);
+  /* additional size for trap identification, here either
+   *  bgpEstablishedNotification or bgpBackwardTransNotification (see below) */
+  void *data = tmp_alloc(snmp_oid_size_from_len(ARRAY_SIZE(trap_ids)) + sz);
+  struct oid *head = data;
+
+  { /* trap id BGP4-MIB::bgpEstablishedNotification (.1.3.6.1.2.15.0.1)
+     * or BGP4-MIB::bgpBackwardTransNotification (.1.3.6.1.2.15.0.2) */
+    head->n_subid = ARRAY_SIZE(trap_ids);
+    head->prefix = SNMP_MGMT;
+    head->include = head->reserved = 0;
+
+    for (uint i = 0; i < head->n_subid; i++)
+      head->ids[i] = trap_ids[i];
+  }
+
+  data += sz;
+  struct agentx_varbind *addr_vb = data;
+  struct agentx_varbind *error_vb = \
+    data + snmp_varbind_size_from_len(9, AGENTX_IP_ADDRESS, 0);
+  struct agentx_varbind *state_vb = \
+    (void *) error_vb + snmp_varbind_size_from_len(9, AGENTX_OCTET_STRING, 2);
+
+  u32 oid_ids[] = {
+    SNMP_MIB_2, SNMP_BGP4_MIB, BGP4_MIB_PEER_TABLE, BGP4_MIB_PEER_ENTRY
+  };
+
+  /*
+   * The n_subid is 9 in all cases because all are rows entries of
+   * BGP4-MIB::bgpPeerTable
+   *  BGP4-MIB::bgpPeerRemoteAddr = .1.3.6.1.[2].1.15.3.1.7.a.b.c.d
+   * where .1.3.6.1 is internet prefix, .[2] is SNMP_MGMT,
+   * .1.15.3.1.7.a.b.c.d has 9 elements (a.b.c.d are IP addr bytes)
+   *  Here subidentifier 7 is entry type bgpPeerRemoteAddr.
+   */
+  #define PEER_TABLE_ENTRY 9
+  #define ENTRY_TYPE 4
+
+  { /* BGP4-MIB::bgpPeerRemoteAddr */
+    struct oid *addr = &addr_vb->name;
+    *addr = (struct oid) {
+      .n_subid = PEER_TABLE_ENTRY, .prefix = SNMP_MGMT, .include = 0,
+      .reserved = 0,
+    };
+    for (uint i = 0; i < ARRAY_SIZE(oid_ids); i++)
+      addr->ids[i] = oid_ids[i];
+    addr->ids[ENTRY_TYPE] = BGP4_MIB_REMOTE_ADDR;
+    ip4_to_oid(addr, ip4);
+  }
+  /* We have enough space inside the TX buffer prepared */
+  struct snmp_pdu dummy = { 0 };
+  dummy.sr_vb_start = addr_vb;
+  snmp_varbind_ip4(&dummy, ip4);
+
+  { /* BGP4-MIB::bgpPeerLastError */
+    struct oid *error = &error_vb->name;
+    *error = (struct oid) {
+      .n_subid = PEER_TABLE_ENTRY, .prefix = SNMP_MGMT, .include = 0,
+      .reserved = 0,
+    };
+    for (uint i = 0; i < ARRAY_SIZE(oid_ids); i++)
+      error->ids[i] = oid_ids[i];
+    error->ids[ENTRY_TYPE] = BGP4_MIB_LAST_ERROR;
+    ip4_to_oid(error, ip4);
+  }
+
+  dummy.sr_vb_start = error_vb;
+  snmp_varbind_nstr(&dummy, last_error, 2);
+
+  { /* BGP4-MIB::bgpPeerState */
+    struct oid *state = &state_vb->name;
+    *state = (struct oid) {
+      .n_subid = PEER_TABLE_ENTRY, .prefix = SNMP_MGMT, .include = 0,
+      .reserved = 0,
+    };
+    for (uint i = 0; i < ARRAY_SIZE(oid_ids); i++)
+      state->ids[i] = oid_ids[i];
+    state->ids[ENTRY_TYPE] = BGP4_MIB_STATE;
+    ip4_to_oid(state, ip4);
+  }
+
+  dummy.sr_vb_start = state_vb;
+  snmp_varbind_int(&dummy, state_val);
+
+  /* We do not send the systemUpTime.0 */
+  snmp_notify_pdu(p, head, data, sz, 0);
+
+  #undef OID_N_SUBID
+}
+
+static void
+snmp_bgp_notify_wrapper(struct snmp_proto *p, struct bgp_proto *bgp, uint type)
+{
+  /* possibly incorrect cast */
+  ip4_addr ip4 = ipa_to_ip4(bgp->remote_ip);
+  char last_error[2];
+  snmp_bgp_last_error(bgp, last_error);
+  uint state_val = snmp_bgp_fsm_state(bgp);
+  snmp_bgp_notify_common(p, type, ip4, last_error, state_val);
+}
+
+void UNUSED
+snmp_bgp_notify_established(struct snmp_proto *p, struct bgp_proto *bgp)
+{
+  snmp_bgp_notify_wrapper(p, bgp, BGP4_MIB_ESTABLISHED_NOTIFICATION);
+}
+
+void UNUSED
+snmp_bgp_notify_backward_trans(struct snmp_proto *p, struct bgp_proto *bgp)
+{
+  snmp_bgp_notify_wrapper(p, bgp, BGP4_MIB_BACKWARD_TRANS_NOTIFICATION);
+}
+
+static int
+snmp_bgp_valid_ip4(const struct oid *o)
+{
+  return snmp_valid_ip4_index(o, 5);
+}
+
+static inline ip4_addr
+ip4_from_oid(const struct oid *o)
+{
+  return ip4_build(
+    o->n_subid > 5 ? (o->ids[5] & 0xff) : 0,
+    o->n_subid > 6 ? (o->ids[6] & 0xff) : 0,
+    o->n_subid > 7 ? (o->ids[7] & 0xff) : 0,
+    o->n_subid > 8 ? (o->ids[8] & 0xff) : 0
+  );
+}
+
+static inline void
+ip4_to_oid(struct oid *o, ip4_addr addr)
+{
+  u32 tmp = ip4_to_u32(addr);
+  ASSUME(o->n_subid >= 9);
+  o->ids[5] = (tmp & 0xFF000000) >> 24;
+  o->ids[6] = (tmp & 0x00FF0000) >> 16;
+  o->ids[7] = (tmp & 0x0000FF00) >>  8;
+  o->ids[8] = (tmp & 0x000000FF) >>  0;
+}
+
+static inline enum snmp_search_res
+populate_bgp4(struct snmp_pdu *c, ip4_addr *addr, const struct bgp_proto **proto, const struct bgp_conn
+**conn, const struct bgp_stats **stats, const struct bgp_config **config)
+{
+  const struct oid * const oid = &c->sr_vb_start->name;
+  if (snmp_bgp_valid_ip4(oid) && oid->n_subid == 9)
+    *addr = ip4_from_oid(oid);
+  else
+    return SNMP_SEARCH_NO_INSTANCE;
+
+  struct snmp_bgp_peer *pe = snmp_hash_find(c->p, *addr);
+  if (!pe)
+    return SNMP_SEARCH_NO_INSTANCE;
+
+  const struct bgp_proto *bgp_proto;
+  *proto = bgp_proto = pe->bgp_proto;
+  if (!ipa_is_ip4(bgp_proto->remote_ip))
+  {
+    c->error = AGENTX_RES_GEN_ERROR;
+    return SNMP_SEARCH_NO_INSTANCE;
+  }
+
+  ip4_addr proto_ip = ipa_to_ip4(bgp_proto->remote_ip);
+  if (!ip4_equal(proto_ip, pe->peer_ip))
+  {
+    /* Here, we could be in problem as the bgp_proto IP address could be changed */
+    c->error = AGENTX_RES_GEN_ERROR;
+    return SNMP_SEARCH_NO_INSTANCE;
+  }
+
+  *conn = bgp_proto->conn;
+  *stats = &bgp_proto->stats;
+  *config = bgp_proto->cf;
+
+  return SNMP_SEARCH_OK;
+}
+
+/*
+ *
+ *    MIB tree fill hooks
+ *
+ */
+
+static enum snmp_search_res
+fill_bgp_version(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  if (c->sr_vb_start->name.n_subid != 4)
+    return SNMP_SEARCH_NO_INSTANCE;
+  c->size -= snmp_str_size_from_len(1);
+  snmp_varbind_nstr(c, BGP4_VERSIONS, 1);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_local_as(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  if (c->sr_vb_start->name.n_subid != 4)
+    return SNMP_SEARCH_NO_INSTANCE;
+  snmp_varbind_int(c, c->p->bgp4_local_as);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_peer_id(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  uint fsm_state = snmp_bgp_fsm_state(bgp_proto);
+
+  if (fsm_state == BGP4_MIB_OPENCONFIRM || fsm_state == BGP4_MIB_ESTABLISHED)
+    // TODO last
+    snmp_varbind_ip4(c, ip4_from_u32(bgp_proto->remote_id));
+  else
+    snmp_varbind_ip4(c, IP4_NONE);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_peer_state(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  uint fsm_state = snmp_bgp_fsm_state(bgp_proto);
+
+  snmp_varbind_int(c, fsm_state);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_admin_status(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  if (bgp_proto->p.disabled)
+    snmp_varbind_int(c, BGP4_ADMIN_STOP);
+  else
+    snmp_varbind_int(c, BGP4_ADMIN_START);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_neg_version(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  uint fsm_state = snmp_bgp_fsm_state(bgp_proto);
+
+  if (fsm_state == BGP4_MIB_ESTABLISHED || fsm_state == BGP4_MIB_ESTABLISHED)
+    snmp_varbind_int(c, BGP4_MIB_NEGOTIATED_VER_VALUE);
+  else
+    snmp_varbind_int(c, BGP4_MIB_NEGOTIATED_VER_NO_VALUE);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_local_addr(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_ip4(c, ipa_to_ip4(bgp_proto->local_ip));
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_local_port(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_int(c, bgp_conf->local_port);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_remote_addr(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_ip4(c, ipa_to_ip4(bgp_proto->remote_ip));
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_remote_port(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_int(c, bgp_conf->remote_port);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_remote_as(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_int(c, bgp_proto->remote_as);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_in_updates(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_counter32(c, bgp_stats->rx_updates);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_out_updates(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_counter32(c, bgp_stats->tx_updates);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_in_total_msg(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_counter32(c, bgp_stats->rx_messages);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_out_total_msg(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_counter32(c, bgp_stats->tx_messages);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_last_err(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  char last_error[2];
+  snmp_bgp_last_error(bgp_proto, last_error);
+
+  snmp_varbind_nstr(c, last_error, 2);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_established_trans(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_counter32(c,
+      bgp_stats->fsm_established_transitions);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_established_time(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_gauge32(c,
+       (current_time() - bgp_proto->last_established) TO_S);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_retry_interval(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_int(c, bgp_conf->connect_retry_time);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_hold_time(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_int(c, (bgp_conn) ?  bgp_conn->hold_time : 0);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_keep_alive(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  if (!bgp_conf->hold_time)
+    snmp_varbind_int(c, 0);
+  else
+    snmp_varbind_int(c,
+      (bgp_conn) ? bgp_conn->keepalive_time : 0);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_hold_time_conf(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_int(c, bgp_conf->hold_time);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_keep_alive_conf(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  if (!bgp_conf->keepalive_time)
+    snmp_varbind_int(c, 0);
+  else
+    snmp_varbind_int(c,
+      (bgp_conn) ? bgp_conn->keepalive_time : 0);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_min_as_org_interval(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  /* value should be in 1..65535 but is not supported by bird */
+  snmp_varbind_int(c, 0);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_route_adv_interval(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  /* value should be in 1..65535 but is not supported by bird */
+  snmp_varbind_int(c, 0);
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_in_update_elapsed_time(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  enum snmp_search_res res;
+  DECLARE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  res = POPULATE_BGP4(addr, bgp_proto, bgp_conn, bgp_stats, bgp_conf);
+  if (res != SNMP_SEARCH_OK)
+    return res;
+
+  snmp_varbind_gauge32(c,
+    (current_time() - bgp_proto->last_rx_update) TO_S
+  );
+  return SNMP_SEARCH_OK;
+}
+
+static enum snmp_search_res
+fill_local_id(struct mib_walk_state *walk UNUSED, struct snmp_pdu *c)
+{
+  if (c->sr_vb_start->name.n_subid != 4)
+    return SNMP_SEARCH_NO_INSTANCE;
+  ip4_addr router_id_ip = ip4_from_u32(c->p->bgp4_local_id);
+  snmp_varbind_ip4(c, router_id_ip);
+  return SNMP_SEARCH_OK;
+}
+
+/*
+ * bgp4_next_peer - find next BGP peer with IPv4 address
+ * @state: MIB tree walk state
+ * @c: SNMP PDU context data
+ *
+ * Update TX buffer VarBind name to next peer address.
+ */
+static int
+bgp4_next_peer(struct mib_walk_state *state, struct snmp_pdu *c)
+{
+  struct oid *oid = &c->sr_vb_start->name;
+
+  /* BGP4-MIB::bgpPeerIdentifier */
+  STATIC_OID(9) bgp4_peer_id = STATIC_OID_INITIALIZER(9, SNMP_MGMT,
+    /* ids */ SNMP_MIB_2, SNMP_BGP4_MIB,
+      BGP4_MIB_PEER_TABLE, BGP4_MIB_PEER_ENTRY, BGP4_MIB_PEER_IDENTIFIER);
+
+  ip4_addr ip4 = ip4_from_oid(oid);
+
+  const struct oid *peer_oid = (const struct oid *) &bgp4_peer_id;
+
+  int precise = 1;
+  if (oid->n_subid > 9)
+    precise = 0;
+
+  if (oid->n_subid != 9 || snmp_oid_compare(oid, peer_oid) < 0)
+  {
+    int old = snmp_oid_size(oid);
+    int new = snmp_oid_size(peer_oid);
+
+    if (new - old > 0 && snmp_tbuf_reserve(c, new - old))
+      oid = &c->sr_vb_start->name;
+
+    c->buffer += (new - old);
+
+    snmp_oid_copy(oid, peer_oid);
+    oid->include = 1;
+  }
+
+  ASSUME(oid->n_subid == 9);
+  /* Stack has one more node for empty prefix (tree root) */
+  ASSUME(state->stack_pos > 10);
+  oid->ids[4] = state->stack[10]->empty.id;
+
+  net_addr net;
+  net_fill_ip4(&net, ip4, IP4_MAX_PREFIX_LENGTH);
+  struct f_trie_walk_state ws;
+
+  int match = trie_walk_init(&ws, c->p->bgp_trie, &net, 1);
+
+  if (match && oid->include && precise)
+  {
+    oid->include = 0;
+    ip4_to_oid(oid, ip4);
+    return 0;
+  }
+
+  /* We skip the first match as we should not include ip address in oid */
+  if (match)
+   (void) trie_walk_next(&ws, &net);
+
+  if (trie_walk_next(&ws, &net))
+  {
+    ASSUME(oid->n_subid == 9);
+    ip4_addr res = ipa_to_ip4(net_prefix(&net));
+    ip4_to_oid(oid, res);
+    return 0;
+  }
+
+  return 1;
+}
+
+/*
+ * snmp_bgp4_show_info - display info BGP4-MIB
+ * @p: SNMP protocol instance
+ *
+ * Print info about BGP4-MIB status and bound bgp peers to cli.
+ */
+void
+snmp_bgp4_show_info(struct snmp_proto *p)
+{
+  /* TODO: Use special code (not -1006) for printing MIB, or BGP4-MIB data?
+   * don't forget add it into doc/reply_code */
+  cli_msg(-1006, "    BGP4-MIB");
+  cli_msg(-1006, "      Local AS %u", p->bgp4_local_as);
+  cli_msg(-1006, "      Local router id %R", p->bgp4_local_id);
+  cli_msg(-1006, "      BGP peers");
+
+  if (p->bgp_hash.count == 0 || !snmp_is_active(p))
+  {
+    cli_msg(-1006, "        <no peers available>");
+  }
+
+  if (!snmp_is_active(p))
+    return;
+
+  HASH_WALK(p->bgp_hash, next, peer)
+  {
+    cli_msg(-1006, "        Protocol name: %s", peer->bgp_proto->p.name);
+    cli_msg(-1006, "          Remote IPv4 address: %I4", peer->peer_ip);
+    cli_msg(-1006, "          Remote router id %R", peer->bgp_proto->remote_id);
+    /* TODO: add peer connection local ip */
+  }
+  HASH_WALK_END;
+}
+
+void
+snmp_bgp4_register(struct snmp_proto *p)
+{
+  /* Register the whole BGP4-MIB::bgp root tree node */
+  struct snmp_registration *reg;
+  reg = snmp_registration_create(p, BGP4_MIB_ID);
+
+  struct oid *oid = mb_allocz(p->pool, sizeof(bgp4_mib_oid));
+  memcpy(oid, &bgp4_mib_oid, sizeof(bgp4_mib_oid));
+
+  reg->reg_hook_ok = NULL;
+  reg->reg_hook_fail = snmp_bgp_reg_failed;
+
+  /*
+   * We set both upper bound and index to zero, therefore only single OID
+   * is being registered.
+   */
+  snmp_register(p, oid, 0, 0, SNMP_REGISTER_TREE);
+}
+
+
+/*
+ * snmp_bgp4_start - prepare BGP4-MIB
+ * @p: SNMP protocol instance holding memory pool
+ * @with_mib_tree: flag choosing to insert BGP4-MIB into MIB tree
+ *
+ * This function create all runtime bindings to BGP procotol structures.
+ * It is gruaranteed that the BGP protocols exist.
+ */
+void
+snmp_bgp4_start(struct snmp_proto *p, int with_mib_tree)
+{
+  struct snmp_config *cf = SKIP_BACK(struct snmp_config, cf, p->p.cf);
+
+  /* Create binding to BGP protocols */
+  struct snmp_bond *b;
+  WALK_LIST(b, cf->bgp_entries)
+  {
+    const struct bgp_config *bgp_config = (struct bgp_config *) b->config;
+    const struct bgp_proto *bgp = SKIP_BACK(struct bgp_proto, p,
+      bgp_config->c.proto);
+
+    struct snmp_bgp_peer *peer = \
+      mb_alloc(p->pool, sizeof(struct snmp_bgp_peer));
+
+    peer->bgp_proto = bgp;
+    peer->peer_ip = ipa_to_ip4(bgp->remote_ip);
+
+    struct net_addr net;
+    net_fill_ip4(&net, ipa_to_ip4(bgp->remote_ip), IP4_MAX_PREFIX_LENGTH);
+    trie_add_prefix(p->bgp_trie, &net, IP4_MAX_PREFIX_LENGTH,
+      IP4_MAX_PREFIX_LENGTH);
+
+    snmp_hash_add_peer(p, peer);
+  }
+
+  if (!with_mib_tree)
+    return;
+
+  const STATIC_OID(4) bgp4_mib_peer_entry = STATIC_OID_INITIALIZER(4, SNMP_MGMT,
+    /* ids */ SNMP_MIB_2, SNMP_BGP4_MIB, BGP4_MIB_PEER_TABLE, BGP4_MIB_PEER_ENTRY);
+
+  (void) mib_tree_hint(p->pool, p->mib_tree,
+    (const struct oid *) &bgp4_mib_oid, BGP4_MIB_IDENTIFIER);
+  (void) mib_tree_hint(p->pool, p->mib_tree,
+    (const struct oid *) &bgp4_mib_peer_entry, BGP4_MIB_IN_UPDATE_ELAPSED_TIME);
+
+  mib_node_u *node;
+  struct mib_leaf *leaf;
+  STATIC_OID(4) bgp4_var = STATIC_OID_INITIALIZER(4, SNMP_MGMT,
+    /* ids */ SNMP_MIB_2, SNMP_BGP4_MIB, BGP4_MIB_VERSION, 0);
+
+  struct {
+    u32 id;
+    enum snmp_search_res (*filler)(struct mib_walk_state *state, struct snmp_pdu *c);
+    enum agentx_type type;
+    int size;
+  } leafs[] = {
+    {
+      .id = BGP4_MIB_VERSION,
+      .filler = fill_bgp_version,
+      .type = AGENTX_OCTET_STRING,
+      .size = snmp_str_size_from_len(sizeof(BGP4_VERSIONS)),
+    },
+    {
+      .id =  BGP4_MIB_LOCAL_AS,
+      .filler = fill_local_as,
+      .type = AGENTX_INTEGER,
+    },
+    {
+      .id =  BGP4_MIB_IDENTIFIER,
+      .filler = fill_local_id,
+      .type = AGENTX_IP_ADDRESS,
+    },
+  };
+
+  for (uint i = 0; i < ARRAY_SIZE(leafs); i++)
+  {
+    bgp4_var.ids[ARRAY_SIZE(bgp4_var.ids) - 2] = leafs[i].id;
+    node = mib_tree_add(p->pool, p->mib_tree, (const struct oid *) &bgp4_var, 1);
+
+    ASSUME(mib_node_is_leaf(node));
+    leaf = &node->leaf;
+
+    leaf->filler = leafs[i].filler;
+    leaf->call_next = NULL;
+    leaf->type = leafs[i].type;
+    leaf->size = leafs[i].size;
+  }
+
+  STATIC_OID(5) bgp4_entry_var = STATIC_OID_INITIALIZER(5, SNMP_MGMT,
+    /* ids */ SNMP_MIB_2, SNMP_BGP4_MIB,
+       BGP4_MIB_PEER_TABLE, BGP4_MIB_PEER_ENTRY, BGP4_MIB_PEER_IDENTIFIER);
+
+  struct {
+      enum snmp_search_res (*filler)(struct mib_walk_state *state, struct snmp_pdu *c);
+      enum agentx_type type;
+      int size;
+  } entry_leafs[] = {
+    [BGP4_MIB_PEER_IDENTIFIER] = {
+      .filler =        fill_peer_id,
+      .type = AGENTX_IP_ADDRESS,
+    },
+    [BGP4_MIB_STATE] = {
+      .filler = fill_peer_state,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_ADMIN_STATUS] = {
+      .filler = fill_admin_status,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_NEGOTIATED_VERSION] = {
+      .filler = fill_neg_version,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_LOCAL_ADDR] = {
+      .filler = fill_local_addr,
+      .type = AGENTX_IP_ADDRESS,
+    },
+    [BGP4_MIB_LOCAL_PORT] = {
+      .filler = fill_local_port,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_REMOTE_ADDR] = {
+      .filler = fill_remote_addr,
+      .type = AGENTX_IP_ADDRESS,
+    },
+    [BGP4_MIB_REMOTE_PORT] = {
+      .filler = fill_remote_port,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_REMOTE_AS] = {
+      .filler = fill_remote_as,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_RX_UPDATES] = {
+      .filler = fill_in_updates,
+      .type = AGENTX_COUNTER_32,
+    },
+    [BGP4_MIB_TX_UPDATES] = {
+      .filler = fill_out_updates,
+      .type = AGENTX_COUNTER_32,
+    },
+    [BGP4_MIB_RX_MESSAGES] = {
+      .filler = fill_in_total_msg,
+      .type = AGENTX_COUNTER_32,
+    },
+    [BGP4_MIB_TX_MESSAGES] = {
+      .filler = fill_out_total_msg,
+      .type = AGENTX_COUNTER_32,
+    },
+    [BGP4_MIB_LAST_ERROR] = {
+      .filler = fill_last_err,
+      .type = AGENTX_OCTET_STRING,
+      .size = snmp_str_size_from_len(2),
+    },
+    [BGP4_MIB_FSM_TRANSITIONS] = {
+      .filler = fill_established_trans,
+      .type = AGENTX_COUNTER_32,
+    },
+    [BGP4_MIB_FSM_ESTABLISHED_TIME] = {
+      .filler = fill_established_time,
+      .type = AGENTX_GAUGE_32,
+    },
+    [BGP4_MIB_RETRY_INTERVAL] = {
+      .filler = fill_retry_interval,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_HOLD_TIME] = {
+      .filler = fill_hold_time,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_KEEPALIVE] = {
+      .filler = fill_keep_alive,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_HOLD_TIME_CONFIGURED] = {
+      .filler = fill_hold_time_conf,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_KEEPALIVE_CONFIGURED] = {
+      .filler = fill_keep_alive_conf,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_ORIGINATION_INTERVAL] = {
+      .filler = fill_min_as_org_interval,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_MIN_ROUTE_ADVERTISEMENT] = {
+      .filler = fill_route_adv_interval,
+      .type = AGENTX_INTEGER,
+    },
+    [BGP4_MIB_IN_UPDATE_ELAPSED_TIME] = {
+      .filler = fill_in_update_elapsed_time,
+      .type = AGENTX_GAUGE_32,
+    },
+  }; /* struct _anon entry_leafs[] */
+
+  for (enum bgp4_mib_peer_entry_row e = BGP4_MIB_PEER_IDENTIFIER;
+      e <= BGP4_MIB_IN_UPDATE_ELAPSED_TIME; e++)
+  {
+    bgp4_entry_var.ids[ARRAY_SIZE(bgp4_entry_var.ids) - 1] = (u32) e;
+    node = mib_tree_add(p->pool, p->mib_tree, (const struct oid *) &bgp4_entry_var, 1);
+
+    ASSUME(mib_node_is_leaf(node));
+    leaf = &node->leaf;
+
+    leaf->filler = entry_leafs[e].filler;
+    leaf->call_next = bgp4_next_peer;
+    leaf->type = entry_leafs[e].type;
+    leaf->size = entry_leafs[e].size;
+  }
+}
diff --git a/proto/snmp/bgp4_mib.h b/proto/snmp/bgp4_mib.h
new file mode 100644 (file)
index 0000000..21a6767
--- /dev/null
@@ -0,0 +1,97 @@
+#ifndef _BIRD_SNMP_BGP4_MIB_H_
+#define _BIRD_SNMP_BGP4_MIB_H_
+
+#include "snmp.h"
+#include "proto/bgp/bgp.h"
+#include "subagent.h"
+
+/* BGP4-MIB root identifier is found in snmp.h as SNMP_BGP4_MIB */
+
+/* peers attributes */
+enum bgp4_mib_peer_entry_row {
+  BGP4_MIB_PEER_IDENTIFIER         =  1,
+  BGP4_MIB_STATE                   =  2,
+  BGP4_MIB_ADMIN_STATUS                    =  3,   /* in read-only mode */
+  BGP4_MIB_NEGOTIATED_VERSION      =  4,
+  BGP4_MIB_LOCAL_ADDR              =  5,
+  BGP4_MIB_LOCAL_PORT              =  6,
+  BGP4_MIB_REMOTE_ADDR             =  7,
+  BGP4_MIB_REMOTE_PORT             =  8,
+  BGP4_MIB_REMOTE_AS               =  9,
+  BGP4_MIB_RX_UPDATES              = 10,   /* in updates */
+  BGP4_MIB_TX_UPDATES              = 11,   /* out updates */
+  BGP4_MIB_RX_MESSAGES             = 12,   /* in total messages */
+  BGP4_MIB_TX_MESSAGES             = 13,   /* out total messages */
+  BGP4_MIB_LAST_ERROR              = 14,
+  BGP4_MIB_FSM_TRANSITIONS         = 15,   /* FSM established transitions */
+  BGP4_MIB_FSM_ESTABLISHED_TIME            = 16,
+  BGP4_MIB_RETRY_INTERVAL          = 17,
+  BGP4_MIB_HOLD_TIME               = 18,   /* in read-only mode */
+  BGP4_MIB_KEEPALIVE               = 19,   /* in read-only mode */
+  BGP4_MIB_HOLD_TIME_CONFIGURED            = 20,
+  BGP4_MIB_KEEPALIVE_CONFIGURED            = 21,
+  BGP4_MIB_ORIGINATION_INTERVAL            = 22,   /* UNSUPPORTED - 0 */
+  BGP4_MIB_MIN_ROUTE_ADVERTISEMENT  = 23,   /* UNSUPPORTED - 0 */
+  BGP4_MIB_IN_UPDATE_ELAPSED_TIME   = 24,
+} PACKED;
+
+/* version of BGP, here BGP-4 */
+#define BGP4_VERSIONS ((char[]) { 0x10 }) /* OID BGP4-MIB::bgpVersion */
+
+/* values for BGP4-MIB::bgpPeerNegotiatedVersion */
+#define BGP4_MIB_NEGOTIATED_VER_VALUE 4
+#define BGP4_MIB_NEGOTIATED_VER_NO_VALUE 0
+
+/* values for BGP4-MIB::bgpPeerAdminStatus */
+enum bgp4_admin_status {
+  BGP4_ADMIN_STOP = 1,
+  BGP4_ADMIN_START = 2,
+};
+
+void snmp_bgp4_start(struct snmp_proto *p, int with_mib);
+void snmp_bgp4_register(struct snmp_proto *p);
+void snmp_bgp4_show_info(struct snmp_proto *p);
+
+enum snmp_search_res snmp_bgp_search(struct snmp_proto *p, struct agentx_varbind **vb_search, const struct oid *o_end, struct snmp_pdu *c);
+enum snmp_search_res snmp_bgp_search2(struct snmp_proto *p, struct oid **searched, const struct oid *o_end, uint contid);
+void snmp_bgp_fill(struct snmp_proto *p, struct agentx_varbind **vb, struct snmp_pdu *c);
+//int snmp_bgp_testset(struct snmp_proto *p, const struct agentx_varbind *vb, void* tr, struct oid *oid, uint pkt_type);
+
+void snmp_bgp_notify_established(struct snmp_proto *p, struct bgp_proto *bgp);
+void snmp_bgp_notify_backward_trans(struct snmp_proto *p, struct bgp_proto *bgp);
+
+enum bgp4_mib_rows {
+  BGP4_MIB_VERSION    = 1,
+  BGP4_MIB_LOCAL_AS   = 2,
+  BGP4_MIB_PEER_TABLE = 3,    /* subtable */
+  BGP4_MIB_IDENTIFIER = 4,    /* BGP4-MIB::bgpIdentifier local router id */
+};
+
+enum bgp4_mib_peer_table_rows {
+  BGP4_MIB_PEER_ENTRY = 1,
+};
+
+/* valid values for BGP4_MIB_STATE */
+enum bgp4_mib_bgp_states {
+  BGP4_MIB_IDLE = 1,
+  BGP4_MIB_CONNECT = 2,
+  BGP4_MIB_ACTIVE = 3,
+  BGP4_MIB_OPENSENT = 4,
+  BGP4_MIB_OPENCONFIRM = 5,
+  BGP4_MIB_ESTABLISHED = 6,
+};
+
+STATIC_ASSERT(BGP4_MIB_IDLE == BS_IDLE + 1);
+STATIC_ASSERT(BGP4_MIB_CONNECT == BS_CONNECT + 1);
+STATIC_ASSERT(BGP4_MIB_ACTIVE == BS_ACTIVE + 1);
+STATIC_ASSERT(BGP4_MIB_OPENSENT == BS_OPENSENT + 1);
+STATIC_ASSERT(BGP4_MIB_OPENCONFIRM == BS_OPENCONFIRM + 1);
+STATIC_ASSERT(BGP4_MIB_ESTABLISHED == BS_ESTABLISHED + 1);
+
+/* Traps OID sub-identifiers */
+enum bgp4_traps_subids {
+  BGP4_MIB_ESTABLISHED_NOTIFICATION = 1,
+  BGP4_MIB_BACKWARD_TRANS_NOTIFICATION = 2,
+};
+
+#endif
diff --git a/proto/snmp/config.Y b/proto/snmp/config.Y
new file mode 100644 (file)
index 0000000..4fe8870
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ *     BIRD -- Statistics Protocol Configuration
+ *
+ *      (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ *      (c) 2022 CZ.NIC z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/snmp/snmp.h"
+#include "proto/snmp/subagent.h"
+
+CF_DEFINES
+
+#define SNMP_CFG ((struct snmp_config *) this_proto)
+
+CF_DECLS
+
+CF_KEYWORDS(SNMP, PROTOCOL, LOCAL, AS, REMOTE, ADDRESS, PORT, DESCRIPTION,
+           TIMEOUT, PRIORITY, CONTEXT, DEFAULT, MESSAGE, VERBOSE, AGENTX,
+           SUBAGENT, MASTER, BGP4, MIB, REGISTRATION, PEER)
+
+CF_GRAMMAR
+
+proto: snmp_proto ;
+
+snmp_proto:
+   snmp_proto_start proto_name '{' snmp_proto_opts '}' ;
+
+snmp_proto_item:
+   proto_item
+ | bgp4_mib
+ | SOURCE ADDRESS ipa { SNMP_CFG->local_ip = $3; }
+ | AGENTX MASTER ADDRESS DEFAULT {
+    if (SNMP_CFG->trans_type != SNMP_TRANS_DEFAULT)
+      cf_error("Duplicit option remote address");
+  }
+ | AGENTX MASTER ADDRESS text {
+    if (SNMP_CFG->trans_type != SNMP_TRANS_DEFAULT)
+      cf_error("Duplicit option remote address");
+
+    if (strcmp($4, agentx_master_addr)) {
+      SNMP_CFG->master_path = $4;
+      SNMP_CFG->trans_type = SNMP_TRANS_UNIX;
+    }
+  }
+ | AGENTX MASTER ADDRESS ipa {
+    if (SNMP_CFG->trans_type != SNMP_TRANS_DEFAULT)
+      cf_error("Duplicit option agentx master address");
+
+    SNMP_CFG->master_ip = $4;
+    SNMP_CFG->trans_type = SNMP_TRANS_TCP;
+  }
+ | AGENTX MASTER ADDRESS ipa PORT expr {
+    if (SNMP_CFG->trans_type != SNMP_TRANS_DEFAULT)
+      cf_error("Duplicit option agentx master address");
+
+    if (($6 < 1) || ($6 > UINT16_MAX)) cf_error("Invalid port number");
+
+    SNMP_CFG->master_ip = $4;
+    SNMP_CFG->trans_type = SNMP_TRANS_TCP;
+    SNMP_CFG->master_port = $6;
+  }
+ | SUBAGENT DESCRIPTION text {
+    if (strlen($3) > UINT16_MAX - 1) cf_error("Description is too long");
+    SNMP_CFG->description = $3;
+  }
+ | REGISTRATION PRIORITY expr {
+    if ($3 > 255) cf_error("Registration priority must be in range 0-255");
+    SNMP_CFG->priority = $3;
+  }
+ | MESSAGE TIMEOUT expr_us {
+    /* TODO */
+    if ($3 TO_S > 255 || $3 TO_S < 1)
+      log(L_WARN "resolution of AgentX message timeout is from 1 to 255 s.");
+    if (($3 TO_S) - (int)($3 TO_S) > 0)
+      log(L_WARN "AgentX message timeout cannot use second fraction, "
+       "will use rounded up value.");
+    SNMP_CFG->timeout = $3;
+  }
+/*
+ | ERROR TIMEOUT expr_us {
+    / * TODO * /
+    SNMP_CFG->error_timeout = $3;
+  }
+ | RETRY TIME expr_us {
+    / * TODO * /
+    SNMP_CFG->retry_timeout = $3;
+  }
+*/
+ | START DELAY TIME expr_us { SNMP_CFG->startup_delay = $4; }
+ | VERBOSE bool { SNMP_CFG->verbose = $2; }
+ ;
+
+bgp4_mib:
+   MIB BGP4 '{' bgp4_mib_items '}'
+ | BGP4 MIB '{' bgp4_mib_items '}'
+ ;
+
+bgp4_mib_items:
+   bgp4_mib_items_opt bgp4_mib_as bgp4_mib_items_opt bgp4_mib_id bgp4_mib_items_opt
+ ;
+
+bgp4_mib_as:
+  LOCAL AS expr ';' {
+    if ($3 < 1 || $3 > UINT16_MAX) cf_error("Invalid local AS for BGP4-MIB");
+    SNMP_CFG->bgp4_local_as = $3;
+  }
+  /* TODO add option to follow some bgp peer local as */
+ ;
+
+bgp4_mib_id:
+   LOCAL ROUTER ID idval ';' { SNMP_CFG->bgp4_local_id = $4; }
+  /* TODO add option to inherit global router id, or follow some bgp peer */
+ ;
+
+bgp4_mib_items_opt:
+   /* empty */
+ | bgp4_mib_items_opt bgp4_mib_peer ';'
+ ;
+
+bgp4_mib_peer: PEER symbol
+{
+  /* the snmp_context rule sets the correct value of this_bond */
+  cf_assert_symbol($2, SYM_PROTO);
+
+  if (!$2->proto) cf_error("BGP protocol %s not found", $2->name);
+
+  cf_assert($2->proto->protocol == &proto_bgp,
+    "SNMP BGP bond accepts only BGP protocols");
+
+  struct snmp_bond *this_bond = cfg_alloc(sizeof(struct snmp_bond));
+  this_bond->type = SNMP_BGP;
+  this_bond->config = $2->proto;
+
+  add_tail(&SNMP_CFG->bgp_entries, &this_bond->n);
+  SNMP_CFG->bonds++;
+}
+
+snmp_proto_opts:
+   /* empty */
+ | snmp_proto_opts snmp_proto_item ';'
+ ;
+
+snmp_proto_start: proto_start SNMP
+{
+  this_proto = proto_config_new(&proto_snmp, $1);
+
+  init_list(&SNMP_CFG->bgp_entries);
+  SNMP_CFG->bonds = 0;
+
+  SNMP_CFG->local_ip = IPA_NONE;
+  SNMP_CFG->master_ip = IPA_NONE;
+  SNMP_CFG->master_port = SNMP_PORT;
+  SNMP_CFG->master_path = agentx_master_addr;
+  SNMP_CFG->trans_type = SNMP_TRANS_DEFAULT;
+  SNMP_CFG->bgp4_local_as = 0;
+  SNMP_CFG->bgp4_local_id = 0;
+  SNMP_CFG->verbose = 0;
+
+  SNMP_CFG->description = "bird";
+  SNMP_CFG->timeout = 15;
+  SNMP_CFG->priority = AGENTX_PRIORITY;
+}
+
+
+CF_CODE
+
+CF_END
diff --git a/proto/snmp/mib_tree.c b/proto/snmp/mib_tree.c
new file mode 100644 (file)
index 0000000..b857cec
--- /dev/null
@@ -0,0 +1,877 @@
+#include "mib_tree.h"
+#include "snmp_utils.h"
+
+#ifdef allocz
+#undef allocz
+#endif
+
+#define alloc(size) mb_alloc(p, size)
+#define allocz(size) mb_allocz(p, size)
+#define free(ptr) mb_free(ptr)
+#define realloc(ptr, newsize) mib_mb_realloc(p, ptr, newsize)
+
+/*
+ * mib_mb_realloc - fix mb_realloc for NULL
+ * @p: pool to use for NULL pointers
+ * @ptr: old pointer to be reallocated
+ * @size: new size of allocated memory block
+ *
+ * The mb_realloc() does not work with NULL as ptr.
+ */
+static inline void *
+mib_mb_realloc(pool *p, void *ptr, unsigned size)
+{
+  if (!ptr)
+    return mb_alloc(p, size);
+
+  return mb_realloc(ptr, size);
+}
+
+/**
+ * mib_tree_init - Initialize a MIB tree
+ * @p: allocation source pool
+ * @t: pointer to a tree being initialized
+ *
+ * By default the standard SNMP internet prefix (.1.3.6.1) is inserted into the
+ * tree.
+ */
+void
+mib_tree_init(pool *p, struct mib_tree *t)
+{
+  struct mib_node *node = &t->root;
+  node->c.id = 0;
+  node->c.flags = 0;
+  node->children = NULL;
+  node->child_len = 0;
+
+  struct oid *oid = tmp_alloc(
+    snmp_oid_size_from_len((uint) ARRAY_SIZE(snmp_internet)));
+  oid->n_subid =  ARRAY_SIZE(snmp_internet);
+  oid->prefix = 0;
+  oid->include =  0;
+  oid->reserved = 0;
+
+  for (size_t i = 0; i < ARRAY_SIZE(snmp_internet); i++)
+    oid->ids[i] = snmp_internet[i];
+
+  (void) mib_tree_add(p, t, oid, 0);
+}
+
+/**
+ * mib_tree_hint - preallocate child array for given OID
+ * @p: memory pool to allocated from
+ * @t: MIB tree to hint
+ * @oid: MIB subtree root with upto @size children
+ * @size: number of children in @oid subtree
+ */
+int
+mib_tree_hint(pool *p, struct mib_tree *t, const struct oid *oid, uint size)
+{
+  mib_node_u *node = mib_tree_add(p, t, oid, 0);
+  if (!node || mib_node_is_leaf(node))
+    return 0;
+
+  struct mib_node *inner = &node->inner;
+  if (inner->child_len >= size + 1)
+    return 1;
+
+  u32 old_len = inner->child_len;
+  inner->child_len = size + 1;
+  inner->children = realloc(inner->children,
+    inner->child_len * sizeof(mib_node_u *));
+
+  for (u32 i = old_len; i < inner->child_len; i++)
+    inner->children[i] = NULL;
+  return 1;
+}
+
+/**
+ * mib_tree_add - Insert a new node to the tree
+ * @p: allocation source pool
+ * @t: MIB tree to insert to
+ * @oid: identification of inserted node.
+ * @is_leaf: flag signaling that inserted OID should be leaf node.
+ *
+ * Reinsertion only return already valid node pointer, no allocations are done
+ * in this case. Return pointer to node in the MIB tree @t or NULL if the
+ * requested insertion is invalid. Insertion is invalid if we want to insert
+ * node below a leaf or insert a leaf in place taken by normal node.
+ *
+ */
+mib_node_u *
+mib_tree_add(pool *p, struct mib_tree *t, const struct oid *oid, int is_leaf)
+{
+  // TODO may not function properly in snmp_internet prefix region
+  struct mib_walk_state walk;
+  mib_node_u *node;
+
+  /* The empty prefix is associated with the root tree node */
+  if (snmp_is_oid_empty(oid) && !is_leaf)
+    return (mib_node_u *) &t->root;
+  else if (snmp_is_oid_empty(oid))
+    return NULL;
+
+  mib_tree_walk_init(&walk, t);
+  node = mib_tree_find(t, &walk, oid);
+  ASSERT(walk.id_pos <= oid->n_subid + 1);
+
+  if (node)
+  {
+    if (mib_node_is_leaf(node) == is_leaf)
+      return node;
+
+    /* we are trying to insert a leaf node in place of inner node,
+     * or vice versa */
+    return NULL;
+  }
+
+  ASSERT(walk.id_pos < oid->n_subid + 1);
+
+  node = walk.stack[walk.stack_pos - 1];
+  /* we encounter leaf node before end of OID's id path */
+  if (mib_node_is_leaf(node))
+    return NULL;
+
+  struct mib_node *node_inner = &node->inner;
+  if (snmp_oid_is_prefixed(oid) &&
+      walk.stack_pos <= ARRAY_SIZE(snmp_internet) + 1)
+  {
+    ASSUME(walk.stack_pos && walk.stack[0] == (mib_node_u *) &t->root);
+
+    for (u32 id = walk.stack_pos - 1; id < ARRAY_SIZE(snmp_internet); id++)
+    {
+      if (snmp_internet[id] >= node_inner->child_len)
+      {
+       u32 old_len = node_inner->child_len;
+       node_inner->child_len = snmp_internet[id] + 1;
+       node_inner->children = realloc(node_inner->children,
+         node_inner->child_len * sizeof(mib_node_u *));
+
+       for (u32 i = old_len; i < node_inner->child_len; i++)
+         node_inner->children[i] = NULL;
+      }
+
+      node = allocz(sizeof(struct mib_node));
+      /* assign child into a parent's children array */
+      node_inner->children[snmp_internet[id]] = node;
+      node_inner = &node->inner;
+      node_inner->c.id = snmp_internet[id];
+      /* node_inner's fields c.flags, child_len, children defaults to zero or
+       * NULL respectively */
+      walk.stack[walk.stack_pos++] = node;
+    }
+
+    if (walk.stack_pos == ARRAY_SIZE(snmp_internet) + 1)
+    {
+      u32 old_len = node_inner->child_len;
+      node_inner->child_len = MAX(old_len, (u32) oid->prefix + 1);
+      node_inner->children = realloc(node_inner->children,
+       node_inner->child_len * sizeof(mib_node_u *));
+
+      for (u32 i = old_len; i < node_inner->child_len; i++)
+       node_inner->children[i] = NULL;
+
+      if (is_leaf && !oid->n_subid)
+      {
+       node = allocz(sizeof(struct mib_leaf));
+       node->empty.flags = MIB_TREE_LEAF;
+      }
+      else
+      {
+       node = allocz(sizeof(struct mib_node));
+       node->empty.flags = 0;
+      }
+
+      node->empty.id = oid->prefix;
+      /* add node into the parent's children array */
+      node_inner->children[oid->prefix] = node;
+      node_inner = &node->inner;
+      walk.stack[walk.stack_pos++] = node;
+    }
+  }
+
+  /* snmp_internet + 2 = empty + snmp_internet + prefix */
+  if (snmp_oid_is_prefixed(oid) &&
+      walk.stack_pos == ARRAY_SIZE(snmp_internet) + 2 &&
+      oid->n_subid == 0 &&
+      mib_node_is_leaf(node) == is_leaf)
+    return node;
+
+  if (mib_node_is_leaf(node))
+    return node;
+
+  u8 subids = oid->n_subid;
+  u32 old_len = node_inner->child_len;
+  u32 child_id = oid->ids[walk.id_pos];
+  node_inner->child_len = MAX(old_len, child_id + 1);
+  node_inner->children = realloc(node_inner->children,
+    node_inner->child_len * sizeof(mib_node_u *));
+
+  for (u32 i = old_len; i < node_inner->child_len; i++)
+    node_inner->children[i] = NULL;
+
+  struct mib_node *parent;
+  /* break to loop before last node in the oid */
+  for (; walk.id_pos < subids - 1;)
+  {
+    parent = node_inner;
+    node_inner = allocz(sizeof(struct mib_node));
+
+    parent->children[child_id] = (mib_node_u *) node_inner;
+    node_inner->c.id = child_id;
+
+    child_id = oid->ids[++walk.id_pos];
+
+    node_inner->child_len = child_id + 1;
+    node_inner->children = allocz(node_inner->child_len * sizeof(mib_node_u *));
+  }
+
+  parent = node_inner;
+  mib_node_u *last;
+  if (is_leaf)
+  {
+    last = allocz(sizeof(struct mib_leaf));
+    struct mib_leaf *leaf = &last->leaf;
+
+    parent->children[child_id] = (mib_node_u *) leaf;
+    leaf->c.id = child_id;
+
+    leaf->c.flags = MIB_TREE_LEAF;
+  }
+  else
+  {
+    last = allocz(sizeof(struct mib_node));
+    node_inner = &last->inner;
+
+    parent->children[child_id] = (mib_node_u *) node_inner;
+    node_inner->c.id = child_id;
+    /* fields c.flags, child_len and children are set by zeroed allocz() */
+  }
+
+  return last;
+}
+
+/**
+ * mib_tree_delete - delete a MIB subtree
+ * @t: MIB tree
+ * @walk: MIB tree walk state that specify the subtree
+ *
+ * Return number of nodes deleted in the subtree. It is possible to delete an empty
+ * prefix which leads to deletion of all nodes inside the MIB tree. Note that
+ * the empty prefix (tree root) node itself could be deleted therefore 0 may be
+ * returned in case of empty prefix deletion.
+ */
+int
+mib_tree_delete(struct mib_tree *t, struct mib_walk_state *walk)
+{
+  int deleted = 0;
+  if (!t)
+    return 0;
+
+  /* (walk->stack_pos < 2) It is impossible to delete root node */
+  if (!walk || walk->stack_pos == 0)
+    return 0;
+
+  if (walk->stack_pos == 1)
+  {
+    for (u32 child = 0; child < t->root.child_len; child++)
+    {
+      if (!t->root.children[child])
+       continue;
+
+      walk->stack_pos = 2;
+      walk->stack[0] = (mib_node_u*) &t->root;
+      walk->stack[1] = t->root.children[child];
+
+      deleted += mib_tree_delete(t, walk);
+    }
+
+    return deleted;
+  }
+
+  struct mib_node *parent = &walk->stack[walk->stack_pos - 2]->inner;
+  mib_node_u *node = walk->stack[walk->stack_pos - 1];
+
+  struct mib_walk_state delete = {
+    .id_pos = walk->id_pos,
+    .stack_pos = 2,
+    .stack = {
+      (mib_node_u *) parent,
+      node,
+      NULL,
+    },
+  };
+
+  u32 last_id = 0;
+  while (delete.stack_pos > 1)
+  {
+continue_while:          /* like outer continue, but skip always true condition */
+    parent = (struct mib_node *) delete.stack[delete.stack_pos - 2];
+
+    if (mib_node_is_leaf(node))
+    {
+      /* Free leaf node */
+      last_id = node->leaf.c.id;
+      parent->children[last_id] = NULL;
+      delete.stack[--delete.stack_pos] = NULL;
+      free(node);
+      deleted++;
+      node = delete.stack[delete.stack_pos - 1];
+      continue;          /* here, we couldn't skip the while condition */
+    }
+
+    struct mib_node *node_inner = &node->inner;
+    mib_node_u *child = NULL;
+    for (u32 id = last_id; id < node_inner->child_len; id++)
+    {
+      /* Recursively traverse child nodes */
+      child = node_inner->children[id];
+
+      if (!child)
+       continue;
+
+      last_id = 0;
+      delete.stack[delete.stack_pos++] = child;
+      parent = node_inner;
+      node = child;
+      goto continue_while;    /* outer continue */
+    }
+
+    /* Free inner node without any children */
+    last_id = node_inner->c.id;
+    parent->children[last_id] = NULL;
+    delete.stack[--delete.stack_pos] = NULL;
+    free(node_inner->children);
+    free(node_inner);
+    deleted++;
+    node = (mib_node_u *) parent;
+
+    /* skip check for deleted node in loop over children */
+    last_id++;
+  }
+
+  /* delete the node from original stack */
+  walk->stack[--walk->stack_pos] = NULL;
+
+  node = walk->stack[walk->stack_pos - 1];
+  struct mib_node *node_inner = &node->inner;
+  u32 id;
+  for (id = 0; id < node_inner->child_len; id++)
+  {
+    if (node_inner->children[id] != NULL)
+      break;
+  }
+
+  if (id == node_inner->child_len)
+  {
+    /* all the children are NULL */
+    free(node_inner->children);
+    node_inner->children = NULL;
+    node_inner->child_len = 0;
+  }
+
+  return deleted;
+}
+
+/**
+ * mib_tree_remove - delete a MIB subtree
+ * @t: MIB tree
+ * @oid: object identifier specifying the subtree
+ *
+ * This is a convenience wrapper around mib_tree_delete(). The mib_tree_remove()
+ * finds the corresponding node and deletes it. Return 0 if the OID was not
+ * found. Otherwise return number of deleted nodes (see mib_tree_delete() for
+ * more details).
+ */
+int
+mib_tree_remove(struct mib_tree *t, const struct oid *oid)
+{
+  struct mib_walk_state walk = { };
+  mib_node_u *node = mib_tree_find(t, &walk, oid);
+
+  if (!node)
+    return 0;
+
+  return mib_tree_delete(t, &walk);
+}
+
+/**
+ * mib_tree_find - Find a OID node in MIB tree
+ * @t: searched tree
+ * @walk: output search state
+ * @oid: searched node identification
+ *
+ * Return valid pointer to node in MIB tree or NULL. The search state @walk is
+ * always updated and contains the longest possible prefix of @oid present
+ * inside the tree @t. The @walk must not be NULL and must be blank (only
+ * initialized).
+ */
+mib_node_u *
+mib_tree_find(const struct mib_tree *t, struct mib_walk_state *walk, const struct oid *oid)
+{
+  ASSERT(t && walk);
+
+  if (!oid || snmp_is_oid_empty(oid))
+  {
+    walk->stack_pos = 1;
+    walk->stack[0] = (mib_node_u *) &t->root;
+    return (snmp_is_oid_empty(oid)) ? (mib_node_u *) &t->root : NULL;
+  }
+
+  mib_node_u *node;
+  struct mib_node *node_inner;
+
+  /* the OID id index to use */
+  u8 oid_pos = walk->id_pos;
+
+  if (walk->stack_pos > 0)
+    node = walk->stack[walk->stack_pos - 1];
+  else
+    node = walk->stack[walk->stack_pos++] = (mib_node_u *) &t->root;
+
+  if (mib_node_is_leaf(node))
+  {
+    /* In any of cases below we did not move in the tree therefore the
+     * walk->id_pos is left untouched. */
+    if (snmp_oid_is_prefixed(oid) &&
+       oid->n_subid + ARRAY_SIZE(snmp_internet) + 1 == walk->id_pos)
+      return node;
+
+    else if (snmp_oid_is_prefixed(oid) &&
+       oid->n_subid + ARRAY_SIZE(snmp_internet) + 1 > walk->id_pos)
+      return NULL;
+
+    else if (!snmp_oid_is_prefixed(oid) && oid->n_subid + 1 == walk->id_pos)
+      return node;
+  }
+
+  node_inner = &node->inner;
+  ASSERT(node); /* node may be leaf if OID is not in tree t */
+
+  /* Handling of prefixed OID */
+  if (snmp_oid_is_prefixed(oid) && walk->stack_pos < 6)
+  {
+    /* The movement inside implicit SNMP internet and following prefix is not
+     * projected to walk->id_pos. */
+    uint i = (uint) walk->stack_pos - 1;
+    /* walking the snmp_internet prefix itself */
+    for (; i < ARRAY_SIZE(snmp_internet); i++)
+    {
+      if (node_inner->child_len <= snmp_internet[i])
+       return NULL;
+
+      node = node_inner->children[snmp_internet[i]];
+      node_inner = &node->inner;
+
+      if (!node)
+       return NULL;
+
+      ASSERT(node->empty.id == snmp_internet[i]);
+      walk->stack[walk->stack_pos++] = node;
+
+      if (mib_node_is_leaf(node))
+       return NULL;
+    }
+
+    /* walking the prefix continuation (OID field oid->prefix) */
+    u8 prefix = oid->prefix;
+    if (node_inner->child_len <= prefix)
+      return NULL;
+
+    node = node_inner->children[prefix];
+    node_inner = &node->inner;
+
+    if (!node)
+      return NULL;
+
+    ASSERT(node->empty.id == prefix);
+    walk->stack[walk->stack_pos++] = node;
+
+    if (mib_node_is_leaf(node) && oid->n_subid > 0)
+      return NULL;
+  }
+
+  u8 subids = oid->n_subid;
+  if (subids == 0)
+    return (node == (mib_node_u *) &t->root) ? NULL : node;
+
+  /* loop for all OID's ids except the last one */
+  for (; oid_pos < subids - 1 && walk->stack_pos < MIB_WALK_STACK_SIZE + 1; oid_pos++)
+  {
+    u32 id = oid->ids[oid_pos];
+    if (node_inner->child_len <= id)
+    {
+      /* The walk->id_pos points after the last accepted OID id.
+       * This is correct because we did not find the last OID in the tree. */
+      walk->id_pos = oid_pos;
+      return NULL;
+    }
+
+    node = node_inner->children[id];
+    node_inner = &node->inner;
+
+    if (!node)
+    {
+      /* Same as above, the last node is not valid therefore the walk->is_pos
+       * points after the last accepted OID id. */
+      walk->id_pos = oid_pos;
+      return NULL;
+    }
+
+    ASSERT(node->empty.id == id);
+    walk->stack[walk->stack_pos++] = node;
+
+    if (mib_node_is_leaf(node))
+    {
+      /* We need to increment the oid_pos because the walk->is_pos suppose the
+       * pointer after the last valid OID id. */
+      walk->id_pos = ++oid_pos;
+      return NULL;
+    }
+  }
+
+  walk->id_pos = oid_pos;
+  u32 last_id = oid->ids[oid_pos];
+  if (node_inner->child_len <= last_id ||
+      walk->stack_pos >= MIB_WALK_STACK_SIZE + 1)
+    return NULL;
+
+  node = node_inner->children[last_id];
+  node_inner = &node->inner;
+
+  if (!node)
+    return NULL;
+
+  /* here, the check of node being a leaf is intentionally omitted
+   * because we may need to search for a inner node */
+  ASSERT(node->empty.id == last_id);
+
+  /* We need to increment the oid_pos because the walk->is_pos suppose the
+   * pointer after the last valid OID id. */
+  walk->id_pos = ++oid_pos;
+  return walk->stack[walk->stack_pos++] = node;
+}
+
+/**
+ * mib_tree_walk_init - Initialize MIB tree walk state
+ * @walk: MIB tree walk state to init
+ * @t: optional MIB tree
+ */
+void
+mib_tree_walk_init(struct mib_walk_state *walk, const struct mib_tree *t)
+{
+  walk->id_pos = 0;
+  walk->stack_pos = (t != NULL) ? 1 : 0;
+  memset(&walk->stack, 0, sizeof(walk->stack));
+
+  if (t != NULL)
+    walk->stack[0] = (mib_node_u *) &t->root;
+}
+
+/*
+ * walk_is_prefixable - test prefixability of MIB walk state
+ * @walk: MIB tree walk state to use
+ *
+ * This function is functionally equivalent to snmp_oid_is_prefixable().
+ */
+static inline int
+walk_is_prefixable(const struct mib_walk_state *walk)
+{
+  /* empty prefix and oid->prefix (+2) */
+  if (walk->stack_pos < ARRAY_SIZE(snmp_internet) + 2)
+    return 0;
+
+  for (uint i = 0; i < ARRAY_SIZE(snmp_internet); i++)
+  {
+    if (walk->stack[i + 1]->empty.id != snmp_internet[i])
+      return 0;
+  }
+
+  u32 id = walk->stack[ARRAY_SIZE(snmp_internet) + 1]->empty.id;
+  return id > 0 && id <= UINT8_MAX;
+}
+
+/*
+ * mib_tree_walk_to_oid - retrieve OID from MIB tree walk state
+ * @walk: MIB tree walk state to transform
+ * @result: destination OID
+ * @subids: Maximal number of subids in available space
+ *
+ * Return 1 if the space is insufficient, 0 otherwise.
+ */
+int
+mib_tree_walk_to_oid(const struct mib_walk_state *walk, struct oid *result, u32 subids)
+{
+  ASSERT(walk && result);
+
+  /* the stack_pos point after last valid index, and the first is always empty
+   * prefix */
+  if (walk->stack_pos <= 1)
+  {
+    /* create a null valued OID; sets all n_subid, prefix, include and reserved */
+    memset(result, 0, sizeof(struct oid));
+    return 0;
+  }
+
+  u32 index;
+  if (walk_is_prefixable(walk))
+  {
+    if (walk->stack_pos - 2 > subids - (ARRAY_SIZE(snmp_internet) + 1))
+      return 1;
+
+    /* skip empty prefix, whole snmp_internet .1.3.6.1 and oid->prefix */
+    index = 2 + ARRAY_SIZE(snmp_internet);
+    result->n_subid = walk->stack_pos - (ARRAY_SIZE(snmp_internet) + 2);
+    result->prefix = \
+      walk->stack[ARRAY_SIZE(snmp_internet) + 1]->empty.id;
+  }
+  else
+  {
+    if (walk->stack_pos - 2 > subids)
+      return 1;
+
+    index = 1; /* skip empty prefix */
+    result->n_subid = walk->stack_pos - 1;
+    result->prefix = 0;
+  }
+
+  result->include = 0;
+  result->reserved = 0;
+
+  u32 i = 0;
+  /* the index could point after last stack array element */
+  for (; index < walk->stack_pos && index < MIB_WALK_STACK_SIZE; index++)
+    result->ids[i++] = walk->stack[index]->empty.id;
+
+  return 0;
+}
+
+/*
+ * mib_tree_walk_oid_compare - compare MIB tree walk state and OID
+ * @walk: left relation operand
+ * @oid: Object Identifier in cpu native byte order
+ *
+ * Return value semantics is the same as snmp_oid_compare().
+ */
+int
+mib_tree_walk_oid_compare(const struct mib_walk_state *walk, const struct oid *oid)
+{
+  /* code is very similar to snmp_oid_compare() */
+  if (!walk->stack_pos)
+    return -1;
+
+  uint walk_idx = 1;
+  u8 walk_subids = walk->stack_pos;      /* left_subids */
+  u8 oid_subids = oid->n_subid;  /* right_subids */
+
+  const u8 oid_prefix = oid->prefix;
+
+  if (oid_prefix != 0)
+  {
+    for (; walk_idx < walk_subids && walk_idx < ARRAY_SIZE(snmp_internet) + 1; walk_idx++)
+    {
+      u32 id = walk->stack[walk_idx]->empty.id;
+      if (id < snmp_internet[walk_idx - 1])
+       return -1;
+      else if (id > snmp_internet[walk_idx - 1])
+       return 1;
+    }
+
+    if (walk_idx < ARRAY_SIZE(snmp_internet) + 1)
+      return -1;
+
+    const u8 walk_prefix = walk->stack[walk_idx++]->empty.id;
+    if (walk_prefix < oid_prefix)
+      return -1;
+    else if (walk_prefix > oid_prefix)
+      return 1;
+  }
+
+  uint i = 0;
+  for (; i < oid_subids && walk_idx < walk_subids; i++, walk_idx++)
+  {
+    u32 walk_id = walk->stack[walk_idx]->empty.id;
+    u32 oid_id = oid->ids[i];
+    if (walk_id < oid_id)
+      return -1;
+    else if (walk_id > oid_id)
+      return 1;
+  }
+
+  if (walk_idx == walk_subids && i == oid_subids)
+    return 0;
+  else if (walk_idx == walk_subids)
+    return -1;
+  else /* if (i == oid_subids) */
+    return 1;
+}
+
+
+
+/**
+ * mib_tree_walk_is_oid_descendant - check if OID is in walk subtree
+ * @walk: MIB tree walk state
+ * @oid: OID to use
+ *
+ * Return 0 if @walk specify same path in MIB tree as @oid, return +1 if @oid is
+ * in @walk subtree, return -1 otherwise.
+ */
+int
+mib_tree_walk_is_oid_descendant(const struct mib_walk_state *walk, const struct oid *oid)
+{
+  /* walk stack index skipped zero prefix and OID subidentifier index */
+  u32 i = 1, j = 0;
+
+  if (!walk->stack_pos && snmp_is_oid_empty(oid))
+    return 0;
+
+  if (snmp_oid_is_prefixed(oid))
+  {
+    for (; i < MIN(walk->stack_pos - 1, ARRAY_SIZE(snmp_internet) + 1); i++)
+    {
+      if (walk->stack[i]->empty.id != snmp_internet[i - 1])
+       return -1;
+    }
+
+    if (i == walk->stack_pos)
+      return +1;
+
+    if (i < walk->stack_pos &&
+       walk->stack[i]->empty.id != (u32) oid->prefix)
+      return -1;
+
+    i++;
+  }
+
+  u32 ids = oid->n_subid;
+  for (; i < walk->stack_pos && j < ids; i++, j++)
+  {
+    if (walk->stack[i]->empty.id != oid->ids[j])
+      return -1;
+  }
+
+  if (i < walk->stack_pos)
+    return -1;
+  else if (i == walk->stack_pos && j == ids)
+    return 0;
+  else if (i == walk->stack_pos)
+    return +1;
+  else
+  {
+    die("unreachable");
+    return -1;
+  }
+}
+
+/**
+ * mib_tree_walk_next - find MIB tree node successor in lexicagraphically ordered MIB tree
+ * @t: MIB tree to use
+ * @walk: MIB tree walk state to use
+ */
+mib_node_u *
+mib_tree_walk_next(const struct mib_tree *t, struct mib_walk_state *walk)
+{
+  ASSERT(t && walk);
+
+  u32 next_id = 0;
+
+  if (walk->stack_pos == 0)
+    return NULL;
+
+  mib_node_u *node = walk->stack[walk->stack_pos - 1];
+
+  if (mib_node_is_leaf(node))
+  {
+    next_id = node->leaf.c.id + 1;
+    walk->stack[--walk->stack_pos] = NULL;
+    node = walk->stack[walk->stack_pos - 1];
+  }
+
+  while (walk->stack_pos > 0)
+  {
+    node = walk->stack[walk->stack_pos - 1];
+
+    if (mib_node_is_leaf(node))
+    {
+      walk->stack[walk->stack_pos++] = node;
+      return node;
+    }
+
+    struct mib_node *node_inner = &node->inner;
+    for (u32 id = next_id; id < node_inner->child_len; id++)
+    {
+      mib_node_u *child = node_inner->children[id];
+
+      if (!child)
+       continue;
+
+      walk->stack[walk->stack_pos++] = child;
+      return child;
+    }
+
+    next_id = node_inner->c.id + 1;
+    walk->stack[--walk->stack_pos] = NULL;
+  }
+
+  return NULL;
+}
+
+/**
+ * mib_tree_walk_next_leaf - wrapper around mib_tree_walk_next returning only leafs
+ * @t: MIB tree to use
+ * @walk: MIB tree walk state
+ * @skip: lower value bound for next OID id
+ */
+struct mib_leaf *
+mib_tree_walk_next_leaf(const struct mib_tree *t, struct mib_walk_state *walk, u32 skip)
+{
+  (void)t;
+
+  if (walk->stack_pos == 0)
+    return NULL;
+
+  u32 next_id = skip;
+  mib_node_u *node = walk->stack[walk->stack_pos - 1];
+
+  if (mib_node_is_leaf(node) && walk->stack_pos > 1)
+  {
+    next_id = node->leaf.c.id + 1;
+    walk->stack[--walk->stack_pos] = NULL;
+    node = walk->stack[walk->stack_pos - 1];
+  }
+  else if (mib_node_is_leaf(node))
+  {
+    /* walk->stack_pos == 1, so we NULL out the last stack field */
+    walk->stack[--walk->stack_pos] = NULL;
+    return NULL;
+  }
+
+  while (walk->stack_pos > 0)
+  {
+continue_while:
+    node = walk->stack[walk->stack_pos - 1];
+
+    if (mib_node_is_leaf(node))
+      return (struct mib_leaf *) node;
+
+    struct mib_node *node_inner = &node->inner;
+    for (u32 id = next_id; id < node_inner->child_len; id++)
+    {
+      mib_node_u *child = node_inner->children[id];
+
+      if (!child)
+       continue;
+
+      next_id = 0;
+      walk->stack[walk->stack_pos++] = child;
+      /* node is assign at the beginning of the while loop (from stack) */
+      goto continue_while;
+    }
+
+    next_id = node->empty.id + 1;
+    walk->stack[--walk->stack_pos] = NULL;
+  }
+
+  return NULL;
+}
+
diff --git a/proto/snmp/mib_tree.h b/proto/snmp/mib_tree.h
new file mode 100644 (file)
index 0000000..8978daa
--- /dev/null
@@ -0,0 +1,122 @@
+#ifndef _BIRD_SNMP_MIB_TREE_
+#define _BIRD_SNMP_MIB_TREE_
+
+#include "subagent.h"
+
+#include "lib/resource.h"
+#include "lib/lists.h"
+#include "lib/birdlib.h"
+
+#define MIB_TREE_NO_FLAGS 0x00
+#define MIB_TREE_LEAF 0x01
+#define MIB_TREE_HAS_HOOKS 0x02
+
+typedef union mib_node_union mib_node_u;
+
+struct mib_node_core {
+  u32 id;
+  u8 flags;
+};
+
+struct mib_node {
+  struct mib_node_core c;
+  mib_node_u **children;
+  u32 child_len;
+};
+
+struct mib_walk_state;
+
+struct mib_leaf {
+  struct mib_node_core c;
+
+  /**
+   * filler - hook for filling VarBind data value
+   * @state: self referencing MIB tree walk state
+   * @data: box holding destiantion VarBind and SNMP protocol instance
+   *
+   * If corresponding leaf node has filled in AgentX type and/or size, it is
+   * guaranteed that PDU buffer have enough space. Hook mustn't be NULL.
+   * If the leaf node has set valid type, the varbind type will be automatically
+   * set by the snmp_walk_fill() servicing routine. If the field type is set to
+   * AGENTX_INVALID, it is expected that filler() hook will also fill
+   * the VarBind type.
+   */
+  enum snmp_search_res (*filler)(struct mib_walk_state *state, struct snmp_pdu *context);
+
+  /**
+   * call_next - signal multileaf
+   * @state: self referencing MIB tree walk state
+   * @data: box holding destination VarBind and SNMP protocol insntace
+   *
+   * MIB modules can implement subtrees by a single leaf node in MIB node tree.
+   * When the tree is walked, the specific leaf node has to be returned multiple
+   * times. The @call_next hook determines if we should move to next leaf node.
+   * It is expected that call_next() hook may change the VarBind to be filled.
+   *
+   * Hook may be NULL meaning the leaf node is not multileaf/subtree.
+   *
+   */
+  int (*call_next)(struct mib_walk_state *state, struct snmp_pdu *context);
+
+  /**
+   * type of produced VarBind, may be replaced in packet instanciation by
+   * AGENTX_NO_SUCH_OBJECT, AGENTX_NO_SUCH_INSTANCE or AGENTX_END_OF_MIB_VIEW
+   * The field is unspecified if equal to AGENTX_INVALID.
+   */
+  enum agentx_type type;
+
+  /*
+   * Specify upper bound of VarBind data size. If set to -1, all handling must
+   * be done in filler() hook. In all other cases the filler() hook has
+   * guaranteed that the space is available.
+   */
+  int size;
+};
+
+union mib_node_union {
+  struct mib_node_core empty;
+  struct mib_node inner;
+  struct mib_leaf leaf;
+};
+
+/*
+ * The stack size include empty prefix (mib tree root).
+ */
+#define MIB_WALK_STACK_SIZE 33
+STATIC_ASSERT(OID_MAX_LEN < MIB_WALK_STACK_SIZE);
+
+/* walk state for MIB tree */
+struct mib_walk_state {
+  u8 id_pos;  /* points after last matching subid in OID */
+  u32 stack_pos;  /* points after last valid stack node */
+  mib_node_u *stack[MIB_WALK_STACK_SIZE];
+};
+
+struct mib_tree {
+  struct mib_node root;
+};
+
+void mib_tree_init(pool *p, struct mib_tree *t);
+void mib_tree_walk_init(struct mib_walk_state *state, const struct mib_tree *t);
+int mib_tree_walk_to_oid(const struct mib_walk_state *state, struct oid *result, u32 subids);
+int mib_tree_walk_oid_compare(const struct mib_walk_state *state, const struct oid *oid);
+
+mib_node_u *mib_tree_add(pool *p, struct mib_tree *tree, const struct oid *oid, int is_leaf);
+int mib_tree_remove(struct mib_tree *t, const struct oid *oid);
+int mib_tree_delete(struct mib_tree *t, struct mib_walk_state *state);
+mib_node_u *mib_tree_find(const struct mib_tree *tree, struct mib_walk_state *walk, const struct oid *oid);
+mib_node_u *mib_tree_walk_next(const struct mib_tree *t, struct mib_walk_state *walk);
+struct mib_leaf *mib_tree_walk_next_leaf(const struct mib_tree *t, struct mib_walk_state *walk, u32 skip);
+
+int mib_tree_hint(pool *p, struct mib_tree *t, const struct oid *oid, uint size);
+int mib_tree_walk_is_oid_descendant(const struct mib_walk_state *walk, const struct oid *oid);
+
+static inline int
+mib_node_is_leaf(const mib_node_u *node)
+{
+  ASSUME(node);
+  return node->empty.flags & MIB_TREE_LEAF;
+}
+
+#endif
+
diff --git a/proto/snmp/snmp.c b/proto/snmp/snmp.c
new file mode 100644 (file)
index 0000000..2e48f77
--- /dev/null
@@ -0,0 +1,686 @@
+/*
+ *     BIRD -- Simple Network Management Procotol (SNMP)
+ *
+ *     (c) 2024 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ *     (c) 2024 CZ.NIC z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Simple Network Management Protocol
+ *
+ * The SNMP protocol is divided into several parts: |snmp.c| which implements
+ * integration with BIRD core, |subagent.c| provides AgentX subagent behaviour
+ * as well as functions for creating and parsing packets. In file |mib_tree.c|
+ * is implemented OID prefix tree for storing supported MIBs. File |bgp4_mib.c|
+ * implements parts of BGP4-MIB, |snmp_utils.c| is collection of helper
+ * functions for whole SNMP protocol.
+ *
+ * Althrough called SNMP the BIRD does not implement SNMP directly but acts as
+ * an AgentX subagent. AgentX subagent connects to AgentX master agent that
+ * processes incomming SNMP requests and passes them down to the correct
+ * subagent. Therefore you need also a running master agent somewhere.
+ * Advantages of this design are that you are capable of doing aggregation of
+ * statuses of multiple BIRDs at the master agent level and much simpler
+ * implementation.
+ *
+ * Before any of the SNMP request could be processed, the SNMP need to
+ * established AgentX session with the master agent and need to register all
+ * subtrees to make them accessible from the master agent. The establishement of
+ * the of session is handled by snmp_start(), snmp_start_locked() and
+ * snmp_start_subagent(). Then we register all MIBs from configuration in
+ * snmp_register_mibs().
+ *
+ * The AgentX request are handled only during MIB subtree registrations and
+ * after then on established session (in states SNMP_REGISTER and SNMP_CONN, see
+ * below). It is also guaranteed that no request is received before MIB subtree
+ * registration because the specific subagent is not authoratitave and also the
+ * master agent has no info about MIB subtree supported by subagent. The AgentX
+ * requests are handled by function snmp_rx() in |subagent.c|.
+ *
+ *
+ *
+ */
+
+/*
+ * SNMP State Machine
+ *
+ *  States with main transitions
+ *
+ *
+ *    +-----------------+
+ *    | SNMP_INIT      |     entry state after call snmp_start()
+ *    +-----------------+
+ *       |
+ *       |   acquiring object lock for tcp communication socket
+ *       V
+ *    +-----------------+
+ *    | SNMP_LOCKED    |     object lock aquired
+ *    +-----------------+
+ *       |
+ *       |   opening communication socket
+ *       V
+ *    +-----------------+
+ *    | SNMP_OPEN      |     socket created, starting subagent
+ *    +-----------------+
+ *       |
+ *       |   BIRD receive response for agentx-Open-PDU
+ *       V
+ *    +-----------------+
+ *    | SNMP_REGISTER   |     session was established, subagent registers MIBs
+ *    +-----------------+
+ *       |
+ *       |   subagent received response for any registration requests
+ *       V
+ *    +-----------------+
+ *    | SNMP_CONN      |     everything is set
+ *    +-----------------+
+ *       |
+ *       |   received malformed PDU, protocol disabled,
+ *       |   BIRD sends agentx-Close-PDU or agentx-Response-PDU with an error
+ *       V
+ *    +-----------------+
+ *    | SNMP_STOP      |     waiting until the prepared PDUs are sent
+ *    +-----------------+
+ *       |
+ *       |   cleaning protocol state
+ *       V
+ *    +-----------------+
+ *    | SNMP_DOWN      |     session is closed
+ *    +-----------------+
+ *
+ *
+ *
+ *  Erroneous transitions:
+ *    SNMP is UP (PS_UP) in states SNMP_CONN and also in SNMP_REGISTER because
+ *    the session is establised and the GetNext request should be responsed
+ *    without regards to MIB registration.
+ *
+ *    Reconfiguration is done in similar fashion to BGP, the reconfiguration
+ *    request is declined, the protocols is stoped and started with new
+ *    configuration.
+ *
+ */
+
+#include "nest/bird.h"
+#include "nest/cli.h"
+#include "nest/locks.h"
+#include "lib/socket.h"
+#include "lib/lists.h"
+
+#include "snmp.h"
+#include "subagent.h"
+#include "snmp_utils.h"
+#include "mib_tree.h"
+#include "bgp4_mib.h"
+
+const char agentx_master_addr[] = AGENTX_MASTER_ADDR;
+
+static const char *snmp_state_str[] = {
+  [SNMP_INIT]    = "acquiring address lock",
+  [SNMP_LOCKED]          = "address lock acquired",
+  [SNMP_OPEN]    = "starting AgentX subagent",
+  [SNMP_REGISTER] = "registering MIBs",
+  [SNMP_CONN]    = "AgentX session established",
+  [SNMP_STOP]    = "stopping AgentX subagent",
+  [SNMP_DOWN]    = "protocol down",
+};
+
+
+/*
+ *    Callbacks
+ */
+
+/*
+ * snmp_sock_err - handle errors on socket by reopenning the socket
+ * @sk: socket owned by SNMP protocol instance
+ * @err: socket error code
+ */
+static void
+snmp_sock_err(sock *sk, int UNUSED err)
+{
+  struct snmp_proto *p = sk->data;
+  if (err != 0)
+    TRACE(D_EVENTS, "SNMP socket error (%d)", err);
+  snmp_set_state(p, SNMP_DOWN);
+}
+
+/*
+ * snmp_ping_timeout - send a agentx-Ping-PDU
+ * @tm: the ping_timer holding the SNMP protocol instance.
+ *
+ * Send an agentx-Ping-PDU. This function is periodically called by ping
+ * timer.
+ */
+static void
+snmp_ping_timeout(timer *tm)
+{
+  struct snmp_proto *p = tm->data;
+  snmp_ping(p);
+}
+
+
+/*
+ * snmp_stop_timeout - a timeout for non-responding master agent
+ * @tm: the startup_timer holding the SNMP protocol instance.
+ *
+ * We are trying to empty the TX buffer of communication socket. But if it is
+ * not done in reasonable amount of time, the function is called by timeout
+ * timer. We down the whole SNMP protocol with cleanup of associated data
+ * structures.
+ */
+static void
+snmp_stop_timeout(timer *tm)
+{
+  struct snmp_proto *p = tm->data;
+  snmp_set_state(p, SNMP_DOWN);
+}
+
+/*
+ * snmp_connected - start AgentX session on created socket
+ * @sk: socket owned by SNMP protocol instance
+ *
+ * Starts the AgentX communication by sending an agentx-Open-PDU.
+ * This function is internal and shouldn't be used outside the SNMP module.
+ */
+void
+snmp_connected(sock *sk)
+{
+  struct snmp_proto *p = sk->data;
+  snmp_set_state(p, SNMP_OPEN);
+}
+
+/*
+ * snmp_start_locked - open the socket on locked address
+ * @lock: object lock guarding the communication mean (address, ...)
+ *
+ * This function is called when the object lock is acquired. Main goal is to set
+ * socket parameters and try to open configured socket. Function
+ * snmp_connected() handles next stage of SNMP protocol start. When the socket
+ * coundn't be opened, a new try is scheduled after a small delay.
+ */
+static void
+snmp_start_locked(struct object_lock *lock)
+{
+  struct snmp_proto *p = lock->data;
+  if (p->startup_delay)
+  {
+    ASSERT(p->startup_timer);
+    p->startup_timer->hook = snmp_startup_timeout;
+    tm_start(p->startup_timer, p->startup_delay);
+  }
+  else
+    snmp_set_state(p, SNMP_LOCKED);
+}
+
+/*
+ * snmp_startup_timeout - start the initiliazed SNMP protocol
+ * @tm: the startup_timer holding the SNMP protocol instance.
+ *
+ * When the timer rings, the function snmp_startup() is invoked.
+ * This function is internal and shouldn't be used outside the SNMP module.
+ * Used when we delaying the start procedure, or we want to retry opening
+ * the communication socket.
+ */
+void
+snmp_startup_timeout(timer *tm)
+{
+  struct snmp_proto *p = tm->data;
+  snmp_set_state(p, SNMP_LOCKED);
+}
+
+/*
+ * snmp_rx_skip - skip all received data
+ * @sk: communication socket
+ * @size: size of received PDUs
+ *
+ * Socket rx_hook used when we are reseting the connection due to malformed PDU.
+ */
+static int
+snmp_rx_skip(sock UNUSED *sk, uint UNUSED size)
+{
+  return 1;
+}
+
+/*
+ * snmp_tx_skip - handle empty TX buffer during session reset
+ * @sk: communication socket
+ *
+ * The socket tx_hook is called when the TX buffer is empty, i.e. all data was
+ * send. This function is used only when we found malformed PDU and we are
+ * resetting the established session. If called, we perform a SNMP protocol
+ * state change.
+ */
+static void
+snmp_tx_skip(sock *sk)
+{
+  struct snmp_proto *p = sk->data;
+  snmp_set_state(p, SNMP_STOP);
+}
+
+/*
+ * snmp_cleanup - free all resources allocated by SNMP protocol
+ * @p: SNMP protocol instance
+ *
+ * This function forcefully stops and cleans all resources and memory acqiured
+ * by given SNMP protocol instance, such as timers, lists, hash tables etc.
+ */
+static inline void
+snmp_cleanup(struct snmp_proto *p)
+{
+  /* Function tm_stop() is called inside rfree() */
+  rfree(p->startup_timer);
+  p->startup_timer = NULL;
+
+  rfree(p->ping_timer);
+  p->ping_timer = NULL;
+
+  rfree(p->sock);
+  p->sock = NULL;
+
+  rfree(p->lock);
+  p->lock = NULL;
+
+  struct snmp_registration *r, *r2;
+  WALK_LIST_DELSAFE(r, r2, p->registration_queue)
+  {
+    rem_node(&r->n);
+    mb_free(r);
+    r = NULL;
+  }
+
+  HASH_FREE(p->bgp_hash);
+  rfree(p->lp);
+  p->lp = NULL;
+  /* bgp_trie is allocated exclusively from linpool lp */
+  p->bgp_trie = NULL;
+
+  struct mib_walk_state *walk = tmp_alloc(sizeof(struct mib_walk_state));
+  mib_tree_walk_init(walk, p->mib_tree);
+  (void) mib_tree_delete(p->mib_tree, walk);
+  p->mib_tree = NULL;
+
+  p->state = SNMP_DOWN;
+}
+
+/*
+ * snmp_set_state - change state with associated actions
+ * @p: SNMP protocol instance
+ * @state: new SNMP protocol state
+ *
+ * This function does not notify the bird about protocol state. Return current
+ * protocol state (PS_UP, ...).
+ */
+int
+snmp_set_state(struct snmp_proto *p, enum snmp_proto_state state)
+{
+  enum snmp_proto_state last = p->state;
+  const struct snmp_config *cf = (struct snmp_config *) p->p.cf;
+
+  p->state = state;
+
+  switch (state)
+  {
+  case SNMP_INIT:
+    /* We intentionally do not log anything */
+    ASSERT(last == SNMP_DOWN);
+
+    proto_notify_state(&p->p, PS_START);
+    if (cf->trans_type == SNMP_TRANS_TCP)
+    {
+      /* We need to lock the IP address */
+      struct object_lock *lock;
+      lock = p->lock = olock_new(p->pool);
+      lock->addr = p->master_ip;
+      lock->port = p->master_port;
+      lock->type = OBJLOCK_TCP;
+      lock->hook = snmp_start_locked;
+      lock->data = p;
+      olock_acquire(lock);
+      return PS_START;
+    }
+
+    last = SNMP_INIT;
+    p->state = state = SNMP_LOCKED;
+    /* Fall thru */
+
+  case SNMP_LOCKED:
+    TRACE(D_EVENTS, "SNMP Address lock acquired");
+    ASSERT(last == SNMP_INIT);
+    sock *s = sk_new(p->pool);
+
+    if (cf->trans_type == SNMP_TRANS_TCP)
+    {
+      s->type = SK_TCP_ACTIVE;
+      s->daddr = p->master_ip;
+      s->dport = p->master_port;
+      s->rbsize = SNMP_RX_BUFFER_SIZE;
+      s->tbsize = SNMP_TX_BUFFER_SIZE;
+    }
+    else
+    {
+      s->type = SK_UNIX_ACTIVE;
+      s->host = cf->master_path; /* daddr */
+      s->rbsize = SNMP_RX_BUFFER_SIZE;
+      s->tbsize = SNMP_TX_BUFFER_SIZE;
+    }
+
+    s->tx_hook = snmp_connected;
+    s->err_hook = snmp_sock_err;
+
+    p->sock = s;
+    s->data = p;
+
+    /* Try opening the socket, schedule a retry on fail */
+    if (sk_open(s) < 0)
+    {
+      TRACE(D_EVENTS, "SNMP Opening of communication socket failed");
+      rfree(s);
+      p->sock = NULL;
+      // TODO handle 0 timeout
+      tm_start(p->startup_timer, p->timeout);
+    }
+    return PS_START;
+
+  case SNMP_OPEN:
+    TRACE(D_EVENTS, "SNMP Communication socket opened, starting AgentX subagent");
+    ASSERT(last == SNMP_LOCKED);
+
+    p->sock->rx_hook = snmp_rx;
+    p->sock->tx_hook = NULL;
+
+    snmp_start_subagent(p);
+
+    p->startup_timer->hook = snmp_stop_timeout;
+    tm_start(p->startup_timer, 1 S);
+    return PS_START;
+
+  case SNMP_REGISTER:
+    TRACE(D_EVENTS, "SNMP Registering MIBs");
+    ASSERT(last == SNMP_OPEN);
+
+    tm_stop(p->startup_timer); /* stop timeout */
+
+    p->sock->rx_hook = snmp_rx;
+    p->sock->tx_hook = snmp_tx;
+
+    snmp_register_mibs(p);
+
+    // TODO timer for CONN
+
+    return PS_START;
+
+  case SNMP_CONN:
+    TRACE(D_EVENTS, "MIBs registered, AgentX session established");
+    ASSERT(last == SNMP_REGISTER);
+    proto_notify_state(&p->p, PS_UP);
+    return PS_UP;
+
+  case SNMP_STOP:
+    if (p->sock && p->state != SNMP_OPEN && !sk_tx_buffer_empty(p->sock))
+    {
+      TRACE(D_EVENTS, "SNMP Closing AgentX session");
+      if (p->state == SNMP_OPEN || p->state == SNMP_REGISTER ||
+         p->state == SNMP_CONN)
+       snmp_stop_subagent(p);
+
+      p->sock->rx_hook = snmp_rx_skip;
+      p->sock->tx_hook = snmp_tx_skip;
+
+      p->startup_timer->hook = snmp_stop_timeout;
+      tm_start(p->startup_timer, 150 MS);
+      proto_notify_state(&p->p, PS_STOP);
+      return PS_STOP;
+    }
+
+    p->state = state = SNMP_DOWN;
+    /* Fall thru */
+
+  case SNMP_DOWN:
+    TRACE(D_EVENTS, "SNMP AgentX session closed");
+    snmp_cleanup(p);
+    proto_notify_state(&p->p, PS_DOWN);
+    return PS_DOWN;
+
+  default:
+    die("unknown SNMP state transition");
+    return PS_DOWN;
+  }
+}
+
+/*
+ * snmp_reset - reset AgentX session
+ * @p: SNMP protocol instance
+ *
+ * We wait until the last PDU written into the socket is send while ignoring all
+ * incomming PDUs. Then we hard reset the connection by socket closure. The
+ * protocol instance is automatically restarted by nest.
+ *
+ * Return protocol state (PS_STOP, ...).
+ */
+int
+snmp_reset(struct snmp_proto *p)
+{
+  return snmp_set_state(p, SNMP_STOP);
+}
+
+/*
+ * snmp_up - AgentX session has registered all MIBs, protocols is up
+ * @p: SNMP protocol instance
+ */
+void
+snmp_up(struct snmp_proto *p)
+{
+  if (p->state == SNMP_REGISTER)
+    snmp_set_state(p, SNMP_CONN);
+}
+
+/*
+ * snmp_shutdown - Forcefully stop the SNMP protocol instance
+ * @P: SNMP protocol generic handle
+ *
+ * Simple cast-like wrapper around snmp_reset(), see more info there.
+ */
+static int
+snmp_shutdown(struct proto *P)
+{
+  struct snmp_proto *p = SKIP_BACK(struct snmp_proto, p, P);
+  return snmp_reset(p);
+}
+
+/*
+ * snmp_show_proto_info - print basic information about SNMP protocol instance
+ * @P: SNMP protocol generic handle
+ */
+static void
+snmp_show_proto_info(struct proto *P)
+{
+  struct snmp_proto *p = (void *) P;
+
+  cli_msg(-1006, "  SNMP state: %s", snmp_state_str[p->state]);
+  cli_msg(-1006, "  MIBs");
+
+  snmp_bgp4_show_info(p);
+}
+
+/*
+ * snmp_reconfigure_logic - find changes in configuration
+ * @p: SNMP protocol instance
+ * @new: new SNMP protocol configuration
+ *
+ * Return 1 if only minor changes have occured, 0 if we need full down-up cycle.
+ */
+static inline int
+snmp_reconfigure_logic(struct snmp_proto *p, const struct snmp_config *new)
+{
+  const struct snmp_config *old = SKIP_BACK(struct snmp_config, cf, p->p.cf);
+
+  if ((old->trans_type != SNMP_TRANS_TCP) && (new->trans_type == SNMP_TRANS_TCP)
+    || (old->trans_type == SNMP_TRANS_TCP) && (new->trans_type != SNMP_TRANS_TCP))
+    return 0;
+
+  if (old->trans_type == SNMP_TRANS_TCP &&
+      (ipa_compare(old->master_ip, new->master_ip)
+      || old->master_port != new->master_port))
+    return 0;
+
+  if (old->trans_type != SNMP_TRANS_TCP &&
+      bstrcmp(old->master_path, new->master_path))
+    return 0;
+
+  return (old->bgp4_local_id != new->bgp4_local_id
+      || old->bgp4_local_as != new->bgp4_local_as
+      || old->timeout != new->timeout  // TODO distinguish message timemout
+       //(Open.timeout and timeout for timer)
+      || old->priority != new->priority
+      || strncmp(old->description, new->description, UINT16_MAX - 1));
+}
+
+/*
+ * snmp_reconfigure - Indicate instance reconfigurability
+ * @P - SNMP protocol generic handle, current state
+ * @CF - SNMP protocol configuration generic handle carring new values
+ *
+ * We accept the reconfiguration if the new configuration @CF is identical with
+ * the currently deployed configuration. Otherwise we deny reconfiguration because
+ * the implementation would be cumbersome.
+ */
+static int
+snmp_reconfigure(struct proto *P, struct proto_config *CF)
+{
+  struct snmp_proto *p = SKIP_BACK(struct snmp_proto, p, P);
+  const struct snmp_config *new = SKIP_BACK(struct snmp_config, cf, CF);
+
+  /* We are searching for configuration changes */
+  int reconfigurable = snmp_reconfigure_logic(p, new);
+
+  if (reconfigurable)
+  {
+    /* copy possibly changed values */
+    p->startup_delay = new->startup_delay;
+    p->verbose = new->verbose;
+
+    ASSERT(p->ping_timer);
+    int active = tm_active(p->ping_timer);
+    rfree(p->ping_timer);
+    p->ping_timer = tm_new_init(p->pool, snmp_ping_timeout, p, p->timeout, 0);
+
+    if (active)
+      tm_start(p->ping_timer, p->timeout);
+
+    HASH_FREE(p->bgp_hash);
+    HASH_INIT(p->bgp_hash, p->pool, 10);
+
+    rfree(p->lp);
+    p->lp = lp_new(p->pool);
+    p->bgp_trie = f_new_trie(p->lp, 0);
+
+    /* We repopulate BGP related data structures (bgp_hash, bgp_trie). */
+    snmp_bgp4_start(p, 0);
+  }
+
+  return reconfigurable;
+}
+
+/*
+ * snmp_start - Initialize the SNMP protocol instance
+ * @P: SNMP protocol generic handle
+ *
+ * The first step in AgentX subagent startup is protocol initialition.
+ * We must prepare lists, find BGP peers and finally asynchronously start
+ * a AgentX subagent session.
+ */
+static int
+snmp_start(struct proto *P)
+{
+  struct snmp_proto *p = (void *) P;
+  struct snmp_config *cf = (struct snmp_config *) P->cf;
+
+  p->local_ip = cf->local_ip;
+  p->master_ip = cf->master_ip;
+  p->master_port = cf->master_port;
+  p->bgp4_local_as = cf->bgp4_local_as;
+  p->bgp4_local_id = cf->bgp4_local_id;
+  p->timeout = cf->timeout;
+  p->startup_delay = cf->startup_delay;
+  p->verbose = cf->verbose;
+
+  p->pool = p->p.pool;
+  p->lp = lp_new(p->pool);
+  p->bgp_trie = f_new_trie(p->lp, 0);
+  p->mib_tree = mb_alloc(p->pool, sizeof(struct mib_tree));
+
+  p->startup_timer = tm_new_init(p->pool, snmp_startup_timeout, p, 0, 0);
+  p->ping_timer = tm_new_init(p->pool, snmp_ping_timeout, p, p->timeout, 0);
+
+  init_list(&p->registration_queue);
+
+  /* We create copy of bonds to BGP protocols. */
+  HASH_INIT(p->bgp_hash, p->pool, 10);
+
+  mib_tree_init(p->pool, p->mib_tree);
+  snmp_bgp4_start(p, 1);
+
+  return snmp_set_state(p, SNMP_INIT);
+}
+
+/*
+ * snmp_init - preinitialize SNMP instance
+ * @CF: SNMP configuration generic handle
+ *
+ * Returns a generic handle pointing to preinitialized SNMP procotol
+ * instance.
+ */
+static struct proto *
+snmp_init(struct proto_config *CF)
+{
+  struct proto *P = proto_new(CF);
+  struct snmp_proto *p = SKIP_BACK(struct snmp_proto, p, P);
+
+  p->rl_gen = (struct tbf) TBF_DEFAULT_LOG_LIMITS;
+  p->state = SNMP_DOWN;
+
+  return P;
+}
+
+/*
+ * snmp_postconfig - Check configuration correctness
+ * @CF: SNMP procotol configuration generic handle
+ */
+static void
+snmp_postconfig(struct proto_config *CF)
+{
+  const struct snmp_config *cf  = (struct snmp_config *) CF;
+
+  /* Walk the BGP protocols and cache their references. */
+  if (cf->bgp4_local_as == 0)
+    cf_error("local as not specified");
+}
+
+
+/*
+ * Protocol infrastructure
+ */
+
+struct protocol proto_snmp = {
+  .name =              "SNMP",
+  .template =          "snmp%d",
+  .class =             PROTOCOL_SNMP,
+  .channel_mask =      0,
+  .proto_size =                sizeof(struct snmp_proto),
+  .config_size =       sizeof(struct snmp_config),
+  .postconfig =                snmp_postconfig,
+  .init =              snmp_init,
+  .start =             snmp_start,
+  .reconfigure =       snmp_reconfigure,
+  .show_proto_info =   snmp_show_proto_info,
+  .shutdown =          snmp_shutdown,
+};
+
+void
+snmp_build(void)
+{
+  proto_build(&proto_snmp);
+}
+
diff --git a/proto/snmp/snmp.h b/proto/snmp/snmp.h
new file mode 100644 (file)
index 0000000..06e90c6
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ *     BIRD -- Simple Network Management Protocol (SNMP)
+ *
+ *      (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ *      (c) 2022 CZ.NIC z.s.p.o
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_SNMP_H_
+#define _BIRD_SNMP_H_
+
+#include "nest/bird.h"
+#include "nest/protocol.h"
+#include "lib/resource.h"
+#include "lib/ip.h"
+#include "lib/socket.h"
+#include "lib/timer.h"
+#include "filter/data.h"
+
+#define SNMP_UNDEFINED 0
+#define SNMP_BGP       1
+#define SNMP_OSPF      2
+#define SNMP_INVALID  255
+
+#define SNMP_PORT 705
+
+#define SNMP_RX_BUFFER_SIZE 8192
+#define SNMP_TX_BUFFER_SIZE 8192
+#define SNMP_PKT_SIZE_MAX 4098
+
+#define AGENTX_MASTER_ADDR "/var/agentx/master"
+
+enum snmp_proto_state {
+  SNMP_DOWN = 0,
+  SNMP_INIT = 1,
+  SNMP_LOCKED,
+  SNMP_OPEN,
+  SNMP_REGISTER,
+  SNMP_CONN,
+  SNMP_STOP,
+};
+
+struct snmp_bond {
+  node n;
+  struct proto_config *config;
+  u8 type;
+};
+
+enum snmp_transport_type {
+  SNMP_TRANS_DEFAULT,
+  SNMP_TRANS_UNIX,
+  SNMP_TRANS_TCP,
+};
+
+#define SNMP_BGP_P_REGISTERING 0x01
+#define SNMP_BGP_P_REGISTERED  0x02
+
+struct snmp_bgp_peer {
+  const struct bgp_proto *bgp_proto;
+  ip4_addr peer_ip;                  /* used as hash key */
+  struct snmp_bgp_peer *next;
+};
+
+struct snmp_config {
+  struct proto_config cf;
+  enum snmp_transport_type trans_type;
+  ip_addr local_ip;
+  ip_addr master_ip;             /* master agentx IP address for TCP transport */
+  u16 master_port;
+  const char *master_path;       /* master agentx UNIX socket name */
+
+  u32 bgp4_local_id;     /* BGP4-MIB related fields */
+  u32 bgp4_local_as;
+
+  btime timeout;
+  btime startup_delay;
+  u8 priority;
+  u32 bonds;
+  const char *description;       /* The order of fields is not arbitrary */
+  list bgp_entries;              /* We want dynamically allocated fields to be
+                                  * at the end of the config struct.
+                                  * We use this fact to check differences of
+                                  * nonallocated parts of configs with memcpy
+                                  */
+  //const struct oid *oid_identifier;  TODO
+  int verbose;
+};
+
+struct snmp_proto {
+  struct proto p;
+  struct object_lock *lock;
+  pool *pool;                    /* a shortcut to the procotol mem. pool */
+  linpool *lp;                   /* linpool for bgp_trie nodes */
+
+  enum snmp_proto_state state;
+
+  ip_addr local_ip;
+  ip_addr master_ip;
+  u16 master_port;
+
+  /* TODO add struct for grouping BGP4-MIB data */
+  u32 bgp4_local_id;             /* BGP4-MIB related fields */
+  u32 bgp4_local_as;
+
+  sock *sock;
+
+
+  btime timeout;                 /* timeout is part of MIB registration. It
+                                   specifies how long should the master
+                                   agent wait for request responses. */
+
+  u32 session_id;
+  u32 transaction_id;
+  u32 packet_id;
+
+  list registration_queue;                 /* list containing snmp_register records */
+
+  // map
+  struct f_trie *bgp_trie;
+  HASH(struct snmp_bgp_peer) bgp_hash;
+  struct tbf rl_gen;
+
+  list pending_pdus;
+
+  timer *ping_timer;
+  btime startup_delay;
+  timer *startup_timer;
+
+  struct mib_tree *mib_tree;
+  int verbose;
+  uint pings;
+  u32 ignore_ping_id;
+};
+
+enum agentx_mibs {
+  BGP4_MIB_ID,
+  AGENTX_MIB_COUNT,
+  AGENTX_MIB_UNKNOWN,
+};
+
+struct snmp_registration;
+struct agentx_response; /* declared in subagent.h */
+typedef void (*snmp_reg_hook_t)(struct snmp_proto *p, const struct agentx_response *res, struct snmp_registration *reg);
+
+struct snmp_registration {
+  node n;
+  enum agentx_mibs mib;
+  u32 session_id;
+  u32 transaction_id;
+  u32 packet_id;
+  snmp_reg_hook_t reg_hook_ok; /* hook called when successful response to OID registration is recieved */
+  snmp_reg_hook_t reg_hook_fail; /* hook called when OID registration fail */
+};
+
+void snmp_startup(struct snmp_proto *p);
+void snmp_connected(sock *sk);
+void snmp_startup_timeout(timer *tm);
+void snmp_reconnect(timer *tm);
+int snmp_set_state(struct snmp_proto *p, enum snmp_proto_state state);
+int snmp_reset(struct snmp_proto *p);
+void snmp_up(struct snmp_proto *p);
+
+extern const char agentx_master_addr[sizeof(AGENTX_MASTER_ADDR)];
+
+static inline int
+proto_is_snmp(const struct proto *P)
+{
+  extern struct protocol proto_snmp;
+  return P->proto == &proto_snmp;
+}
+
+
+#endif
diff --git a/proto/snmp/snmp_test.c b/proto/snmp/snmp_test.c
new file mode 100644 (file)
index 0000000..ebef779
--- /dev/null
@@ -0,0 +1,1965 @@
+/*
+ *     BIRD -- Simple Network Management Protocol (SNMP) Unit tests
+ *
+ *      (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ *      (c) 2022 CZ.NIC z.s.p.o
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <stdarg.h>
+
+#include "test/birdtest.h"
+#include "test/bt-utils.h"
+
+#include "bgp4_mib.h"
+#include "subagent.h"
+#include "snmp.h"
+#include "snmp_utils.h"
+#include "mib_tree.h"
+
+static int t_oid_empty(void);
+static int t_oid_compare(void);
+static int t_varbind_name_to_tx(void);
+static int t_walk_oid_desc(void);
+static int t_walk_oid_compare(void);
+static int t_tree_find(void);
+static int t_tree_traversal(void);
+static int t_tree_leafs(void);
+static int t_tree_add(void);
+static int t_tree_delete(void);
+
+#define SNMP_BUFFER_SIZE 1024
+#define TESTS_NUM   32
+#define SMALL_TESTS_NUM 10
+static int tree_sizes[] = { 0, 1, 10, 100, 1000 };
+
+/* smaller than theoretical maximum (2^32) to fit in memory */
+#define OID_MAX_ID 16
+
+#define SNMP_EXPECTED(actual, expected) \
+  bt_debug("%s  expected: %3u   actual: %3u\n", \
+    #expected, expected, actual);
+
+static inline struct oid *
+oid_allocate(uint size)
+{
+  return tmp_alloc(sizeof(struct oid) + size * sizeof(u32));
+}
+
+static inline void
+oid_init2(struct oid *oid, u8 n_subid, u8 prefix, u8 include, va_list ids)
+{
+  oid->n_subid = n_subid;
+  oid->prefix = prefix;
+  oid->include = include;
+  oid->reserved = 0;
+
+  for (u8 i = 0; i < n_subid; i++)
+  {
+    u32 id = va_arg(ids, u32);
+    oid->ids[i] = id;
+  }
+}
+
+static inline void
+oid_init(struct oid *oid, u8 n_subid, u8 prefix, u8 include, ...)
+{
+  va_list ids;
+  va_start(ids, include);
+  oid_init2(oid, n_subid, prefix, include, ids);
+  va_end(ids);
+}
+
+static inline struct oid *
+oid_create(u8 n_subid, u8 prefix, u8 include, ...)
+{
+  struct oid *result = tmp_alloc(snmp_oid_size_from_len(n_subid));
+  va_list ids;
+
+  va_start(ids, include);
+  oid_init2(result, n_subid, prefix, include, ids);
+  va_end(ids);
+
+  return result;
+}
+
+static u32
+oid_random_id(void)
+{
+  return (bt_random() % (OID_MAX_ID));
+}
+
+static struct oid *
+random_prefixed_oid(void)
+{
+  u32 len = bt_random_n(OID_MAX_LEN + 1 - (ARRAY_SIZE(snmp_internet) + 1));
+
+  u8 prefix = (u8) bt_random_n(UINT8_MAX + 1);
+
+  if (!prefix)
+    return oid_create(0, 0, 0, 0);
+
+  struct oid *random = tmp_alloc(snmp_oid_size_from_len(len));
+  /* (bt_random_n(2) * bt_random()) has 0.5 probability to have value 0 and
+   * 0.5 to have random u32 (including zero) */
+  oid_init(random, 0, prefix, bt_random_n(2) * bt_random());
+  random->n_subid = len;
+
+  for (u32 id = 0; id < len; id++)
+    random->ids[id] = oid_random_id();
+
+  return random;
+}
+
+static struct oid *
+random_no_prefix_oid(void)
+{
+  /* probability that the random OID is prefixable is practically zero */
+  u32 len = bt_random_n(OID_MAX_LEN + 1);
+
+  struct oid *random = tmp_alloc(snmp_oid_size_from_len(len));
+  /* (bt_random_n(2) * bt_random()) has 0.5 probability to have value 0 and
+   * 0.5 to have random u32 (including zero) */
+  oid_init(random, 0, 0, bt_random_n(2) * bt_random());
+  random->n_subid = len;
+
+  for (u32 id = 0; id < len; id++)
+    random->ids[id] = oid_random_id();
+
+  return random;
+}
+
+static struct oid *
+random_prefixable_oid(void)
+{
+  /* generate the len without the snmp_internet prefix included and prefix ID */
+  u32 len = bt_random_n(OID_MAX_LEN + 1 - (ARRAY_SIZE(snmp_internet) + 1));
+
+  struct oid *random = tmp_alloc(
+      snmp_oid_size_from_len(len + ARRAY_SIZE(snmp_internet) + 1));
+  /* (bt_random_n(2) * bt_random()) has 0.5 probability to have value 0 and
+   * 0.5 to have random u32 (including zero) */
+  oid_init(random, 0, 0, bt_random_n(2) * bt_random());
+  random->n_subid = len + ARRAY_SIZE(snmp_internet) + 1;
+
+  for (u32 inet_id = 0; inet_id < ARRAY_SIZE(snmp_internet); inet_id++)
+    random->ids[inet_id] = snmp_internet[inet_id];
+
+  random->ids[ARRAY_SIZE(snmp_internet)] = bt_random_n(UINT8_MAX + 1);
+
+  for (u32 id = 0; id < len; id++)
+    random->ids[id + ARRAY_SIZE(snmp_internet) + 1] = oid_random_id();
+
+  return random;
+}
+
+static struct oid *
+random_oid(void)
+{
+  u32 option = bt_random_n(3);
+
+  if (option == 0)
+    return random_prefixed_oid();
+  else if (option == 1)
+    return random_no_prefix_oid();
+  else
+    return random_prefixable_oid();
+}
+
+static int
+t_oid_empty(void)
+{
+  struct lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  bt_assert(snmp_is_oid_empty(NULL) == 0);
+
+  {
+    struct oid *blank = oid_create(0, 0, 0 /* no ids */);
+    bt_assert(snmp_is_oid_empty(blank) == 1);
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+
+  {
+    struct oid *prefixed = oid_create(3, 100, 1,
+      /* ids */ ~((u32) 0), 0, 256);
+    bt_assert(snmp_is_oid_empty(prefixed) == 0);
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+
+  {
+    struct oid *to_prefix = oid_create(8, 0, 1,
+      /* ids */ 1, 3, 6, 1, 100, ~((u32) 0), 0, 256);
+    bt_assert(snmp_is_oid_empty(to_prefix) == 0);
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+
+  {
+    struct oid *unprefixable = oid_create(2, 0, 0,
+      /* ids */ 65535, 4);
+    bt_assert(snmp_is_oid_empty(unprefixable) == 0);
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  {
+    struct oid *unprefixable2 = oid_create(8, 0, 1,
+      /* ids */ 1, 3, 6, 2, 1, 2, 15, 6);
+    bt_assert(snmp_is_oid_empty(unprefixable2) == 0);
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  tmp_flush();
+  return 1;
+}
+
+static int
+t_oid_compare(void)
+{
+  struct lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  /* same length, no prefix */
+  struct oid *l1 = oid_create(5, 0, 1,
+      /* ids */ 1, 2, 3, 4, 5);
+
+  struct oid *r1 = oid_create(5, 0, 0,
+      /* ids */ 1, 2, 3, 4, 6);
+
+  bt_assert(snmp_oid_compare(l1, r1) == -1);
+  bt_assert(snmp_oid_compare(r1, l1) ==  1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  /* same results for prefixed oids */
+  l1->prefix = 1;
+  r1->prefix = 1;
+
+  bt_assert(snmp_oid_compare(l1, r1) == -1);
+  bt_assert(snmp_oid_compare(r1, l1) ==  1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  /* different prefix -- has higher priority */
+  l1->prefix = 8;
+  r1->prefix = 4;
+
+  bt_assert(snmp_oid_compare(l1, r1) ==  1);
+  bt_assert(snmp_oid_compare(r1, l1) == -1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  lp_restore(tmp_linpool, &tmps);
+
+
+  /* different length, no prefix */
+  l1 = oid_create(4, 0, 0,
+      /* ids */ 1, 2, 3, 4);
+
+  r1 = oid_create(5, 0, 1,
+      /* ids */ 1, 2, 3, 4, 1);
+
+  bt_assert(snmp_oid_compare(l1, r1) == -1);
+  bt_assert(snmp_oid_compare(r1, l1) ==  1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  /* same results for prefixed oids */
+  l1->prefix = 3;
+  r1->prefix = 3;
+
+  bt_assert(snmp_oid_compare(l1, r1) == -1);
+  bt_assert(snmp_oid_compare(r1, l1) ==  1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  /* different prefix -- has higher priority */
+  l1->prefix = 17;
+  r1->prefix = 14;
+
+  bt_assert(snmp_oid_compare(l1, r1) ==  1);
+  bt_assert(snmp_oid_compare(r1, l1) == -1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  lp_restore(tmp_linpool, &tmps);
+
+
+  /* inverse order different length, no prefix */
+  l1 = oid_create(4, 0, 0,
+      /* ids */ 1, 2, 3, 5);
+
+  r1 = oid_create(5, 0, 0,
+      /* ids */ 1, 2, 3, 4, 1);
+
+  bt_assert(snmp_oid_compare(l1, r1) ==  1);
+  bt_assert(snmp_oid_compare(r1, l1) == -1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  /* same results for prefixed oids */
+  l1->prefix = 254;
+  r1->prefix = 254;
+
+  bt_assert(snmp_oid_compare(l1, r1) ==  1);
+  bt_assert(snmp_oid_compare(r1, l1) == -1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  /* different prefix -- has higher priority */
+  l1->prefix = 127;
+  r1->prefix = 35;
+
+  bt_assert(snmp_oid_compare(l1, r1) ==  1);
+  bt_assert(snmp_oid_compare(r1, l1) == -1);
+
+  lp_restore(tmp_linpool, &tmps);
+
+
+/* ==== MIXED PREFIXED / NON PREFIXED OID compare ==== */
+  /* same length, mixed */
+  l1 = oid_create(6, 0, 1,
+      /* ids */ 1, 2, 17, 3, 21, 4);
+
+  r1 = oid_create(1, 5, 1,
+      /* ids */ 3);
+
+  bt_assert(snmp_oid_compare(l1, r1) == -1);
+  bt_assert(snmp_oid_compare(r1, l1) ==  1);
+
+  bt_assert(snmp_oid_compare(l1, l1) ==  0);
+  bt_assert(snmp_oid_compare(r1, r1) ==  0);
+
+  lp_restore(tmp_linpool, &tmps);
+
+  struct oid *super = oid_create(4, 0, 0, /* ids */ 1, 3, 6, 1);
+  struct oid *weird = oid_create(4, 70, 0, /* ids */ 9, 10, 10, 12);
+
+  bt_assert(snmp_oid_compare(super, weird) != 0);
+
+  struct oid *pref = oid_create(0, 7, 0); // no ids, only prefix
+  struct oid *no_pref = oid_create(5, 0, 0, /* ids */ 1, 3, 6, 1, 7);
+
+  bt_assert(snmp_oid_compare(pref, no_pref) == 0);
+
+  struct oid *inet = oid_create(4, 0, 0, /* ids */ 1, 3, 6, 1);
+
+  bt_assert(snmp_oid_compare(inet, pref) < 0);
+  bt_assert(snmp_oid_compare(pref, inet) > 0);
+  bt_assert(snmp_oid_compare(inet, no_pref) < 0);
+  bt_assert(snmp_oid_compare(no_pref, inet) > 0);
+
+  struct oid *pref2 = oid_create(0, 16, 0); // no ids, only prefix
+  struct oid *no_pref2 = oid_create(5, 0, 0, /* ids */ 1, 3, 6, 1, 16);
+
+  bt_assert(snmp_oid_compare(pref2, no_pref2) == 0);
+  bt_assert(snmp_oid_compare(no_pref2, pref2) == 0);
+
+  bt_assert(snmp_oid_compare(pref, pref2) < 0);
+  bt_assert(snmp_oid_compare(pref2, pref) > 0);
+  bt_assert(snmp_oid_compare(pref, no_pref2) < 0);
+  bt_assert(snmp_oid_compare(no_pref2, pref) > 0);
+  bt_assert(snmp_oid_compare(no_pref, pref2) < 0);
+  bt_assert(snmp_oid_compare(pref2, no_pref) > 0);
+  bt_assert(snmp_oid_compare(no_pref, no_pref2) < 0);
+  bt_assert(snmp_oid_compare(no_pref2, no_pref) > 0);
+
+
+  tmp_flush();
+  return 1;
+}
+
+static inline void
+fix_byteorder(u32 *ids, u32 len)
+{
+  for (u32 i = 0; i < len; i++)
+    STORE_U32(ids[i], ids[i]);
+}
+
+int
+u32cmp_bo(const u32 *cpu_native, const u32 *net_bo, u32 len)
+{
+  for (u32 i = 0; i < len; i++)
+  {
+    if (cpu_native[i] != LOAD_U32(net_bo[i]))
+      return LOAD_U32(net_bo[i]) - cpu_native[i];
+  }
+
+  return 0;
+}
+
+#define CREATE_RANDOM(gen)     \
+  ({  \
+    struct oid *_o = gen(); \
+    fix_byteorder(_o->ids, _o->n_subid);  \
+    _o; \
+  })
+
+static int
+t_varbind_name_to_tx(void)
+{
+  /* Test snmp_vb_name_to_tx() */
+
+  lp_state tmps = { };
+  struct snmp_proto *snmp_proto = tmp_alloc(sizeof(struct snmp_proto));
+  memset(snmp_proto, 0, sizeof(struct snmp_proto));
+  sock *s = sk_new(&root_pool);
+  snmp_proto->sock = s;
+  sk_set_tbsize(s, SNMP_BUFFER_SIZE);
+  void *buffer = s->tbuf;
+
+  struct snmp_pdu copy = {
+    .p = snmp_proto,
+    .sr_vb_start = (void *) buffer,
+    .buffer = buffer,
+  };
+  struct snmp_pdu c = copy;
+  struct oid *new;
+  struct agentx_varbind *vb;
+
+  lp_save(tmp_linpool, &tmps);
+
+  /* testing prefixable OIDs */
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+    const struct oid *oid = CREATE_RANDOM(random_prefixable_oid);
+
+    /* both LOAD_U8() and STORE_U8() are pointless as it byteorder does not
+     * influence single byte values.
+     */
+
+    u8 subids = oid->n_subid;
+    u8 include = oid->include;
+    u32 pid = LOAD_U32(oid->ids[ARRAY_SIZE(snmp_internet)]);
+
+    /* reset to the default snmp_pdu */
+    c = copy; memset(buffer, 0, snmp_oid_size(oid) + 8);
+
+    vb = snmp_vb_name_to_tx(&c, oid);
+    new = &vb->name;
+
+    bt_assert(new->n_subid == subids - (ARRAY_SIZE(snmp_internet) + 1));
+    bt_assert(new->prefix == pid);
+    bt_assert(!!new->include == !!include);
+    bt_assert(new->reserved == 0);
+
+    for (u32 i = 0; i < new->n_subid; i++)
+    {
+      bt_assert(new->ids[i] == LOAD_U32(oid->ids[i + ARRAY_SIZE(snmp_internet) + 1]));
+    }
+
+    for (u32 j = 0; j < ARRAY_SIZE(snmp_internet); j++)
+      bt_assert(LOAD_U32(oid->ids[j]) == snmp_internet[j]);
+
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  /* testing already prefixed OIDs */
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+    const struct oid *prefixed = CREATE_RANDOM(random_prefixed_oid);
+
+    /* reset to the default snmp_pdu */
+    c = copy; memset(buffer, 0, snmp_oid_size(prefixed) + 8);
+
+    vb = snmp_vb_name_to_tx(&c, prefixed);
+    new = &vb->name;
+
+    bt_assert(new->n_subid == prefixed->n_subid);
+    bt_assert(new->prefix == prefixed->prefix);
+    bt_assert(!!new->include == !!prefixed->include);
+    bt_assert(new->reserved == 0);
+    bt_assert(!u32cmp_bo(&new->ids[0], &prefixed->ids[0], new->n_subid));
+
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  lp_restore(tmp_linpool, &tmps);
+
+  /* testing non-prefixable OIDs */
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+    const struct oid *oid = CREATE_RANDOM(random_no_prefix_oid);
+
+    /* test that the OID is _really_ not prefixable */
+    if (oid->n_subid > ARRAY_SIZE(snmp_internet) &&
+       LOAD_U32(oid->ids[ARRAY_SIZE(snmp_internet) + 1]) <= UINT8_MAX)
+    {
+      for (u32 i = 0; i < ARRAY_SIZE(snmp_internet); i++)
+       if (LOAD_U32(oid->ids[i]) != snmp_internet[i]) goto continue_testing;
+
+      break; /* outer for loop */
+    }
+
+continue_testing:
+
+    /* reset to the default snmp_pdu */
+    c = copy; memset(buffer, 0, snmp_oid_size(oid) + 8);
+
+    vb = snmp_vb_name_to_tx(&c, oid);
+    new = &vb->name;
+
+    bt_assert(new->n_subid == oid->n_subid);
+    bt_assert(new->prefix == oid->prefix);
+    bt_assert(!!new->include == !!oid->include);
+    bt_assert(new->reserved == 0);
+    bt_assert(!u32cmp_bo(&new->ids[0], &oid->ids[0], new->n_subid));
+
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  for (int test = 0; test < SMALL_TESTS_NUM; test++)
+  {
+    const struct oid *oid;
+    {
+      struct oid *work = random_prefixable_oid();
+      fix_byteorder(work->ids, work->n_subid);
+
+      /* include also the prefix ID (at index 4) */
+      u32 index = bt_random_n(ARRAY_SIZE(snmp_internet) + 1);
+      /* change randomly picked id at index from 0..5 (included) */
+      u32 random = bt_random();
+      if (index == ARRAY_SIZE(snmp_internet) && random > 255)
+       work->ids[index] = VALUE_U32(random);
+      else if (index != ARRAY_SIZE(snmp_internet) && work->ids[index] != random)
+       work->ids[index] = VALUE_U32(random);
+      else
+       continue;
+      oid = work;
+    }
+
+    /* reset to the default snmp_pdu */
+    c = copy; memset(buffer, 0, snmp_oid_size(oid) + 8);
+
+    vb = snmp_vb_name_to_tx(&c, oid);
+    new = &vb->name;
+
+    bt_assert(new->n_subid == oid->n_subid);
+    bt_assert(new->prefix == oid->prefix);
+    bt_assert(!!new->include == !!oid->include);
+    bt_assert(new->reserved == 0);
+    bt_assert(!u32cmp_bo(&new->ids[0], &oid->ids[0], new->n_subid));
+
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  rfree(snmp_proto->sock);
+  tmp_flush();
+  return 1;
+}
+
+static inline void
+walk_to_oid_one(pool *pool, const struct oid *oid)
+{
+  struct mib_tree storage, *tree = &storage;
+  mib_tree_init(pool, tree);
+
+  struct mib_walk_state walk;
+  mib_tree_walk_init(&walk, tree);
+
+  const struct oid *inet_pref = oid_create(1, 0, 0, /* ids */ 1);
+  mib_tree_remove(tree, inet_pref);
+
+  (void) mib_tree_add(pool, tree, oid, bt_random_n(2));
+  mib_tree_find(tree, &walk, oid);
+
+  char buf[1024];
+  struct oid *from_walk = (struct oid *) buf;
+
+  int r = mib_tree_walk_to_oid(&walk, from_walk,
+    (1024 - sizeof(struct oid)) / sizeof(u32));
+
+  /* the memory limit should not be breached */
+  bt_assert(r == 0);
+
+  bt_assert(snmp_oid_compare(from_walk, oid) == 0);
+
+  /* cleanup */
+  mib_tree_remove(tree, inet_pref);
+}
+
+/* test MIB tree walk to OID */
+static int
+t_walk_to_oid(void)
+{
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+
+  pool *pool = &root_pool;
+
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+
+    walk_to_oid_one(pool, random_prefixed_oid());
+    walk_to_oid_one(pool, random_no_prefix_oid());
+    walk_to_oid_one(pool, random_prefixable_oid());
+    /* only a one of above */
+    //walk_to_oid_one(random_oid);
+
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  tmp_flush();
+
+  return 1;
+}
+
+
+static void
+test_both(void *buffer, uint size, const struct oid *left, const struct oid
+*right, const struct oid *expected)
+{
+  memset(buffer, 0, size);
+  snmp_oid_common_ancestor(left, right, buffer);
+  bt_assert(snmp_oid_compare(buffer, expected) == 0);
+
+  memset(buffer, 0, size);
+  snmp_oid_common_ancestor(right, left, buffer);
+  bt_assert(snmp_oid_compare(buffer, expected) == 0);
+}
+
+#define TEST_BOTH(l, r, e) test_both(buffer, 1024, l, r, e)
+static int
+t_oid_ancestor(void)
+{
+  const struct oid *null = oid_create(0, 0, 0);
+  const struct oid *shorter = oid_create(3, 15, 0, /* ids */ 192, 1, 7);
+  const struct oid *prefixed = oid_create(4, 15, 0, /* ids */ 192, 1, 7, 82);
+  const struct oid *no_prefix = oid_create(9, 0, 0, /* ids */ 1, 3, 6, 1, 15, 192, 1, 7, 82);
+  const struct oid *outside = oid_create(7, 0, 0, /* ids */ 4, 3, 2, 1, 8, 0, 2);
+  const struct oid *prefix_only =  oid_create(0, 15, 0);
+  const struct oid *prefix_only2 = oid_create(0, 9, 0);
+  const struct oid *partial = oid_create(3, 0, 0, /* ids */ 1, 3, 6);
+  const struct oid *no_inet = oid_create(5, 0, 0, /* ids */ 1, 3, 6, 2, 5);
+
+  const struct oid *inet = oid_create(4, 0, 0, /* ids */ 1, 3, 6, 1);
+
+
+  const struct oid *oids[] = {
+    null, shorter, prefixed, no_prefix, outside, prefix_only, partial, no_inet, inet
+  };
+
+  char buffer[1024];
+
+  /* skip null oid */
+  for (size_t o = 1; o < ARRAY_SIZE(oids); o++)
+    TEST_BOTH(null, oids[o], null);
+
+  for (size_t o = 0; o < ARRAY_SIZE(oids); o++)
+    TEST_BOTH(oids[o], oids[o], oids[o]);
+
+  TEST_BOTH(partial, no_prefix, partial);
+  TEST_BOTH(partial, prefixed, partial);
+  TEST_BOTH(partial, prefix_only, partial);
+  TEST_BOTH(partial, prefix_only2, partial);
+
+  TEST_BOTH(prefix_only2, prefixed, inet);
+  TEST_BOTH(prefix_only2, no_prefix, inet);
+
+  TEST_BOTH(prefix_only2, inet, inet);
+
+  TEST_BOTH(prefix_only, prefix_only2, inet);
+
+  TEST_BOTH(prefix_only, prefixed, prefix_only);
+  TEST_BOTH(prefix_only, no_prefix, prefix_only);
+
+  TEST_BOTH(prefix_only, inet, inet);
+
+  /* skip null oid */
+  for (size_t o = 1; o < ARRAY_SIZE(oids); o++)
+  {
+    if (oids[o] == outside) continue;
+
+    TEST_BOTH(outside, oids[o], null);
+  }
+
+  TEST_BOTH(no_inet, partial, partial);
+  TEST_BOTH(no_inet, inet, partial);
+  TEST_BOTH(no_inet, prefix_only, partial);
+  TEST_BOTH(no_inet, prefix_only2, partial);
+  TEST_BOTH(no_inet, prefixed, partial);
+  TEST_BOTH(no_inet, no_prefix, partial);
+
+  TEST_BOTH(shorter, prefixed, shorter);
+  TEST_BOTH(shorter, no_prefix, shorter);
+
+  return 1;
+}
+
+/* really: static int test_snmp_oid_compare(const struct oid **left, const struct oid **right); */
+static int
+test_snmp_oid_compare(const void *left, const void *right)
+{
+  return snmp_oid_compare(
+    *((const struct oid **) left),
+    *((const struct oid **) right)
+  );
+}
+
+static void
+generate_raw_oids(struct oid *oids[], int size, struct oid *(*generator)(void))
+{
+  for (int i = 0; i < size; i++)
+  {
+    /* binary version of ~5% */
+    if (i > 0 && bt_random_n(256) <= 13)
+    {
+      /* at this chance, we create a copy instead of generating new oid */
+      oids[i] = tmp_alloc(snmp_oid_size(oids[i-1]));
+      memcpy(oids[i], oids[i-1], snmp_oid_size(oids[i-1]));
+    }
+    else
+      oids[i] = generator();
+  }
+}
+
+static int
+generate_oids(struct oid *oids[], struct oid *sorted[], int size, struct oid *(*generator)(void))
+{
+  generate_raw_oids(oids, size, generator);
+
+  memcpy(sorted, oids, size * sizeof(struct oid *));
+
+  qsort(sorted, (size_t) size, sizeof(struct oid *),
+      test_snmp_oid_compare);
+
+  // test sizes 0, 1, 2, 10, ...
+  int last_used = 0;
+  for (int index = 0; index < size; index++)
+  {
+    if (snmp_oid_compare(sorted[last_used], sorted[index]) != 0)
+      sorted[++last_used] = sorted[index];
+  }
+
+  /* delete old pointers */
+  for (int i = last_used + 1; i < size; i++)
+    sorted[i] = NULL;
+
+  return (size > 1) ? last_used + 1 : size;
+}
+
+static int
+t_walk_oid_desc(void)
+{
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  pool *pool = &root_pool;
+
+  struct mib_tree storage, *tree = &storage;
+  mib_tree_init(pool, tree);
+
+  STATIC_ASSERT(ARRAY_SIZE(tree_sizes) > 0);
+  int size = tree_sizes[ARRAY_SIZE(tree_sizes) - 1];
+  ASSERT(size > 0);
+  struct oid **oids = mb_alloc(pool, size * sizeof(struct oid *));
+  struct oid **sorted = mb_alloc(pool, size * sizeof(struct oid *));
+
+  (void) generate_oids(oids, sorted, size, random_oid);
+
+  for (int i = 0; i < size; i++)
+    (void) mib_tree_add(pool, tree, oids[i], 0);
+
+  for (int test = 0; test < size; test++)
+  {
+    int i = bt_random_n(size);
+
+    char buffer[1024];
+    struct oid *oid = (struct oid *) buffer;
+
+    memcpy(oid, oids[i], snmp_oid_size(oids[i]));
+
+    struct mib_walk_state walk;
+    mib_tree_walk_init(&walk, NULL);
+    (void) mib_tree_find(tree, &walk, oid);
+
+    int type = bt_random_n(4);
+    switch (type)
+    {
+      case 0:
+       bt_assert(mib_tree_walk_is_oid_descendant(&walk, oids[i]) == 0);
+       break;
+
+      case 1:
+      {
+       /* oid is longer than walk or has same length */
+       u8 ids = oid->n_subid;
+       u32 upto = MIN(OID_MAX_LEN - ids, 16);
+
+       if (!upto)
+         continue;
+
+       u32 new = bt_random_n(upto) + 1;
+       oid->n_subid = ids + new;
+
+       for (u32 i = 0; i < new; i++)
+         oid->ids[ids + i] = bt_random_n(OID_MAX_ID);
+
+       bt_assert(mib_tree_walk_is_oid_descendant(&walk, oid) > 0);
+
+       break;
+      }
+      case 2:
+      case 3:
+      {
+       /* oid is shorter than walk */
+       u8 ids = oid->n_subid;
+
+       if (ids == 0 || ids == OID_MAX_LEN)
+         continue;
+
+       u32 split = (ids > 1) ? bt_random_n(ids - 1) + 1 : 0;
+       u32 ext = (type == 3) ? bt_random_n(MIN(OID_MAX_LEN - ids, 16)) : 0;
+
+       oid->n_subid = split + ext;
+       for (u32 i = 0; i < ext; i++)
+         oid->ids[split + i] = bt_random_n(OID_MAX_ID);
+
+       int no_change = 1;
+       for (u32 j = 0; j < MIN(ids - split, ext); j++)
+       {
+         if (oid->ids[split + j] != oids[i]->ids[split + j])
+           no_change = 0;
+       }
+
+       if (no_change)
+         continue;
+
+       bt_assert(mib_tree_walk_is_oid_descendant(&walk, oid) < 0);
+       break;
+      }
+    }
+  }
+
+  {
+    struct mib_walk_state walk;
+    mib_tree_walk_init(&walk, tree);
+
+    u32 zero = 0;
+    const struct oid *null_oid = (struct oid *) &zero;
+    u32 index = bt_random_n(size);
+
+    bt_assert(mib_tree_walk_is_oid_descendant(&walk, null_oid) == 0);
+    if (!snmp_is_oid_empty(oids[index]))
+      bt_assert(mib_tree_walk_is_oid_descendant(&walk, oids[index]) > 0);
+    (void) mib_tree_find(tree, &walk, oids[index]);
+    if (!snmp_is_oid_empty(oids[index]))
+      bt_assert(mib_tree_walk_is_oid_descendant(&walk, null_oid) < 0);
+  }
+
+  u32 null_oid = 0;
+  mib_tree_remove(tree, (struct oid *) &null_oid);
+  lp_restore(tmp_linpool, &tmps);
+
+  return 1;
+}
+
+static int
+t_walk_oid_compare(void)
+{
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  pool *pool = &root_pool;
+
+  struct mib_tree storage, *tree = &storage;
+  mib_tree_init(pool, tree);
+
+  STATIC_ASSERT(ARRAY_SIZE(tree_sizes) > 0);
+  int size = tree_sizes[ARRAY_SIZE(tree_sizes) - 1];
+  ASSERT(size > 0);
+  struct oid **oids = mb_alloc(pool, size * sizeof(struct oid *));
+  struct oid **sorted = mb_alloc(pool, size * sizeof(struct oid *));
+
+  (void) generate_oids(oids, sorted, size, random_oid);
+
+  for (int i = 0; i < size; i++)
+    (void) mib_tree_add(pool, tree, oids[i], 0);
+
+  for (int test = 0; test < size; test++)
+  {
+    int i = bt_random_n(size);
+
+    char buffer[1024];
+    struct oid *oid = (struct oid *) buffer;
+
+    memcpy(oid, oids[i], snmp_oid_size(oids[i]));
+
+    struct mib_walk_state walk;
+    mib_tree_walk_init(&walk, NULL);
+    (void) mib_tree_find(tree, &walk, oids[i]);
+
+    int type = bt_random_n(4);
+    switch (type)
+    {
+      case 0:
+       bt_assert(mib_tree_walk_oid_compare(&walk, oids[i]) == 0);
+       break;
+
+      case 1:
+      {
+       /* oid is longer than walk or has same length */
+       u8 ids = oid->n_subid;
+       u32 upto = MIN(OID_MAX_LEN - ids, 16);
+
+       if (!upto)
+         continue;
+
+       u32 new = bt_random_n(upto) + 1;
+       oid->n_subid = ids + new;
+       ASSERT(snmp_oid_size(oid) < 1024);
+
+       for (u32 i = 0; i < new; i++)
+         oid->ids[ids + i] = bt_random_n(OID_MAX_ID);
+
+
+       bt_assert(mib_tree_walk_oid_compare(&walk, oid) < 0);
+       break;
+      }
+      case 2:
+      case 3:
+      {
+       /* oid is shorter than walk */
+       u8 ids = oid->n_subid;
+
+       if (ids == 0 || ids == OID_MAX_LEN)
+         continue;
+
+       u32 split = (ids > 1) ? bt_random_n(ids - 1) + 1 : 0;
+       u32 ext = (type == 3) ? bt_random_n(MIN(OID_MAX_LEN - ids, 16)) : 0;
+
+       oid->n_subid = split + ext;
+       for (u32 i = 0; i < ext; i++)
+         oid->ids[split + i] = bt_random_n(OID_MAX_ID);
+
+       int cmp_res = 0;
+       for (u32 j = 0; j < MIN(ids - split, ext) && !cmp_res; j++)
+         cmp_res = oids[i]->ids[split + j] - oid->ids[split + j];
+
+       if (!cmp_res && split + ext == ids)
+         continue;
+
+       if (!cmp_res && split + ext < ids)
+         cmp_res = +1;
+
+       if (!cmp_res && split + ext > ids)
+         cmp_res = -1;
+
+       if (cmp_res < 0)
+         cmp_res = -1;
+       else if (cmp_res > 0)
+         cmp_res = +1;
+
+       bt_assert(mib_tree_walk_oid_compare(&walk, oid) == cmp_res);
+       break;
+      }
+    }
+  }
+
+  {
+    struct mib_walk_state walk;
+    mib_tree_walk_init(&walk, tree);
+
+    u32 zero = 0;
+    const struct oid *null_oid = (struct oid *) &zero;
+    u32 index = bt_random_n(size);
+
+    bt_assert(mib_tree_walk_oid_compare(&walk, null_oid) == 0);
+    if (!snmp_is_oid_empty(oids[index]))
+      bt_assert(mib_tree_walk_oid_compare(&walk, oids[index]) < 0);
+    else
+      bt_assert(mib_tree_walk_oid_compare(&walk, oids[index]) == 0);
+    (void) mib_tree_find(tree, &walk, oids[index]);
+    if (!snmp_is_oid_empty(oids[index]))
+      bt_assert(mib_tree_walk_oid_compare(&walk, null_oid) > 0);
+  }
+
+  u32 null_oid = 0;
+  mib_tree_remove(tree, (struct oid *) &null_oid);
+  lp_restore(tmp_linpool, &tmps);
+
+  return 1;
+
+}
+
+static void UNUSED
+print_dups(const struct oid *oids[], uint size)
+{
+  for (uint i = 0; i < size; i++)
+    for (uint j = i + 1; j < size; j++)
+      if (snmp_oid_compare(oids[i], oids[j]) == 0)
+       log(L_WARN "pair (%u, %u)", i, j);
+}
+
+static void UNUSED
+print_all(const struct oid *oids[], uint size)
+{
+  for (uint i = 0; i < size; i++)
+    snmp_oid_log(oids[i]);
+}
+
+static inline int
+oid_is_leaf(const struct oid *oid, const struct oid *leafs[], uint leaf_idx)
+{
+  for (uint l = 0; l < leaf_idx; l++)
+    if (snmp_oid_compare(oid, leafs[l]) == 0)
+      return 1;
+
+  return 0;
+}
+
+static int
+all_invalid(const struct oid *oids[], const byte *invalid, uint size, uint index)
+{
+  if (!invalid[index])
+    return 0;
+
+  for (uint i = 0; i < size; i++)
+  {
+    if (i == index) continue;
+
+    if (snmp_oid_compare(oids[i], oids[index]) == 0 &&
+       !invalid[i])
+      return 0;
+  }
+
+  return 1;
+}
+
+static int
+count_error(const struct oid *oids[], const byte *invalid, uint size)
+{
+  int error = 0;
+  for (uint i = 0; i < size; i++)
+  {
+    if (!invalid[i]) continue;
+
+    int skip = 0;
+    for (uint j = 0; j < i; j++)
+    {
+      if (snmp_oid_compare(oids[i], oids[j]) == 0)
+      {
+       skip = 1;
+       break;
+      }
+    }
+
+    if (skip) continue;
+
+    if (all_invalid(oids, invalid, size, i))
+      error++;
+  }
+
+  return error;
+}
+
+static int
+gen_test_add(struct oid *(*generator)(void))
+{
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  pool *pool = &root_pool;
+
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+    size_t tsz = ARRAY_SIZE(tree_sizes);
+
+    int size = tree_sizes[test % tsz];
+    int with_leafs = (test % (2 * tsz)) < tsz;
+    int no_inet_prefix = (test % (4 * tsz)) < (2 * tsz);
+
+    struct oid **oids = mb_alloc(pool, size * sizeof(struct oid *));
+    byte *types = mb_alloc(pool, size * sizeof(byte));
+    byte *invalid_hist = mb_alloc(pool, size * sizeof(byte));
+    struct oid **sorted = mb_alloc(pool, size * sizeof(struct oid *));
+    struct oid **leafs = (with_leafs) ? mb_alloc(pool, size * sizeof(struct oid *))
+      : NULL;
+    int leaf_idx = 0;
+    int empty_prefix_added = 0;
+    int distinct = generate_oids(oids, sorted, size, generator);
+
+    struct mib_tree storage, *tree = &storage;
+    mib_tree_init(pool, tree);
+
+    if (no_inet_prefix)
+    {
+      /* remove the node .1 and all children */
+      const struct oid *inet_pref = oid_create(1, 0, 0, /* ids */ 1);
+      mib_tree_remove(tree, inet_pref);
+    }
+
+    int invalid_counter = 0;
+    int counter = 0;
+    int cut = 0;
+    for (int i = 0; i < size; i++)
+    {
+      int invalid = 0;
+      int is_leaf = (with_leafs) ? (int) bt_random_n(2) : 0;
+      types[i] = (byte) is_leaf;
+
+      int will_cut = 0;
+      int oid_nulled = snmp_is_oid_empty(oids[i]);
+
+      if (oid_nulled && is_leaf)
+       invalid = 1;
+
+      if (!no_inet_prefix)
+      {
+       char buffer[1024];
+       struct oid *o = (struct oid *) buffer;
+
+       struct oid *inet = oid_create(4, 0, 0, /* ids */ 1, 3, 6, 1);
+       snmp_oid_common_ancestor(oids[i], inet, o);
+
+       /* If the standard internet prefix is present,
+        * then the prefix leafs are invalid. */
+       if (snmp_oid_compare(oids[i], o) == 0)
+         invalid = is_leaf;
+      }
+
+      /* check existence of ancestor node of a new leaf */
+      for (int oi = 0; !invalid && !oid_nulled && oi < i; oi++)
+      {
+       char buffer[1024];
+       struct oid *o = (struct oid *) buffer;
+
+       if (invalid_hist[oi])
+         continue;
+
+       int other_is_leaf = (int) types[oi];
+
+       if (snmp_oid_compare(oids[oi], oids[i]) == 0 &&
+           !snmp_is_oid_empty(oids[i]))
+       {
+         if (other_is_leaf == is_leaf)
+           will_cut = 1;
+         else if (other_is_leaf != is_leaf)
+           invalid = 1;
+
+         break;
+       }
+
+       snmp_oid_common_ancestor(oids[oi], oids[i], o);
+
+       if ((snmp_oid_compare(oids[i], o) == 0 && is_leaf) ||
+           (snmp_oid_compare(oids[oi], o) == 0 && other_is_leaf))
+       {
+         invalid = 1;
+         break;
+       }
+      }
+
+      if (!invalid && will_cut)
+       cut++;
+
+      if (is_leaf && !invalid)
+       /* leafs could have duplicates */
+       leafs[leaf_idx++] = oids[i];
+
+      mib_node_u *node = mib_tree_add(pool, tree, oids[i], is_leaf);
+
+      bt_assert((node == NULL) == invalid);
+
+      invalid_hist[i] = 0;
+      if (invalid)
+      {
+       invalid_hist[i] = 1;
+       invalid_counter++;
+      }
+
+      if (node != NULL && (!snmp_is_oid_empty(oids[i]) || !empty_prefix_added))
+       counter++;
+
+      if (snmp_is_oid_empty(oids[i]) && !is_leaf)
+       empty_prefix_added = 1;
+    }
+
+    int error = count_error((const struct oid **) oids, invalid_hist, size);
+    bt_assert(counter - cut == distinct - error);
+
+    lp_restore(tmp_linpool, &tmps);
+    mb_free(oids);
+    mb_free(sorted);
+    mb_free(leafs);
+  }
+
+  return 1;
+}
+
+static int
+t_tree_add(void)
+{
+
+  gen_test_add(random_prefixed_oid);
+  gen_test_add(random_no_prefix_oid);
+  gen_test_add(random_prefixable_oid);
+  gen_test_add(random_oid);
+
+  return 1;
+}
+
+static int
+gen_test_find(struct oid *(*generator)(void))
+{
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  pool *pool = &root_pool;
+
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+    size_t tsz = ARRAY_SIZE(tree_sizes);
+
+    int size = tree_sizes[test % tsz];
+    int with_leafs = (test % (2 * tsz)) < tsz;
+    int no_inet_prefix = (test % (4 * tsz)) < (2 * tsz);
+
+    struct oid **oids = mb_alloc(pool, size * sizeof(struct oid *));
+    mib_node_u **nodes = mb_alloc(pool, size * sizeof(mib_node_u *));
+    struct oid **searched = mb_alloc(pool, size * sizeof(struct oid *));
+    byte *types = mb_alloc(pool, size * sizeof(byte));
+
+    /* enough to hold snmp_internet copy */
+    uint longest_inet_pref_len = 0;
+    struct oid *longest_inet_pref = oid_create(4, 0, 0, /* ids */ 0, 0, 0, 0);
+
+    generate_raw_oids(oids, size, generator);
+    generate_raw_oids(searched, size, generator);
+
+    struct mib_tree storage, *tree = &storage;
+    mib_tree_init(pool, tree);
+
+    if (no_inet_prefix)
+    {
+      /* remove the node .1 and all children */
+      const struct oid *inet_pref = oid_create(1, 0, 0, /* ids */ 1);
+      mib_tree_remove(tree, inet_pref);
+    }
+
+    for (int i = 0; i < size; i++)
+      types[i] = (byte) ((with_leafs) ? bt_random_n(2) : 0);
+
+    /*
+     * by default initialized MIB tree will have internet prefix have inserted
+     */
+    if (!no_inet_prefix)
+    {
+      memcpy(longest_inet_pref->ids, snmp_internet, sizeof(snmp_internet));
+      longest_inet_pref_len = 4;
+    }
+
+    for (int i = 0; i < size; i++)
+    {
+      nodes[i] = mib_tree_add(pool, tree, oids[i], types[i]);
+
+      if (nodes[i] == NULL) continue;
+
+      if (snmp_oid_is_prefixed(oids[i]))
+      {
+       memcpy(longest_inet_pref->ids, snmp_internet, sizeof(snmp_internet));
+       longest_inet_pref_len = 4;
+      }
+      else
+      {
+       for (uint j = 0; j < MIN(oids[i]->n_subid,
+           ARRAY_SIZE(snmp_internet)); j++)
+       {
+         if (oids[i]->ids[j] == snmp_internet[j] &&
+             j >= longest_inet_pref_len)
+         {
+           longest_inet_pref->ids[j] = snmp_internet[j];
+           longest_inet_pref_len = j + 1;
+         }
+         else if (oids[i]->ids[j] == snmp_internet[j])
+           ;
+         else
+           break;
+       }
+      }
+    }
+
+    for (int i = 0; i < size; i++)
+    {
+      for (int j = 0; j < size; j++)
+      {
+       if (nodes[i] != NULL &&
+           nodes[j] != NULL &&
+           snmp_oid_compare(oids[i], oids[j]) == 0)
+         bt_assert(nodes[i] == nodes[j]);
+      }
+    }
+
+    mib_node_u *last = NULL;
+    for (int i = 0; i < size; i++)
+    {
+      /*
+       * This solves cases where we tried to insert
+       * both leaf and inner node for same OID.
+       * Result of insertion should be NULL in cases
+       * when the insertion is inconsistent with the current tree state.
+       * (the first insertion wins)
+       */
+      int expected_precise = 1;
+      mib_node_u *expected = nodes[i];
+
+      if (!no_inet_prefix)
+      {
+       char buf[1024];
+       struct oid *o = (struct oid *) buf;
+       snmp_oid_common_ancestor(oids[i], longest_inet_pref, o);
+       if (snmp_oid_compare(oids[i], o) == 0)
+         expected_precise = 0;
+      }
+
+      if (snmp_is_oid_empty(oids[i]))
+      {
+       expected_precise = 0;
+      }
+
+      for (int j = 0; expected_precise && j < size; j++)
+      {
+       if (i == j) continue;
+
+       if (snmp_oid_compare(oids[i], oids[j]) == 0 &&
+           types[i] != types[j] && nodes[i] == NULL)
+       {
+         if (nodes[j] != NULL)
+         {
+           expected = nodes[j];
+           break;
+         }
+
+         /* else expected = NULL; */
+       }
+
+       char buf[1024];
+       struct oid *o = (struct oid *) buf;
+
+       snmp_oid_common_ancestor(oids[i], oids[j], o);
+
+       /* oids[j] lies on path from root to oids[i] */
+       if (snmp_oid_compare(oids[i], o) == 0 &&
+           nodes[j] != NULL &&
+           expected == NULL)
+       {
+         expected_precise = 0;
+         break;
+       }
+      }
+
+
+      struct mib_walk_state walk;
+      //mib_tree_walk_init(&walk, tree, 0);
+      mib_tree_walk_init(&walk, NULL);
+      mib_node_u *found = mib_tree_find(tree, &walk, oids[i]);
+
+      bt_assert(walk.stack_pos <= MIB_WALK_STACK_SIZE + 1);
+      bt_assert(walk.id_pos <= OID_MAX_LEN);
+
+      if (expected_precise)
+       bt_assert(found == expected);
+      else
+       /* found is an auto-inserted node on path to some dest OID */
+       bt_assert(found != NULL);
+
+      last = found;
+
+      /* test finding with walk state not pointing at the root of the tree */
+      u8 subids = oids[i]->n_subid;
+      if (subids > 0)
+      {
+       found = NULL;
+       u32 new_ids = bt_random_n(subids);
+       mib_tree_walk_init(&walk, (bt_random_n(2)) ? tree : NULL);
+
+       oids[i]->n_subid = new_ids;
+
+       mib_node_u *ignored UNUSED;
+       ignored = mib_tree_find(tree, &walk, oids[i]);
+
+       oids[i]->n_subid = subids;
+
+       found = mib_tree_find(tree, &walk, oids[i]);
+
+       /* see above */
+       if (expected_precise)
+         bt_assert(found == expected);
+       else
+       {
+         /* test that the result is same as direct searched from tree root */
+         bt_assert(found == last);
+         bt_assert(found != NULL);
+       }
+      }
+    }
+
+    for (int search = 0; search < size; search++)
+    {
+      int has_node = snmp_is_oid_empty(searched[search]);
+
+      for (int stored = 0; stored < size; stored++)
+      {
+       char buf[1024];
+       struct oid *o = (struct oid *) buf;
+       snmp_oid_common_ancestor(oids[stored], searched[search], o);
+
+       /* test if OID oids[stored] is valid and if it forms a path from root
+        * with OID searched[search] */
+       if (nodes[stored] != NULL && snmp_oid_compare(searched[search], o) == 0)
+       {
+         has_node = 1;
+         break;
+       }
+      }
+
+      const struct oid *oid = searched[search];
+      if (!has_node && !snmp_oid_is_prefixed(oid))
+      {
+       for (uint i = 0; i < MIN(ARRAY_SIZE(snmp_internet),
+           oid->n_subid); i++)
+       {
+         if (longest_inet_pref->ids[i] != 0 &&
+             longest_inet_pref->ids[i] == oid->ids[i])
+           has_node = 1;
+         else
+         {
+           has_node = 0;
+           break;
+         }
+       }
+
+       if (has_node && oid->n_subid > ARRAY_SIZE(snmp_internet))
+         has_node = 0;
+      }
+
+      struct mib_walk_state walk;
+      mib_tree_walk_init(&walk, NULL);
+      mib_node_u *found = mib_tree_find(tree, &walk, searched[search]);
+      bt_assert(has_node == (found != NULL));
+
+      bt_assert(walk.stack_pos <= MIB_WALK_STACK_SIZE + 1);
+      bt_assert(walk.id_pos <= OID_MAX_LEN);
+
+      last = found;
+
+      u8 subids = searched[search]->n_subid;
+      if (subids > 0)
+      {
+       found = NULL;
+       u32 new_ids = bt_random_n(subids);
+       mib_tree_walk_init(&walk, (bt_random_n(2)) ? tree : NULL);
+
+       searched[search]->n_subid = new_ids;
+
+       mib_node_u *ignored UNUSED;
+       ignored = mib_tree_find(tree, &walk, searched[search]);
+
+       searched[search]->n_subid = subids;
+
+       found = mib_tree_find(tree, &walk, searched[search]);
+
+       bt_assert(has_node == (found != NULL));
+
+       bt_assert(walk.stack_pos <= MIB_WALK_STACK_SIZE + 1);
+       bt_assert(walk.id_pos <= OID_MAX_LEN);
+
+       /* test that the result is same as direct search from tree root */
+       bt_assert(last == found);
+      }
+    }
+
+    lp_restore(tmp_linpool, &tmps);
+    mb_free(oids);
+    mb_free(nodes);
+    mb_free(searched);
+    mb_free(types);
+  }
+
+  tmp_flush();
+  return 1;
+}
+
+static int
+t_tree_find(void)
+{
+
+  gen_test_find(random_prefixed_oid);
+  gen_test_find(random_no_prefix_oid);
+  gen_test_find(random_prefixable_oid);
+  gen_test_find(random_oid);
+
+  return 1;
+}
+
+#if 0
+static int
+delete_cleanup(const struct oid *oid, struct oid *oids[], mib_node_u *valid[], int size)
+{
+  uint counter = 0;
+  for (int i = 0; i < size; i++)
+  {
+    char buf[1024];
+    struct oid *o = (struct oid *) buf;
+
+    if (oid == oids[i])
+    {
+      counter++;
+      continue;
+    }
+
+    snmp_oid_common_ancestor(oid, oids[i], o);
+
+    if (snmp_oid_compare(oid, o) == 0)
+    {
+      valid[i] = NULL;
+      counter++;
+    }
+  }
+
+  return counter;
+}
+#endif
+
+static int
+gen_test_delete_remove(struct oid *(*generator)(void), int remove)
+{
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  pool *pool = &root_pool;
+
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+    size_t tsz = ARRAY_SIZE(tree_sizes);
+
+    int size = tree_sizes[test % tsz];
+    int with_leafs = (test % (2 * tsz)) < tsz;
+    int no_inet_prefix = (test % (4 * tsz)) < (2 * tsz);
+
+    struct oid **oids = mb_alloc(pool, size * sizeof(struct oid *));
+    struct oid **sorted = mb_alloc(pool, size * sizeof(struct oid *));
+    mib_node_u **nodes = mb_alloc(pool, size * sizeof(mib_node_u *));
+    byte *types = mb_alloc(pool, size * sizeof(byte));
+
+    struct mib_tree storage, *tree = &storage;
+    mib_tree_init(pool, tree);
+
+    if (no_inet_prefix)
+    {
+      /* remove the node .1 and all children */
+      const struct oid *inet_pref = oid_create(1, 0, 0, /* ids */ 1);
+      mib_tree_remove(tree, inet_pref);
+    }
+
+    int distinct = generate_oids(oids, sorted, size, generator);
+
+    for (int i = 0; i < size; i++)
+    {
+      int is_leaf;
+      is_leaf = types[i] = (byte) (with_leafs) ? bt_random_n(2) : 0;
+      (void) mib_tree_add(pool, tree, oids[i], is_leaf);
+    }
+
+    for (int d = 0; d < distinct; d++)
+    {
+      struct mib_walk_state walk;
+      mib_tree_walk_init(&walk, NULL);
+      //mib_tree_walk_init(&walk, tree); TODO
+      nodes[d] = mib_tree_find(tree, &walk, sorted[d]);
+    }
+
+    /* we need to populate the nodes array after all insertions because
+     * some insertion may fail (== NULL) because we try to insert a leaf */
+#if 0
+    for (int i = 0; i < size; i++)
+    {
+      struct mib_walk_state walk;
+      mib_tree_walk_init(&walk, tree, 0);
+      nodes[i] = mib_tree_find(tree, &walk, oids[i]);
+    }
+#endif
+
+    int deleted, invalid_counter;
+    /* test deletion one of the inserted OIDs */
+    for (int round = 0; round < (size + 3) / 4 + remove; round++)
+    {
+      /* note: we do not run any rounds for size zero because bt_random_n(0)
+       * does not exist */
+      int i;
+      struct oid *oid;
+      if (!remove)
+      {
+       /* this way we are also testing remove non-existent tree nodes */
+       i = bt_random_n(size); /* not bt_random_n(distinct) */
+       oid = oids[i];
+      }
+      else
+      {
+       i = -1; /* break fast  */
+       oid = generator();
+      }
+
+      struct mib_walk_state walk;
+      mib_tree_walk_init(&walk, NULL);
+      // mib_tree_walk_init(&walk, tree); TODO
+      mib_node_u *node = mib_tree_find(tree, &walk, oid);
+
+      if (node == NULL)
+       continue;
+
+      if (!remove)
+       deleted = mib_tree_delete(tree, &walk);
+      else
+       deleted = mib_tree_remove(tree, oid);
+
+      bt_assert(deleted > 0 || snmp_is_oid_empty(oid));
+
+      invalid_counter = 0;
+      int counted_removed = 0;
+      for (int j = 0; j < distinct; j++)
+      {
+       //mib_tree_walk_init(&walk, tree, 0);
+       mib_tree_walk_init(&walk, NULL);
+       mib_node_u *node = mib_tree_find(tree, &walk, sorted[j]);
+
+       if (snmp_is_oid_empty(oid))
+         ;
+       /* the oid could have multiple instances in the oids dataset */
+       else if (snmp_oid_compare(oid, sorted[j]) == 0 && !counted_removed)
+       {
+         invalid_counter++;
+         counted_removed = 1;
+         bt_assert(node == NULL);
+         nodes[j] = NULL;
+       }
+       else if (node != nodes[j])
+       {
+         invalid_counter++;
+         bt_assert(node == NULL);
+         nodes[j] = NULL;
+       }
+      }
+
+      /* we do not count the internal node that are included in the deleted */
+      bt_assert(deleted >= invalid_counter);
+    }
+
+    lp_restore(tmp_linpool, &tmps);
+    mb_free(oids);
+    mb_free(sorted);
+    mb_free(nodes);
+  }
+
+  tmp_flush();
+
+  return 1;
+}
+
+static int
+t_tree_delete(void)
+{
+
+  gen_test_delete_remove(random_prefixed_oid, 0);
+  gen_test_delete_remove(random_no_prefix_oid, 0);
+  gen_test_delete_remove(random_prefixable_oid, 0);
+  gen_test_delete_remove(random_oid, 0);
+
+  return 1;
+}
+
+static int
+t_tree_remove(void)
+{
+
+  gen_test_delete_remove(random_prefixed_oid, 1);
+  gen_test_delete_remove(random_no_prefix_oid, 1);
+  gen_test_delete_remove(random_prefixable_oid, 1);
+  gen_test_delete_remove(random_oid, 1);
+
+  return 1;
+}
+
+static void
+gen_test_traverse(struct oid *(*generator)(void))
+{
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  pool *pool = &root_pool;
+
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+    size_t tsz = ARRAY_SIZE(tree_sizes);
+
+    int size = tree_sizes[test % tsz];
+    int with_leafs = (test % (2 * tsz)) < tsz;
+    int no_inet_prefix = (test % (4 * tsz)) < (2 * tsz);
+
+    struct oid **oids = mb_alloc(pool, size * sizeof(struct oid *));
+    struct oid **sorted = mb_alloc(pool, size * sizeof(struct oid *));
+    mib_node_u **nodes = mb_allocz(pool, size * sizeof(mib_node_u *));
+
+    const int distinct = generate_oids(oids, sorted, size, generator);
+
+    struct mib_tree storage, *tree = &storage;
+    mib_tree_init(pool, tree);
+
+    if (no_inet_prefix)
+    {
+      /* remove the node .1 and all children */
+      const struct oid *inet_pref = oid_create(1, 0, 0, /* ids */ 1);
+      mib_tree_remove(tree, inet_pref);
+    }
+
+    for (int o = 0; o < size; o++)
+    {
+      int is_leaf = (with_leafs) ? (int) bt_random_n(2) : 0;
+      (void) mib_tree_add(pool, tree, oids[o], is_leaf);
+    }
+
+    for (int d = 0; d < distinct; d++)
+    {
+      struct mib_walk_state walk;
+      mib_tree_walk_init(&walk, NULL);
+      nodes[d] = mib_tree_find(tree, &walk, sorted[d]);
+    }
+
+    int bound = 0;
+
+    for (int d = 0; d < distinct; d++)
+    {
+      if (snmp_oid_is_prefixed(sorted[d]))
+       bound += 5;
+      bound += (int) sorted[d]->n_subid;
+    }
+
+    if (!no_inet_prefix)
+      bound += (ARRAY_SIZE(snmp_internet) + 1);
+
+    struct mib_walk_state walk;
+    mib_tree_walk_init(&walk, tree);
+
+    char buf[1024], buf2[1024];
+    struct oid *oid = (struct oid *) buf;
+    struct oid *last = (struct oid *) buf2;
+    memset(oid, 0, sizeof(struct oid));          /* create a null OID */
+    memset(last, 0, sizeof(struct oid));
+
+    int oid_index = 0;
+    if (size > 0  && snmp_is_oid_empty(sorted[oid_index]))
+      oid_index++;
+
+    mib_node_u *current;
+    int i = 0;
+    while ((current = mib_tree_walk_next(tree, &walk)) != NULL && i++ < bound)
+    {
+      memcpy(last, oid, snmp_oid_size(oid));
+      mib_tree_walk_to_oid(&walk, oid,
+         (1024 - sizeof(struct oid) / sizeof(u32)));
+
+      bt_assert(snmp_oid_compare(last, oid) < 0);
+
+      while (oid_index < distinct && nodes[oid_index] == NULL)
+       oid_index++;
+
+      if (oid_index < distinct && snmp_oid_compare(sorted[oid_index], oid) == 0)
+       oid_index++;
+    }
+
+    bt_assert(current == NULL);
+    while (oid_index < distinct && nodes[oid_index] == NULL)
+      oid_index++;
+
+    /* the bound check is only for that the loop is finite */
+    bt_assert(i <= bound + 2);
+
+    current = mib_tree_walk_next(tree, &walk);
+    bt_assert(current == NULL);
+    bt_assert(oid_index == distinct);
+
+    mb_free(oids);
+    mb_free(sorted);
+    mb_free(nodes);
+
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  tmp_flush();
+}
+
+static int
+t_tree_traversal(void)
+{
+  gen_test_traverse(random_prefixed_oid);
+  gen_test_traverse(random_no_prefix_oid);
+  gen_test_traverse(random_prefixable_oid);
+  gen_test_traverse(random_oid);
+
+  return 1;
+}
+
+static void
+gen_test_leafs(struct oid *(*generator)(void))
+{
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+
+  pool *pool = &root_pool;
+
+  for (int test = 0; test < TESTS_NUM; test++)
+  {
+    size_t tsz = ARRAY_SIZE(tree_sizes);
+
+    int size = tree_sizes[test % tsz];
+    int with_leafs = (test % (2 * tsz)) < tsz;
+    int no_inet_prefix = (test % (4 * tsz)) < (2 * tsz);
+
+    struct oid **oids = mb_alloc(pool, size * sizeof(struct oid *));
+    struct oid **sorted = mb_alloc(pool, size * sizeof(struct oid *));
+    mib_node_u **nodes = mb_allocz(pool, size * sizeof(mib_node_u *));
+
+    const int distinct = generate_oids(oids, sorted, size, generator);
+
+    struct mib_tree storage, *tree = &storage;
+    mib_tree_init(pool, tree);
+
+    if (no_inet_prefix)
+    {
+      /* remove the node .1 and all children */
+      const struct oid *inet_pref = oid_create(1, 0, 0, /* ids */ 1);
+      mib_tree_remove(tree, inet_pref);
+    }
+
+    for (int o = 0; o < size; o++)
+    {
+      int is_leaf = (with_leafs) ? (int) bt_random_n(2) : 0;
+      (void) mib_tree_add(pool, tree, oids[o], is_leaf);
+    }
+
+    int leafs = 0;
+    for (int d = 0; d < distinct; d++)
+    {
+      struct mib_walk_state walk;
+      mib_tree_walk_init(&walk, NULL);
+      nodes[d] = mib_tree_find(tree, &walk, sorted[d]);
+
+      /* count only leafs that was successfully inserted without duplicits */
+      if (nodes[d] != NULL && mib_node_is_leaf(nodes[d]))
+       leafs++;
+    }
+
+    struct mib_walk_state walk;
+    mib_tree_walk_init(&walk, tree);
+    if (!with_leafs)
+    {
+      struct mib_leaf *leaf = mib_tree_walk_next_leaf(tree, &walk, 0);
+      bt_assert(leaf == NULL);
+
+      continue;
+    }
+
+    char buf[1024], buf2[1024];
+    struct oid *oid = (struct oid *) buf;
+    struct oid *last = (struct oid *) buf2;
+    memset(oid, 0, sizeof(struct oid));          /* create a null OID */
+    memset(last, 0, sizeof(struct oid));
+
+    int oid_index = 0;
+
+    struct mib_leaf *current;
+    int i = 0; /* iteration counter ~ leafs found */
+    while ((current = mib_tree_walk_next_leaf(tree, &walk, 0)) != NULL && i++ < leafs)
+    {
+      memcpy(last, oid, snmp_oid_size(oid));
+      mib_tree_walk_to_oid(&walk, oid,
+         (1024 - sizeof(struct oid) / sizeof(u32)));
+
+      bt_assert(snmp_oid_compare(last, oid) < 0);
+      bt_assert(mib_node_is_leaf(((mib_node_u *)current)));
+
+      while (oid_index < distinct &&
+         (nodes[oid_index] == NULL || !mib_node_is_leaf(nodes[oid_index])))
+       oid_index++;
+
+      if (oid_index < distinct && snmp_oid_compare(sorted[oid_index], oid) == 0)
+       oid_index++;
+    }
+
+    bt_assert(current == NULL);
+    while (oid_index < distinct &&
+       (nodes[oid_index] == NULL || !mib_node_is_leaf(nodes[oid_index])))
+      oid_index++;
+
+    current = mib_tree_walk_next_leaf(tree, &walk, 0);
+    bt_assert(current == NULL);
+    bt_assert(oid_index == distinct);
+    bt_assert(i == leafs);
+
+    mb_free(oids);
+    mb_free(sorted);
+    mb_free(nodes);
+
+    lp_restore(tmp_linpool, &tmps);
+  }
+
+  tmp_flush();
+}
+
+static int
+t_tree_leafs(void)
+{
+
+  gen_test_leafs(random_prefixed_oid);
+  gen_test_leafs(random_no_prefix_oid);
+  gen_test_leafs(random_prefixable_oid);
+  gen_test_leafs(random_oid);
+
+  return 1;
+}
+
+#if 0
+static int
+t_tree_all(void)
+{
+  /* random sequences of insertion/deletion/searches and walks */
+  return 0; /* failed */
+}
+#endif
+
+
+int main(int argc, char **argv)
+{
+  bt_init(argc, argv);
+  bt_bird_init();
+
+  bt_test_suite(t_oid_empty, "Function that determines if the OID is empty");
+  bt_test_suite(t_oid_compare, "Function defining lexicographical order on OIDs");
+  bt_test_suite(t_varbind_name_to_tx, "Function loading OID from RX buffer with prefixation");
+  bt_test_suite(t_oid_ancestor, "Function finding common ancestor of two OIDs");
+  bt_test_suite(t_walk_to_oid, "Function transforming MIB tree walk state to OID");
+  bt_test_suite(t_walk_oid_desc, "Function testing relation being subtree between MIB tree walk and OID");
+  bt_test_suite(t_walk_oid_compare, "Function comparing MIB tree walk and OID");
+
+  bt_test_suite(t_tree_find, "MIB tree search");
+  bt_test_suite(t_tree_traversal, "MIB tree traversal");
+  bt_test_suite(t_tree_leafs, "MIB tree leafs traversal");
+  bt_test_suite(t_tree_add, "MIB tree insertion");
+  bt_test_suite(t_tree_delete, "MIB tree deletion");
+  bt_test_suite(t_tree_remove, "MIB tree removal");
+  //bt_test_suite(t_tree_all, "MIB tree random find, add, delete mix");
+
+  return bt_exit_value();
+}
diff --git a/proto/snmp/snmp_utils.c b/proto/snmp/snmp_utils.c
new file mode 100644 (file)
index 0000000..0a795da
--- /dev/null
@@ -0,0 +1,972 @@
+/*
+ *     BIRD -- Simple Network Management Protocol (SNMP) helper functions
+ *
+ *      (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ *      (c) 2022 CZ.NIC z.s.p.o
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ *
+ */
+
+#include "snmp_utils.h"
+#include <stdio.h>
+
+inline void
+snmp_pdu_context(struct snmp_pdu *pdu, struct snmp_proto *p, sock *sk)
+{
+  pdu->p = p;
+  pdu->error = AGENTX_RES_NO_ERROR;
+  pdu->buffer = sk->tpos;
+  pdu->size = sk->tbuf + sk->tbsize - sk->tpos;
+  pdu->index = 0;
+  pdu->sr_vb_start = NULL;
+  pdu->sr_o_end = NULL;
+}
+
+/*
+ * snmp_session - store packet ids from protocol to header
+ * @p: source SNMP protocol instance
+ * @h: dest PDU header
+ */
+inline void
+snmp_session(const struct snmp_proto *p, struct agentx_header *h)
+{
+  STORE_U32(h->session_id, p->session_id);
+  STORE_U32(h->transaction_id, p->transaction_id);
+  STORE_U32(h->packet_id, p->packet_id);
+}
+
+inline void *
+snmp_varbind_data(const struct agentx_varbind *vb)
+{
+  uint name_size = snmp_oid_size(&vb->name);
+  return (void *) &vb->name + name_size;
+}
+
+/*
+ * snmp_is_oid_empty - check if oid is null-valued
+ * @oid: object identifier to check
+ *
+ * Test if the oid header is full of zeroes. For NULL-pointer @oid returns 0.
+ * We ignore include field to prevent weird behaviour.
+ */
+inline int
+snmp_is_oid_empty(const struct oid *oid)
+{
+  /* We intentionaly ignore padding that should be zeroed */
+  if (oid != NULL)
+    return oid->n_subid == 0 && oid->prefix == 0;
+  else
+    return 0;
+}
+
+/*
+ * snmp_oid_is_prefixable - check for prefixed form conversion possibility
+ * @oid: Object Identifier in packet byte order to check
+ *
+ * Check if it is possible to convert @oid to prefixed form. The condition of
+ * that is standart .1.3.6.1 internet prefix and 5-th id that fits in one byte.
+ */
+inline int
+snmp_pkt_oid_is_prefixable(const struct oid *oid)
+{
+  if (LOAD_U8(oid->n_subid) < 5)
+    return 0;
+
+  for (int i = 0; i < 4; i++)
+    if (LOAD_U32(oid->ids[i]) != snmp_internet[i])
+      return 0;
+
+  if (LOAD_U32(oid->ids[4]) >= 256)
+    return 0;
+
+  return 1;
+}
+
+/*
+ * snmp_oid_copy - copy OID from one place to another
+ * @dest: destination to use
+ * @src: OID to be copied from
+ */
+void
+snmp_oid_copy(struct oid *dest, const struct oid *src)
+{
+  dest->n_subid = src->n_subid;
+  dest->prefix = src->prefix;
+  dest->include = src->include ? 1 : 0;
+  dest->reserved = 0;
+
+  memcpy(dest->ids, src->ids, src->n_subid * sizeof(u32));
+}
+
+/*
+ * snmp_oid_from_buf - copy OID from RX buffer to dest in native byte order
+ * @dst: destination to use (native byte order)
+ * @src: OID to be copied from (packet byte order)
+ */
+void
+snmp_oid_from_buf(struct oid *dst, const struct oid *src)
+{
+  dst->n_subid = LOAD_U8(src->n_subid);
+  dst->prefix = LOAD_U8(src->prefix);
+  dst->include = LOAD_U8(src->include) ? 1 : 0;
+  dst->reserved = 0;
+
+  for (uint i = 0; i < dst->n_subid; i++)
+    dst->ids[i] = LOAD_U32(src->ids[i]);
+}
+
+/*
+ * snmp_oid_to_buf - copy OID to TX buffer with packet byte order
+ * @dst: destination to use (packet byte order)
+ * @src: OID to be copied from (native byte order)
+ */
+void
+snmp_oid_to_buf(struct oid *dst, const struct oid *src)
+{
+  STORE_U8(dst->n_subid, src->n_subid);
+  STORE_U8(dst->prefix, src->prefix);
+  STORE_U8(dst->include, (src->include) ? 1 : 0);
+  STORE_U8(dst->reserved, 0);
+
+  for (uint i = 0; i < src->n_subid; i++)
+    STORE_U32(dst->ids[i], src->ids[i]);
+}
+
+/*
+ * snmp_str_size_from_len - return in-buffer octet string size
+ * @len: length of C-string, returned from strlen()
+ */
+inline size_t
+snmp_str_size_from_len(uint len)
+{
+  return 4 + BIRD_ALIGN(len, 4);
+}
+
+/*
+ * snmp_str_size - return in packet size of supplied string
+ * @str: measured string
+ *
+ * Returned value is string length aligned to 4 byte with 32bit length
+ * annotation included.
+ */
+inline size_t
+snmp_str_size(const char *str)
+{
+  return snmp_str_size_from_len(strlen(str));
+}
+
+/*
+ * snmp_oid_size - measure size of OID in bytes
+ * @o: object identifier to use
+ *
+ * Work for both packet and cpu native byte orders.
+ */
+uint
+snmp_oid_size(const struct oid *o)
+{
+  /* LOAD_U8() is in both cases basic mem read */
+  return 4 + (LOAD_U8(o->n_subid) * 4);
+}
+
+/*
+ * snmp_oid_size_from_len - return size of OID with @n_subid subids in bytes
+ * @n_subid: number of subids in ids array
+ */
+inline size_t
+snmp_oid_size_from_len(uint n_subid)
+{
+  return sizeof(struct oid) + n_subid * sizeof(u32);
+}
+
+static inline uint
+snmp_get_octet_size(const struct agentx_octet_str *str)
+{
+  return str->length;
+}
+
+/*
+ * snmp_varbind_header_size - measure size of VarBind without data in bytes
+ * @vb_name: VarBind OID name
+ *
+ * Return size including whole OID as well as the VarBind header.
+ */
+uint
+snmp_varbind_header_size(const struct oid *vb_name)
+{
+  ASSUME(vb_name);
+  return snmp_oid_size(vb_name) + OFFSETOF(struct agentx_varbind, name);
+}
+
+uint
+snmp_varbind_size_unsafe(const struct agentx_varbind *vb)
+{
+  ASSUME(snmp_test_varbind_type(vb->type));
+  int value_size = agentx_type_size(vb->type);
+  uint vb_header = snmp_varbind_header_size(&vb->name);
+
+  if (value_size == 0)
+    return vb_header;
+
+  if (value_size > 0)
+    return vb_header + value_size;
+
+  switch (vb->type)
+  {
+    case AGENTX_OBJECT_ID:;
+      struct oid *oid = snmp_varbind_data(vb);
+      return vb_header + snmp_oid_size(oid);
+
+    case AGENTX_OCTET_STRING:
+    case AGENTX_IP_ADDRESS:
+    case AGENTX_OPAQUE:;
+      struct agentx_octet_str *string = snmp_varbind_data(vb);
+      return vb_header + snmp_get_octet_size(string);
+
+    default:
+      /* Shouldn't happen */
+      die("getting size of VarBind with unknown type (%u)", vb->type);
+      return 0;
+  }
+}
+
+/*
+ * snmp_varbind_size_from_len - get size in-buffer VarBind for known OID and data
+ * @n_subid: number of subidentifiers of the VarBind's OID name
+ * @type: type of VarBind
+ * @len: length of variably long data
+ *
+ * For types with fixed size the @len is not used. For types such as Octet
+ * String, or OID the @len is used directly.
+ *
+ * Return number of bytes used by VarBind in specified form.
+ */
+inline size_t
+snmp_varbind_size_from_len(uint n_subid, enum agentx_type type, uint len)
+{
+  size_t sz = snmp_oid_size_from_len(n_subid)
+    + sizeof(struct agentx_varbind) - sizeof(struct oid);
+
+  int data_sz = agentx_type_size(type);
+  if (data_sz < 0)
+    sz += len;
+  else
+    sz += data_sz;
+
+  return sz;
+}
+
+/*
+ * snmp_test_varbind - test validity of VarBind type
+ * @type: Type of VarBind in cpu native byte order
+ */
+int
+snmp_test_varbind_type(u16 type)
+{
+  if (type == AGENTX_INTEGER  ||
+      type == AGENTX_OCTET_STRING  ||
+      type == AGENTX_NULL  ||
+      type == AGENTX_OBJECT_ID  ||
+      type == AGENTX_IP_ADDRESS  ||
+      type == AGENTX_COUNTER_32  ||
+      type == AGENTX_GAUGE_32  ||
+      type == AGENTX_TIME_TICKS  ||
+      type == AGENTX_OPAQUE  ||
+      type == AGENTX_COUNTER_64  ||
+      type == AGENTX_NO_SUCH_OBJECT  ||
+      type == AGENTX_NO_SUCH_INSTANCE  ||
+      type == AGENTX_END_OF_MIB_VIEW)
+    return 1;
+  else
+    return 0;
+}
+
+
+/*
+ * snmp_valid_ip4_index - check IPv4 address validity in oid
+ * @o: object identifier holding ip address
+ * @start: index of first address id
+ */
+int
+snmp_valid_ip4_index(const struct oid *o, uint start)
+{
+  if (start + 3 < o->n_subid)
+    return snmp_valid_ip4_index_unsafe(o, start);
+  else
+    return 0;
+}
+
+/*
+ * snmp_valid_ip4_index_unsafe - check validity of IPv4 address in oid
+ * @o: object identifier holding ip address
+ * @start: index of first address id
+ *
+ * This function is unsafe - no checks of object identifier ids
+ * length sufficiency is done.
+ */
+int
+snmp_valid_ip4_index_unsafe(const struct oid *o, uint start)
+{
+  for (int i = 0; i < 4; i++)
+    if (o->ids[start + i] >= 256)
+      return 0;
+
+  return 1;
+}
+
+/*
+ * snmp_put_nstr - copy c-string into buffer with limit
+ * @buf: destination buffer
+ * @str: string to use
+ * @len: number of characters to use from string
+ */
+byte *
+snmp_put_nstr(byte *buf, const char *str, uint len)
+{
+  uint alen = BIRD_ALIGN(len, 4);
+
+  struct agentx_octet_str *octet = (void *) buf;
+  STORE_U32(octet->length, len);
+  memcpy(&octet->data, str, len);
+  buf += len + sizeof(octet->length);
+
+  /* Insert zero padding in the gap at the end */
+  for (uint i = 0; i < alen - len; i++)
+    buf[i] = '\0';
+
+  return buf + (alen - len);
+}
+
+/*
+ * snmp_put_str - put string into SNMP PDU transcieve buffer
+ * @buf: pointer to first unoccupied buffer byte
+ * @str: string to place
+ *
+ * Handles all conditions specified by RFC, namely string length annotation
+ * and padding 4 byte alignment with zeroes. Return NULL if string is too large
+ * for SNMP message.
+ */
+byte *
+snmp_put_str(byte *buf, const char *str)
+{
+  uint len = strlen(str);
+  return snmp_put_nstr(buf, str, len);
+}
+
+byte *
+snmp_put_ip4(byte *buf, ip4_addr addr)
+{
+  /* octet string has size 4 bytes */
+  STATIC_ASSERT(sizeof(ip4_addr) == sizeof(u32));
+  STORE_PTR(buf, sizeof(ip4_addr));
+
+  /* Always use Network byte order */
+  put_u32(buf+4, ip4_to_u32(addr));
+
+  return buf + 8;
+}
+
+byte *
+snmp_put_blank(byte *buf)
+{
+  STORE_PTR(buf, 0);
+  return buf + 4;
+}
+
+/*
+ * snmp_put_fbyte - put one padded byte to SNMP PDU transcieve buffer
+ * @buf: pointer to free buffer byte
+ * @data: byte to use
+ *
+ * Put @data into buffer @buf with 3B zeroed padding.
+ */
+byte *
+snmp_put_fbyte(byte *buf, u8 data)
+{
+  STORE_U8(*buf++, data);
+  memset(buf, 0, 3); /* we fill the 24bit padding with zeros */
+  return buf + 3;
+}
+
+/**
+ * snmp_oid_compare - find the lexicographical order relation between @left and @right
+ * @left: left object id relation operand
+ * @right: right object id relation operand
+ *
+ * both @left and @right has to be non-blank.
+ * function returns 0 if left == right,
+ *   -1 if left < right,
+ *   and 1 otherwise
+ */
+int
+snmp_oid_compare(const struct oid *left, const struct oid *right)
+{
+  const u8 left_subids = left->n_subid;
+  u8 right_subids = right->n_subid; /* see hack for more info */
+
+  const u8 left_prefix = left->prefix;
+  const u8 right_prefix = right->prefix;
+
+  if (left_prefix == 0 && right_prefix == 0)
+    goto test_ids;
+
+  if (right_prefix == 0)
+    return (-1) * snmp_oid_compare(right, left);
+
+  if (left_prefix == 0)
+  {
+    uint bound = MIN((uint) left_subids, (uint) ARRAY_SIZE(snmp_internet));
+    for (uint idx = 0; idx < bound; idx++)
+    {
+      u32 id = left->ids[idx];
+      if (id < snmp_internet[idx])
+       return -1;
+      else if (id > snmp_internet[idx])
+       return 1;
+    }
+
+    if (left_subids <= ARRAY_SIZE(snmp_internet))
+      return -1;
+
+    /* check prefix */
+    if (left->ids[4] < (u32) right_prefix)
+      return -1;
+    else if (left->ids[4] > (u32) right_prefix)
+      return 1;
+
+    /* the right prefix is already checked (+1) */
+    int limit = MIN(left_subids - (int) (ARRAY_SIZE(snmp_internet) + 1),
+      (int) right_subids);
+    for (int i = 0; i < limit; i++)
+    {
+      u32 left_id = left->ids[i + ARRAY_SIZE(snmp_internet) + 1];
+      u32 right_id = right->ids[i];
+      if (left_id < right_id)
+       return -1;
+      else if (left_id > right_id)
+       return 1;
+    }
+
+    /* hack: we known at this point that right has >= 5 subids
+     *   (implicit in snmp_internet and oid->prefix), so
+     *   we simplify to common case by altering left_subids */
+    right_subids += 5;
+    goto all_same;
+  }
+
+  if (left_prefix < right_prefix)
+    return -1;
+  else if (left_prefix > right_prefix)
+    return 1;
+
+test_ids:
+  for (int i = 0; i < MIN(left->n_subid, right->n_subid); i++)
+  {
+    u32 left_id = left->ids[i];
+    u32 right_id = right->ids[i];
+    if (left_id < right_id)
+      return -1;
+    else if (left_id > right_id)
+      return 1;
+  }
+
+all_same:
+  /* shorter sequence is before longer in lexicografical order  */
+  if (left_subids < right_subids)
+    return -1;
+  else if (left_subids > right_subids)
+    return 1;
+  else
+    return 0;
+}
+
+struct snmp_registration *
+snmp_registration_create(struct snmp_proto *p, enum agentx_mibs mib)
+{
+  struct snmp_registration *r;
+  r = mb_alloc(p->p.pool, sizeof(struct snmp_registration));
+
+  r->n.prev = r->n.next = NULL;
+
+  r->session_id = p->session_id;
+  r->transaction_id = p->transaction_id;
+  /* will be incremented by snmp_session() macro during packet assembly */
+  r->packet_id = p->packet_id + 1;
+  r->mib = mib;
+
+  add_tail(&p->registration_queue, &r->n);
+
+  return r;
+}
+
+int
+snmp_registration_match(struct snmp_registration *r, struct agentx_header *h)
+{
+  return (LOAD_U32(r->session_id) == h->session_id) &&
+    (LOAD_U32(r->transaction_id) == h->transaction_id) &&
+    (LOAD_U32(r->packet_id) == h->packet_id);
+}
+
+
+/*
+ * agentx_type_size - get in packet VarBind type size
+ * @type: VarBind type
+ *
+ * Returns length of agentx_type @type in bytes, Variable length types result in
+ * -1.
+ */
+int
+agentx_type_size(enum agentx_type type)
+{
+  /*
+   * AGENTX_NULL, AGENTX_NO_SUCH_OBJECT, AGENTX_NO_SUCH_INSTANCE,
+   * AGENTX_END_OF_MIB_VIEW
+   */
+  if (type >= AGENTX_NO_SUCH_OBJECT || type == AGENTX_NULL)
+    return 0;
+
+  /* AGENTX_INTEGER, AGENTX_COUNTER_32, AGENTX_GAUGE_32, AGENTX_TIME_TICKS */
+  if (type >= AGENTX_COUNTER_32 && type <= AGENTX_TIME_TICKS ||
+      type == AGENTX_INTEGER)
+    return 4;
+
+  if (type == AGENTX_COUNTER_64)
+    return 8;
+
+  if (AGENTX_IP_ADDRESS)
+    return snmp_str_size_from_len(4);
+
+  /* AGENTX_OBJECT_ID, AGENTX_OCTET_STRING, AGENTX_OPAQUE */
+  else
+    return -1;
+}
+
+static inline void
+snmp_varbind_type32(struct agentx_varbind *vb, struct snmp_pdu *c, enum agentx_type type, u32 val)
+{
+  ASSUME(agentx_type_size(type) == 4); /* type as 4B representation */
+
+  vb->type = type;
+  u32 *data = snmp_varbind_data(vb);
+  STORE_PTR(data, val);
+  data++;
+  c->buffer = (byte *) data;
+}
+
+inline void
+snmp_varbind_int(struct snmp_pdu *c, u32 val)
+{
+  snmp_varbind_type32(c->sr_vb_start, c, AGENTX_INTEGER, val);
+}
+
+inline void
+snmp_varbind_counter32(struct snmp_pdu *c, u32 val)
+{
+  snmp_varbind_type32(c->sr_vb_start, c, AGENTX_COUNTER_32, val);
+}
+
+inline void
+snmp_varbind_ticks(struct snmp_pdu *c, u32 val)
+{
+  snmp_varbind_type32(c->sr_vb_start, c, AGENTX_TIME_TICKS, val);
+}
+
+inline void
+snmp_varbind_gauge32(struct snmp_pdu *c, s64 time)
+{
+  snmp_varbind_type32(c->sr_vb_start, c,
+    AGENTX_GAUGE_32, MAX(0, MIN(time, UINT32_MAX)));
+}
+
+inline void
+snmp_varbind_ip4(struct snmp_pdu *c, ip4_addr addr)
+{
+  c->sr_vb_start->type = AGENTX_IP_ADDRESS;
+  c->buffer = snmp_put_ip4(snmp_varbind_data(c->sr_vb_start), addr);
+}
+
+/*
+ * snmp_varbind_nstr - fill varbind context with octet string
+ * @vb: VarBind to use
+ * @c: PDU information
+ * @str: C-string to put as the VarBind data
+ * @len: length of the string @str
+ *
+ * Beware: this function assumes there is enough space in the underlaying
+ * TX buffer. The caller has to provide that, see snmp_str_size_from_len() for
+ * more info.
+ */
+void
+snmp_varbind_nstr(struct snmp_pdu *c, const char *str, uint len)
+{
+  c->sr_vb_start->type = AGENTX_OCTET_STRING;
+  c->buffer = snmp_put_nstr(snmp_varbind_data(c->sr_vb_start), str, len);
+}
+
+/*
+ * snmp_varbind_oid - fill VarBind data with OID @oid_val
+ * @oid_val - Object Identifier in cpu native byte order
+ *
+ * Function puts the @oid_val to the packet byte order.
+ */
+void
+snmp_varbind_oid(struct snmp_pdu *c, const struct oid *oid_val)
+{
+  c->sr_vb_start->type = AGENTX_OBJECT_ID;
+  snmp_oid_to_buf(snmp_varbind_data(c->sr_vb_start), oid_val);
+}
+
+inline enum agentx_type
+snmp_search_res_to_type(enum snmp_search_res r)
+{
+  ASSUME(r != SNMP_SEARCH_OK);
+  enum agentx_type type_arr[] = {
+    [SNMP_SEARCH_NO_OBJECT]   = AGENTX_NO_SUCH_OBJECT,
+    [SNMP_SEARCH_NO_INSTANCE] = AGENTX_NO_SUCH_INSTANCE,
+    [SNMP_SEARCH_END_OF_VIEW] = AGENTX_END_OF_MIB_VIEW,
+  };
+
+  return type_arr[r];
+}
+
+inline int
+snmp_test_close_reason(byte value)
+{
+  if (value >= (byte) AGENTX_CLOSE_OTHER &&
+      value <= (byte) AGENTX_CLOSE_BY_MANAGER)
+    return 1;
+  else
+    return 0;
+}
+
+
+/*
+ *  Debugging
+ */
+
+void UNUSED
+snmp_oid_dump(const struct oid *oid)
+{
+  log(L_WARN "OID DUMP ========");
+
+  if (oid == NULL)
+  {
+    log(L_WARN "is eqaul to NULL");
+    log(L_WARN "OID DUMP END ====");
+    log(L_WARN ".");
+    return;
+  }
+
+  else if (snmp_is_oid_empty(oid))
+  {
+    log(L_WARN "is empty");
+    log(L_WARN "OID DUMP END ====");
+    log(L_WARN ".");
+    return;
+  }
+
+  log(L_WARN "  #ids: %4u  prefix %3u  include: %5s",
+    oid->n_subid, oid->prefix, (oid->include)? "true" : "false");
+  log(L_WARN "IDS -------------");
+
+  for (int i = 0; i < oid->n_subid; i++)
+    log(L_WARN "  %2u:  %11u  ~ 0x%08X", i, oid->ids[i], oid->ids[i]);
+
+  log(L_WARN "OID DUMP END ====");
+  log(L_WARN);
+}
+
+void UNUSED
+snmp_oid_log(const struct oid *oid)
+{
+  char buf[1024] = { };
+  char *pos = buf;
+
+  if (snmp_oid_is_prefixed(oid))
+  {
+    for (uint i = 0; i < ARRAY_SIZE(snmp_internet); i++)
+      pos += snprintf(pos, buf + 1024 - pos, ".%u", snmp_internet[i]);
+
+    pos += snprintf(pos, buf + 1024 - pos, ".%u", oid->prefix);
+  }
+
+  for (int id = 0; id < oid->n_subid; id++)
+    pos += snprintf(pos, buf + 1024 - pos, ".%u", oid->ids[id]);
+
+  log(L_WARN, "%s", buf);
+}
+
+
+/*
+ * snmp_oid_common_ancestor - find a common ancestor
+ * @left: first OID
+ * @right: second OID
+ * @out: buffer for result
+ *
+ * The @out must be large enough to always fit the resulting OID, a safe value
+ * is minimum between number of left subids and right subids. The result might
+ * be NULL OID in cases where there is no common subid. The result could be also
+ * viewed as longest common prefix. Note that if both @left and @right are
+ * prefixable but not prefixed the result in @out will also not be prefixed.
+ *
+ * This function is used intensively by |snmp_test.c|.
+ */
+void
+snmp_oid_common_ancestor(const struct oid *left, const struct oid *right, struct oid *out)
+{
+  ASSERT(left && right && out);
+
+  out->include = 0;
+  out->reserved = 0;
+  out->prefix = 0;
+
+  u32 offset = 0;
+  u8 left_ids = left->n_subid, right_ids = right->n_subid;
+
+  int l = snmp_oid_is_prefixed(left), r = snmp_oid_is_prefixed(right);
+  if (l && r)
+  {
+    if (left->prefix != right->prefix)
+    {
+      out->n_subid = 4;
+
+      for (uint id = 0; id < ARRAY_SIZE(snmp_internet); id++)
+       out->ids[id] = snmp_internet[id];
+
+      return;
+    }
+
+    out->prefix = left->prefix;
+  }
+  else if (!l && r)
+  {
+    if (left_ids == 0)
+    {
+      /* finish creating NULL OID */
+      out->n_subid = 0;
+      return;
+    }
+
+    for (uint id = 0; id < MIN(ARRAY_SIZE(snmp_internet), left_ids); id++)
+    {
+      if (left->ids[id] != snmp_internet[id])
+      {
+       out->n_subid = id;
+       return;
+      }
+
+      out->ids[id] = snmp_internet[id];
+    }
+
+    if (left_ids <= ARRAY_SIZE(snmp_internet))
+    {
+      out->n_subid = left_ids;
+      return;
+    }
+
+    /* index 4 is conresponding to the prefix in prefixed OID */
+    if (left->ids[4] != (u32) right->prefix)
+    {
+      out->n_subid = ARRAY_SIZE(snmp_internet);
+      return;
+    }
+
+    /* delete snmp_internet from out->ids and store OID prefix */
+    offset = ARRAY_SIZE(snmp_internet) + 1;
+    out->n_subid = out->n_subid - ARRAY_SIZE(snmp_internet);
+    out->prefix = right->prefix;
+  }
+  else if (l && !r)
+  {
+    snmp_oid_common_ancestor(right, left, out);
+    return;
+  }
+
+  ASSERT(offset <= left_ids);
+
+  u8 subids = 0;
+  for (u32 id = 0; id < MIN(left_ids - offset, right_ids); id++)
+  {
+    if (left->ids[offset + id] == right->ids[id])
+    {
+      subids++;
+      out->ids[id] = right->ids[id];
+    }
+    else
+      break;
+  }
+  out->n_subid = subids;
+}
+
+/*
+ * SNMP MIB tree walking
+ */
+
+/**
+ * snmp_walk_init - Try to find exactly matching OID packat VarBind in MIB tree
+ * @tree: MIB tree to use
+ * @walk: MIB tree walk state storage
+ * @c: AgentX PDU creation context
+ *
+ * Populate the @walk state and try to find MIB tree leaf equivalent to
+ * c->sr_vb_start which is requested VarBind to fill based on it's OID name.
+ * Return value is either pointer to valid MIB tree leaf or NULL if no leaf
+ * matched.
+ */
+struct mib_leaf *
+snmp_walk_init(struct mib_tree *tree, struct mib_walk_state *walk, struct snmp_pdu *c)
+{
+  mib_tree_walk_init(walk, tree);
+
+  mib_node_u *node = mib_tree_find(tree, walk, &c->sr_vb_start->name);
+
+  // TODO hide me in mib_tree code
+  /* mib_tree_find() returns NULL if the oid is longer than existing any path */
+  if (node == NULL && walk->stack_pos > 0)
+    node = walk->stack[walk->stack_pos - 1];
+
+  return (!node || !mib_node_is_leaf(node)) ? NULL : &node->leaf;
+}
+
+/**
+ * snmp_walk_next - wrapper around MIB tree mib_walk_next() for single call
+ * @tree: MIB tree to use
+ * @walk: MIB tree walk state storage
+ * @c: AgentX PDU creation context
+ *
+ * The snmp_walk_next() function searches MIB tree with updates of the VarBind
+ * OID name with.
+ */
+struct mib_leaf *
+snmp_walk_next(struct mib_tree *tree, struct mib_walk_state *walk, struct snmp_pdu *c)
+{
+  ASSUME(tree && walk);
+
+  if (!walk->stack_pos)
+    return NULL;
+
+  mib_node_u *node = walk->stack[walk->stack_pos - 1];
+
+  int found = 0;
+  struct mib_leaf *leaf = &node->leaf;
+
+  if (mib_node_is_leaf(node) && leaf->call_next)
+  {
+    const struct oid *oid = &c->sr_vb_start->name;
+    if (mib_tree_walk_oid_compare(walk, oid) > 0)
+    {
+      int old = snmp_oid_size(&c->sr_vb_start->name);
+      if (mib_tree_walk_to_oid(walk,
+         &c->sr_vb_start->name, 20 * sizeof(u32)))
+       return NULL;
+
+      int new = snmp_oid_size(&c->sr_vb_start->name);
+      c->buffer += (new - old);
+    }
+
+    found = !leaf->call_next(walk, c);
+  }
+  else if (mib_node_is_leaf(node) && c->sr_vb_start->name.include)
+  {
+    found = 1;
+    c->sr_vb_start->name.include = 0;
+  }
+
+  const struct oid *oid = &c->sr_vb_start->name;
+  u32 skip = (walk->id_pos < oid->n_subid) ?
+    oid->ids[walk->id_pos] : 0;
+  while (!found && (leaf = mib_tree_walk_next_leaf(tree, walk, skip)) != NULL)
+  {
+    /* mib_tree_walk_next() forces VarBind's name OID overwriting */
+    int old = snmp_oid_size(&c->sr_vb_start->name);
+    // TODO autogrow
+    if (mib_tree_walk_to_oid(walk, &c->sr_vb_start->name, 20 * sizeof(u32)))
+      return NULL;
+
+    int new = snmp_oid_size(&c->sr_vb_start->name);
+    c->buffer += (new - old);
+
+    if (leaf->call_next && !leaf->call_next(walk, c))
+      found = 1;
+    else if (!leaf->call_next)
+      found = 1;
+
+    oid = &c->sr_vb_start->name;
+    skip = (walk->id_pos < oid->n_subid) ?
+      oid->ids[walk->id_pos] : 0;
+  }
+
+  if (!found)
+    return NULL;
+
+  return leaf;
+}
+
+/**
+ * snmp_walk_fill - fill current VarBind by filler hook invocation
+ * @leaf: MIB tree leaf with filler hook
+ * @walk: MIB tree walk state
+ * @c: AgentX PDU creation context
+ *
+ * The function takes responsibility for VarBind type setting (for known VB
+ * types) and for buffer space allocated for VarBind data (based on type or
+ * configured size). This simplifies code of filler hooks in most cases.
+ * We also allow the @leaf to be NULL, in which case we set the VarBind to
+ * error type noSuchObject.
+ */
+enum snmp_search_res
+snmp_walk_fill(struct mib_leaf *leaf, struct mib_walk_state *walk, struct snmp_pdu *c)
+{
+  struct agentx_varbind *vb = c->sr_vb_start;
+
+  enum snmp_search_res res;
+  /* The OID c->sr_vb_start->name is either left untouched for agentx-Get-PDU,
+   * or updated by snmp_walk_next() for agentx-GetNext-PDU and agentx-GetBulk-PDU
+   *
+   * The null OID in c->sr_o_end means no limits. The OID c->sr_o_end is always
+   * null for agentx-Get-PDU and therefore evaluates to 0.
+   */
+  if (!snmp_check_search_limit(&c->sr_vb_start->name, c->sr_o_end))
+  {
+    res = SNMP_SEARCH_END_OF_VIEW;
+    vb->type = snmp_search_res_to_type(res);
+    return res;
+  }
+
+  if (!leaf)
+    return SNMP_SEARCH_NO_OBJECT;
+
+  uint size = 0;
+  enum agentx_type type = AGENTX_NULL;
+  if (leaf->size >= 0)
+  {
+    if (leaf->type == AGENTX_OCTET_STRING || leaf->type == AGENTX_OPAQUE ||
+         leaf->type == AGENTX_OBJECT_ID)
+    {
+      type = leaf->type;
+      size = leaf->size;
+    }
+    else if (leaf->type != AGENTX_INVALID)
+    {
+      type = leaf->type;
+      size = agentx_type_size(leaf->type);
+    }
+    else
+      size = leaf->size;
+  }
+
+  (void) snmp_tbuf_reserve(c, size);
+  vb->type = (u16) type;
+
+  res = leaf->filler(walk, c);
+
+  vb = c->sr_vb_start;
+
+  if (res != SNMP_SEARCH_OK)
+    vb->type = snmp_search_res_to_type(res);
+
+  ASSUME(vb->type == leaf->type || vb->type == AGENTX_END_OF_MIB_VIEW ||
+    vb->type == AGENTX_NO_SUCH_OBJECT || vb->type == AGENTX_NO_SUCH_INSTANCE);
+
+  return res;
+}
diff --git a/proto/snmp/snmp_utils.h b/proto/snmp/snmp_utils.h
new file mode 100644 (file)
index 0000000..870b338
--- /dev/null
@@ -0,0 +1,127 @@
+#ifndef _BIRD_SNMP_UTILS_H_
+#define _BIRD_SNMP_UTILS_H_
+
+#include "subagent.h"
+#include "mib_tree.h"
+
+/*
+ *
+ *    AgentX Variable Biding (VarBind) utils
+ *
+ */
+
+/*
+ *  AgentX - Variable Binding (VarBind) type utils
+ */
+int agentx_type_size(enum agentx_type t);
+
+/* type Octet String */
+size_t snmp_str_size_from_len(uint len);
+size_t snmp_str_size(const char *str);
+
+/* type OID - Object Identifier */
+int snmp_is_oid_empty(const struct oid *oid);
+int snmp_pkt_oid_is_prefixable(const struct oid *oid);
+uint snmp_oid_size(const struct oid *o);
+size_t snmp_oid_size_from_len(uint n_subid);
+void snmp_oid_copy(struct oid *dest, const struct oid *src);
+int snmp_oid_compare(const struct oid *first, const struct oid *second);
+void snmp_oid_common_ancestor(const struct oid *left, const struct oid *right, struct oid *result);
+void snmp_oid_from_buf(struct oid *dest, const struct oid *src);
+void snmp_oid_to_buf(struct oid *dest, const struct oid *src);
+
+static inline int
+snmp_check_search_limit(const struct oid *search, const struct oid *limit)
+{
+  ASSERT(search && limit);
+  return snmp_is_oid_empty(limit) || snmp_oid_compare(search, limit) < 0;
+}
+
+/*
+ * snmp_oid_is_prefixed - test if OID is prefixed
+ * @oid: OID to use
+ *
+ * Works for both cpu native and packet byte order.
+ */
+static inline int
+snmp_oid_is_prefixed(const struct oid *oid)
+{
+  /* LOAD_U8() is in both cases basic mem load */
+  return LOAD_U8(oid->prefix) != 0;
+}
+
+/* type IPv4 */
+int snmp_valid_ip4_index(const struct oid *o, uint start);
+int snmp_valid_ip4_index_unsafe(const struct oid *o, uint start);
+
+/*
+ *  AgentX - Variable Binding (VarBind) manupulation
+ */
+uint snmp_varbind_header_size(const struct oid *vb_name);
+uint snmp_varbind_size_unsafe(const struct agentx_varbind *vb);
+size_t snmp_varbind_size_from_len(uint n_subid, enum agentx_type t, uint len);
+int snmp_test_varbind_type(u16 type);
+void *snmp_varbind_data(const struct agentx_varbind *vb);
+
+/*
+ *  AgentX - PDU headers, types, contexts
+ */
+void snmp_session(const struct snmp_proto *p, struct agentx_header *h);
+void snmp_pdu_context(struct snmp_pdu *pdu, struct snmp_proto *p, sock *sk);
+
+static inline int
+snmp_has_context(const struct agentx_header *h)
+{
+  return LOAD_U8(h->flags) & AGENTX_NON_DEFAULT_CONTEXT;
+}
+
+int snmp_test_close_reason(byte value);
+
+/*
+ *  AgentX - TX buffer manipulation
+ */
+
+/* Functions filling buffer a typed value */
+void snmp_varbind_int(struct snmp_pdu *c, u32 val);
+void snmp_varbind_counter32(struct snmp_pdu *c, u32 val);
+void snmp_varbind_gauge32(struct snmp_pdu *c, s64 time);
+void snmp_varbind_ticks(struct snmp_pdu *c, u32 val);
+void snmp_varbind_ip4(struct snmp_pdu *c, ip4_addr addr);
+void snmp_varbind_nstr(struct snmp_pdu *c, const char *str, uint len);
+void snmp_varbind_oid(struct snmp_pdu *c, const struct oid *oid_val);
+
+/* Raw */
+byte *snmp_no_such_object(byte *buf, struct agentx_varbind *vb, struct oid *oid);
+byte *snmp_no_such_instance(byte *buf, struct agentx_varbind *vb, struct oid *oid);
+byte *snmp_put_str(byte *buf, const char *str);
+byte *snmp_put_nstr(byte *buf, const char *str, uint len);
+byte *snmp_put_blank(byte *buf);
+byte *snmp_put_ip4(byte *buf, ip4_addr ip4);
+byte *snmp_put_fbyte(byte *buf, u8 data);
+
+
+/*
+ *
+ *    Helpers, Misc, Debugging
+ *
+ */
+struct snmp_registration *snmp_registration_create(struct snmp_proto *p, enum agentx_mibs mib);
+int snmp_registration_match(struct snmp_registration *r, struct agentx_header *h);
+
+void snmp_oid_dump(const struct oid *oid);
+void snmp_oid_log(const struct oid *oid);
+
+enum agentx_type snmp_search_res_to_type(enum snmp_search_res res);
+
+#define AGENTX_TYPE_INT_SIZE ((uint) agentx_type_size(AGENTX_INTEGER))
+#define AGENTX_TYPE_IP4_SIZE ((uint) agentx_type_size(AGENTX_IP_ADDRESS))
+#define AGENTX_TYPE_COUNTER32_SIZE ((uint) agentx_type_size(AGENTX_COUNTER_32))
+
+/*
+ * SNMP MIB tree walking
+ */
+struct mib_leaf *snmp_walk_init(struct mib_tree *tree, struct mib_walk_state *state, struct snmp_pdu *context);
+struct mib_leaf *snmp_walk_next(struct mib_tree *tree, struct mib_walk_state *state, struct snmp_pdu *context);
+enum snmp_search_res snmp_walk_fill(struct mib_leaf *leaf, struct mib_walk_state *state, struct snmp_pdu *context);
+
+#endif
diff --git a/proto/snmp/subagent.c b/proto/snmp/subagent.c
new file mode 100644 (file)
index 0000000..55f075c
--- /dev/null
@@ -0,0 +1,1426 @@
+/*
+ *     BIRD -- Simple Network Management Protocol (SNMP)
+ *
+ *      (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
+ *      (c) 2022 CZ.NIC z.s.p.o
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ *
+ */
+
+#include "subagent.h"
+#include "mib_tree.h"
+#include "snmp_utils.h"
+#include "bgp4_mib.h"
+
+#include "lib/unaligned.h"
+
+/*
+ * Handling of malformed packet:
+ *
+ * When we find an error in PDU data, we create and send a response with error
+ * defined by the RFC. We await until the packet is send and then we close the
+ * communication socket ignoring any possible response. This implicitly closes
+ * the established session. We chose this approach because we cannot easily
+ * mark the boundary between packets.
+ *
+ *
+ * Partial parsing:
+ *
+ * It may happen that we received only staring part of some PDU from the
+ * communication socket. In most cases, if we recognize this situation, we
+ *
+ */
+
+void snmp_register_ack(struct snmp_proto *p, struct agentx_response *res);
+
+/* standard SNMP internet prefix (.1.3.6.1) */
+const u32 snmp_internet[] = { SNMP_ISO, SNMP_ORG, SNMP_DOD, SNMP_INTERNET };
+
+/*
+ * update_packet_size - set PDU size
+ * @start: pointer to PDU data start (excluding header size)
+ * @end: pointer after the last PDU byte
+ *
+ * Return number of bytes in TX buffer (including header size).
+ */
+static inline uint
+update_packet_size(struct agentx_header *start, byte *end)
+{
+  /* pkt_len */
+  uint s = (end - (byte *) start) - AGENTX_HEADER_SIZE;
+  STORE_U32(start->payload, s);
+  return AGENTX_HEADER_SIZE + s;
+}
+
+/*
+ * response_err_ind - update response error and index
+ * @p: SNMP protocol instance
+ * @res: response PDU header
+ * @err: error status
+ * @ind: index of error, ignored for noAgentXError
+ *
+ * Update agentx-Response-PDU header fields res.error and it's res.index. If the
+ * error is not noError, also set the corrent response PDU payload size.
+ */
+static inline void
+response_err_ind(struct snmp_proto *p, struct agentx_response *res, enum agentx_response_errs err, u16 ind)
+{
+  STORE_U16(res->error, (u16) err);
+  if (err != AGENTX_RES_NO_ERROR && err != AGENTX_RES_GEN_ERROR)
+  {
+    if (p->verbose)
+      TRACE(D_PACKETS, "SNMP last PDU resulted in error %u", err);
+    STORE_U16(res->index, ind);
+    /* Reset VarBindList to null */
+    STORE_U32(res->h.payload,
+      sizeof(struct agentx_response) - AGENTX_HEADER_SIZE);
+  }
+  else if (err == AGENTX_RES_GEN_ERROR)
+  {
+    if (p->verbose)
+      TRACE(D_PACKETS, "SNMP last PDU resulted in error genErr");
+    STORE_U16(res->index, 0);
+    /* Reset VarBindList to null */
+    STORE_U32(res->h.payload,
+      sizeof(struct agentx_response) - AGENTX_HEADER_SIZE);
+  }
+  else
+    STORE_U16(res->index, 0);
+}
+
+/*
+ * snmp_varbind_leave - transform VarBind to packet byte order
+ * @vb: prepared VarBind in cpu native byte order
+ */
+void
+snmp_varbind_leave(struct agentx_varbind *vb)
+{
+  STORE_U16(vb->type, vb->type);
+
+  /* Does nothing */
+  STORE_U16(vb->reserved, 0);
+  struct oid *oid = &vb->name;
+  STORE_U8(oid->n_subid, oid->n_subid);
+  STORE_U8(oid->prefix, oid->prefix);
+  STORE_U8(oid->include, oid->include);
+  STORE_U8(oid->reserved, 0);
+
+  for (u8 i = 0; i < oid->n_subid; i++)
+    STORE_U32(oid->ids[i], oid->ids[i]);
+}
+
+/*
+ * snmp_tbuf_reserve - conditionally grow the TX buffer
+ * @c: transmit PDU context
+ * @size: size to make available
+ *
+ * Return non-zero if the buffer was relocated.
+ */
+int
+snmp_tbuf_reserve(struct snmp_pdu *c, size_t size)
+{
+  if (size >= c->size)
+  {
+    struct snmp_proto *p = c->p;
+    sock *sk = p->sock;
+
+    int start_diff;
+    if (c->sr_vb_start != NULL)
+      start_diff = (char *) c->sr_vb_start - (char *) sk->tbuf;
+
+    sk_set_tbsize(sk, sk->tbsize + 2048);
+    c->size += 2048;
+
+    if (c->sr_vb_start != NULL)
+      c->sr_vb_start = (struct agentx_varbind *) (sk->tbuf + start_diff);
+
+    return 1;
+  }
+
+  return 0;
+}
+
+/*
+ * refresh_ids - Copy current ids from packet to protocol
+ * @p: SNMP protocol instance
+ * @h: PDU header with new transaction_id and packet_id ids.
+ */
+static inline void
+refresh_ids(struct snmp_proto *p, struct agentx_header *h)
+{
+  p->transaction_id = LOAD_U32(h->transaction_id);
+  p->packet_id = LOAD_U32(h->packet_id);
+}
+
+/*
+ * snmp_header - store packet header into buffer
+ * @h: pointer to created packet header in TX buffer
+ * @type: created PDU type
+ * @flags: set flags
+ *
+ * Payload length is set to zero legth. Padding is also zeroed. Real stored
+ * flags depend on compile-time message byte order configuration.
+ */
+static inline void
+snmp_header(struct agentx_header *h, enum agentx_pdu_types type, u8 flags)
+{
+  STORE_U8(h->version, AGENTX_VERSION);
+  STORE_U8(h->type, type);
+  STORE_U8(h->flags, flags | SNMP_BYTE_ORDER);
+  STORE_U8(h->reserved, 0);
+  STORE_U32(h->payload, 0);
+}
+
+/*
+ * snmp_blank_header - create header with no flags except byte order
+ * @h: pointer to created header in TX buffer
+ * @type: create PDU type
+ *
+ * Only flag possibly set may be packet byte order configuration.
+ */
+static inline void
+snmp_blank_header(struct agentx_header *h, enum agentx_pdu_types type)
+{
+  snmp_header(h, type, (u8) 0);
+}
+
+/*
+ * prepare_response - fill buffer with AgentX PDU header
+ * @p: SNMP protocol instance
+ * @c: transmit PDU context to use
+ *
+ * Prepare known parts of AgentX packet header into the TX buffer held by @c.
+ */
+static struct agentx_response *
+prepare_response(struct snmp_proto *p, struct snmp_pdu *c)
+{
+  struct agentx_response *r = (void *) c->buffer;
+  struct agentx_header *h = &r->h;
+
+  snmp_blank_header(h, AGENTX_RESPONSE_PDU);
+  snmp_session(p, h);
+
+  /* protocol doesn't care about subagent upTime */
+  STORE_U32(r->uptime, 0);
+  STORE_U16(r->error, AGENTX_RES_NO_ERROR);
+  STORE_U16(r->index, 0);
+
+  ADVANCE(c->buffer, c->size, sizeof(struct agentx_response));
+  return r;
+}
+
+/*
+ * snmp_oid_prefixize_unsafe - normalize OID to prefixed form
+ * @dest: destination for normalized OID in native byte order
+ * @src: source OID in packet byte order
+ *
+ * Note that again, snmp_oid_prefixize_unsafe is intended to copy Object
+ * Identifier from RX buffer to TX buffer but also optionally swap the byte
+ * order from packet b.o. to cpu native b.o. This is done to simplify the code
+ * dealing with OIDs.
+ */
+static inline void
+snmp_oid_prefixize_unsafe(struct oid *dest, const struct oid *src)
+{
+  dest->n_subid = LOAD_U8(src->n_subid) - 5;
+  dest->prefix = (u8) LOAD_U32(src->ids[ARRAY_SIZE(snmp_internet)]);
+  dest->include = (LOAD_U8(src->include)) ? 1 : 0;
+  dest->reserved = 0;
+
+  /* The LOAD_U32() and STORE_U32() cancel out */
+  for (u8 i = 0; i < dest->n_subid; i++)
+    dest->ids[i] = LOAD_U32(src->ids[i + 5]);
+}
+
+/*
+ * snmp_vb_name_to_tx - create VarBind in TX buffer from RX buffer OID
+ * @c: PDU context
+ * @oid: Object Identifier located in RX buffer with packet byte order
+ *
+ * Create a NULL initialized VarBind inside TX buffer (from @c) whose name
+ * is @oid. Because we want to simplify code dealing with OIDs, the byte order
+ * of the name is optionally swapped to match cpu native byte order.
+ */
+struct agentx_varbind *
+snmp_vb_name_to_tx(struct snmp_pdu *c, const struct oid *oid)
+{
+  uint vb_hdr_size = snmp_varbind_header_size(oid);
+  (void) snmp_tbuf_reserve(c, vb_hdr_size);
+
+  ASSERT(c->size >= vb_hdr_size);
+  struct agentx_varbind *vb = (struct agentx_varbind *) c->buffer;
+  ADVANCE(c->buffer, c->size, sizeof(struct agentx_varbind) - sizeof(struct oid));
+  /* Move the c->buffer so that is points at &vb->name */
+  vb->type = AGENTX_NULL;
+
+  if (snmp_pkt_oid_is_prefixable(oid) && !snmp_oid_is_prefixed(oid))
+  {
+    u8 subids = LOAD_U8(oid->n_subid) - 5;
+    ADVANCE(c->buffer, c->size, snmp_oid_size_from_len(subids));
+    snmp_oid_prefixize_unsafe(&vb->name, oid);
+
+    return vb;
+  }
+
+  ADVANCE(c->buffer, c->size, snmp_oid_size(oid));
+  snmp_oid_from_buf(&vb->name, oid);
+
+  return vb;
+}
+
+// TODO XXX docstring
+int
+snmp_load_oids(byte **pkt_ptr, uint *pkt_sz, struct snmp_pdu *c)
+{
+  byte *pkt = *pkt_ptr;
+  uint pkt_size = *pkt_sz;
+
+  uint sz;
+  /* in packet byte order */
+  const struct oid *start_buf = (const struct oid *) pkt;
+  if ((sz = snmp_oid_size(start_buf)) > pkt_size ||
+      LOAD_U8(start_buf->n_subid) >= OID_MAX_LEN)
+  {
+    c->error = AGENTX_RES_PARSE_ERROR;
+    *pkt_ptr = pkt;
+    *pkt_sz = pkt_size;
+    return 0;
+  }
+
+  ADVANCE(pkt, pkt_size, sz);
+
+  /* in packet byte order */
+  const struct oid *end_buf = (const struct oid *) pkt;
+  if ((sz = snmp_oid_size(end_buf)) > pkt_size ||
+      LOAD_U8(end_buf->n_subid) >= OID_MAX_LEN)
+  {
+    c->error = AGENTX_RES_PARSE_ERROR;
+    *pkt_ptr = pkt;
+    *pkt_sz = pkt_size;
+    return 0;
+  }
+
+  /* in cpu native byte order */
+  struct agentx_varbind *start_vb = snmp_vb_name_to_tx(c, start_buf);
+
+  /* in cpu native byte order */
+  struct oid *end_oid = tmp_alloc(sz);
+  snmp_oid_from_buf(end_oid, end_buf);
+
+  ADVANCE(pkt, pkt_size, sz);
+
+  if (!snmp_is_oid_empty(end_oid) &&
+      snmp_oid_compare(&start_vb->name, end_oid) > 0)
+  {
+    c->error = AGENTX_RES_GEN_ERROR;
+    *pkt_ptr = pkt;
+    *pkt_sz = pkt_size;
+    return 0;
+  }
+
+  ASSERT(start_vb != NULL);
+  ASSERT(end_oid != NULL);
+
+  c->sr_vb_start = start_vb;
+  c->sr_o_end = end_oid;
+  *pkt_ptr = pkt;
+  *pkt_sz = pkt_size;
+  return 1; /* ok */
+}
+
+/*
+ * close_pdu - send an agentx-Close-PDU
+ * @p: SNMP protocol instance
+ * @reason: reason for closure
+ */
+static void
+close_pdu(struct snmp_proto *p, enum agentx_close_reasons reason)
+{
+  sock *sk = p->sock;
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, sk);
+
+  TRACE(D_PACKETS, "SNMP sending agentx-Close-PDU with reason %u", reason);
+
+#define REASON_SIZE sizeof(u32)
+  (void) snmp_tbuf_reserve(&c, AGENTX_HEADER_SIZE + REASON_SIZE);
+
+  struct agentx_header *h = (void *) c.buffer;
+  ADVANCE(c.buffer, c.size, AGENTX_HEADER_SIZE);
+  snmp_blank_header(h, AGENTX_CLOSE_PDU);
+  p->packet_id++;
+  snmp_session(p, h);
+
+  (void) snmp_put_fbyte(c.buffer, (u8) reason);
+  ADVANCE(c.buffer, c.size, REASON_SIZE);
+
+  uint s = update_packet_size(h, c.buffer);
+  sk_send(sk, s);
+#undef REASON_SIZE
+}
+
+/*
+ * ping_pdu - send an agentx-Ping-PDU
+ * @p: SNMP protocol instance
+ */
+void
+snmp_ping(struct snmp_proto *p)
+{
+  /* ping_pdu */
+  if (!snmp_is_active(p))
+    return;
+
+  sock *sk = p->sock;
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, sk);
+
+  if (c.size < AGENTX_HEADER_SIZE)
+    return;
+
+  int unused = sk->tbuf + sk->tbsize - c.buffer;
+  if (unused < AGENTX_HEADER_SIZE)
+    return;
+
+  struct agentx_header *h = (void *) c.buffer;
+  ADVANCE(c.buffer, c.size, AGENTX_HEADER_SIZE);
+  snmp_blank_header(h, AGENTX_PING_PDU);
+  p->packet_id++;
+  snmp_session(p, h);
+  if (p->verbose)
+    TRACE(D_PACKETS, "SNMP sending agentx-Ping-PDU");
+  p->ignore_ping_id = p->packet_id;
+
+  /* sending only header */
+  uint s = update_packet_size(h, (byte *) h + AGENTX_HEADER_SIZE);
+
+  if (p->packet_id)
+    p->pings++;
+  sk_send(sk, s);
+}
+
+/*
+ * snmp_simple_response - send an agentx-Response-PDU with no data payload
+ * @p: SNMP protocol instance
+ * @error: response PDU error fields value
+ * @index: response PDU error index field value
+ *
+ * This function assumes that the buffer has enough space to fill
+ * in the agentx-Response-PDU.
+ */
+static void
+snmp_simple_response(struct snmp_proto *p, enum agentx_response_errs error, u16 index)
+{
+  sock *sk = p->sock;
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, sk);
+
+  ASSUME(c.size >= sizeof(struct agentx_response));
+
+  struct agentx_response *res = prepare_response(p, &c);
+  response_err_ind(p, res, error, index);
+  sk_send(sk, sizeof(struct agentx_response));
+}
+
+/*
+ * send_notify_pdu - send an agentx-Notify-PDU
+ * @p: SNMP protocol instance
+ * @oid: PDU notification Varbind name (OID)
+ * @data: PDU VarBind payload in packet byte order
+ * @size: PDU VarBind payload size
+ * @include_up_time: flag enabling inclusion of sysUpTime.0 OID
+ */
+void
+snmp_notify_pdu(struct snmp_proto *p, struct oid *oid, void *data, uint size, int include_up_time)
+{
+  if (!snmp_is_active(p))
+    return;
+
+  sock *sk = p->sock;
+  TRACE(D_PACKETS, "SNMP sending agentx-Notify-PDU");
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, sk);
+
+#define UPTIME_SIZE sizeof(STATIC_OID(4)) /* see sys_up_time_0 */
+#define TRAP0_HEADER_SIZE sizeof(STATIC_OID(6)) /* see snmp_trap_oid_0 */
+
+  uint sz = AGENTX_HEADER_SIZE + TRAP0_HEADER_SIZE + snmp_oid_size(oid) \
+    + size;
+
+  if (include_up_time)
+    sz += UPTIME_SIZE;
+
+  /* Make sure that we have enough space in TX buffer */
+  (void) snmp_tbuf_reserve(&c, sz);
+
+  struct agentx_header *h = (struct agentx_header *) c.buffer;
+  ADVANCE(c.buffer, c.size, AGENTX_HEADER_SIZE);
+  snmp_blank_header(h, AGENTX_NOTIFY_PDU);
+  p->packet_id++;   /* New packet id */
+  snmp_session(p, h);
+
+  if (include_up_time)
+  {
+    /* sysUpTime.0 oid */
+    STATIC_OID(4) sys_up_time_0 = {
+      .n_subid = 4,
+      .prefix = SNMP_MGMT,
+      .include = 0,
+      .reserved = 0,
+      .ids = { SNMP_MIB_2, SNMP_SYSTEM, SNMP_SYS_UP_TIME, 0 },
+    };
+    struct oid *up_time_0 = (struct oid *) &sys_up_time_0;
+
+    struct agentx_varbind *vb = (struct agentx_varbind *) c.buffer;
+    snmp_oid_to_buf(&vb->name, up_time_0);
+
+    /* TODO use time from last reconfiguration instead? [config->load_time] */
+    btime uptime = current_time() - boot_time;
+    snmp_varbind_ticks(&c, (uptime TO_S) / 100);
+    ADVANCE(c.buffer, c.size, snmp_varbind_size_unsafe(vb));
+    STORE_U16(vb->type, vb->type);
+    /* We do not need to call the snmp_varbind_leave() because we used
+     * the packet byte order in the first place.
+     */
+  }
+
+  /* snmpTrapOID.0 oid */
+  STATIC_OID(6) snmp_trap_oid_0 = {
+    .n_subid = 6,
+    .prefix = SNMP_V2,
+    .include = 0,
+    .reserved = 0,
+    .ids = { SNMP_MODULES, SNMP_ALARM_NEXT_INDEX, SNMP_MIB_OBJECTS, SNMP_TRAP, SNMP_TRAP_OID, 0 },
+  };
+  struct oid *trap_0 = (struct oid *) &snmp_trap_oid_0;
+
+  struct agentx_varbind *trap_vb = (struct agentx_varbind *) c.buffer;
+  snmp_oid_to_buf(&trap_vb->name, trap_0);
+  /* snmp_oid_size() works for both byte orders same */
+  c.sr_vb_start = trap_vb;
+  snmp_varbind_oid(&c, oid);
+  ADVANCE(c.buffer, c.size, snmp_varbind_size_unsafe(trap_vb));
+  STORE_U16(trap_vb->type, trap_vb->type);
+  /* We do not need to call the snmp_varbind_leave() because we used the packet
+   * byte order in the first place.
+   */
+
+  memcpy(c.buffer, data, size);
+  ADVANCE(c.buffer, c.size, size);
+
+  uint s = update_packet_size(h, c.buffer);
+  sk_send(sk, s);
+
+#undef TRAP0_HEADER_SIZE
+#undef UPTIME_SIZE
+}
+
+/*
+ * un_register_pdu - common functionality for registration PDUs
+ * @p: SNMP protocol instance
+ * @oid: OID to register/unregister
+ * @bound: OIDs registration upper bound
+ * @index: OIDs registration n_subid index
+ * @type: register/unregister PDU type
+ * @is_instance: flag enabling instance registration (used only for register)
+ *
+ * Both register and unregister PDUs are capable of specifing a number of OIDs
+ * by using a pair of index and upper bound. The index (r.range_subid) points into
+ * the OID's n_subid array to ID being threated as variable. The upper bound
+ * (r.upper_bound) determins maximal value for n_subid selected by the index.
+ * The index and the upper bound are passed as @index, and @bound respectively.
+ *
+ * Zero value for @is_instance means we want to register/unregister OID as a MIB
+ * subtree, for nonzero value we are registering MIB tree an instance (leaf).
+ * Full name of PDUs are agentx-Register-PDU and agentx-Unregister-PDU.
+ *
+ * This function in internal and shoulnd't be used outside the SNMP module,
+ * see snmp_register() and snmp_unregister() functions.
+ */
+static void
+un_register_pdu(struct snmp_proto *p, struct oid *oid, u32 bound, uint index, enum agentx_pdu_types type, u8 is_instance)
+{
+  const struct snmp_config *cf = SKIP_BACK(struct snmp_config, cf, p->p.cf);
+  sock *sk = p->sock;
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, sk);
+
+#define BOUND_SIZE sizeof(u32)
+  uint sz = AGENTX_HEADER_SIZE + snmp_oid_size(oid) +
+      ((bound > 1) ? BOUND_SIZE : 0);
+
+  (void) snmp_tbuf_reserve(&c, sz);
+
+  struct agentx_header *h = (void *) c.buffer;
+  ADVANCE(c.buffer, c.size, AGENTX_HEADER_SIZE);
+
+  snmp_header(h, type, is_instance ? AGENTX_FLAG_INSTANCE_REGISTRATION : 0);
+  p->packet_id++;
+  snmp_session(p, h);
+
+  struct agentx_un_register_hdr *ur = (struct agentx_un_register_hdr *) c.buffer;
+
+  /* 0 = do not override session message timeout */
+  STORE_U8(ur->timeout, 0);
+  /* use selected priority */
+  STORE_U8(ur->priority, cf->priority);
+  STORE_U8(ur->range_subid, (bound > 1) ? index : 0);
+  STORE_U8(ur->reserved, 0);
+  ADVANCE(c.buffer, c.size, sizeof(struct agentx_un_register_hdr));
+
+  snmp_oid_to_buf((struct oid *) c.buffer, oid);
+  ADVANCE(c.buffer, c.size, snmp_oid_size(oid));
+
+  /* place upper-bound if needed */
+  if (bound > 1)
+  {
+    STORE_PTR(c.buffer, bound);
+    ADVANCE(c.buffer, c.size, BOUND_SIZE);
+  }
+
+  uint s = update_packet_size(h, c.buffer);
+
+  sk_send(sk, s);
+#undef BOUND_SIZE
+}
+
+/*
+ * snmp_unregister - send an agentx-Unregister-PDU
+ * @p: SNMP protocol instance
+ * @oid: OID to uregister
+ * @bound: OIDs unregistration upper bound
+ * @index: OIDs unregistration n_subid index
+ *
+ * For more detailed description see un_register_pdu() function.
+ */
+void UNUSED
+snmp_unregister(struct snmp_proto *p, struct oid *oid, u32 bound, uint index)
+{
+  TRACE(D_PACKETS, "SNMP sending agentx-Unregister-PDU");
+  un_register_pdu(p, oid, bound, index, AGENTX_UNREGISTER_PDU, 0);
+}
+
+/*
+ * snmp_register - send an agentx-Register-PDU
+ * @p: SNMP protocol instance
+ * @oid: OID to register
+ * @bound: OIDs registration upper bound
+ * @index: OIDs registration n_subid index
+ * @is_instance: flag enabling instance registration
+ *
+ * For more detailed description see un_register_pdu() function.
+ */
+void
+snmp_register(struct snmp_proto *p, struct oid *oid, u32 bound, uint index, u8 is_instance)
+{
+  TRACE(D_PACKETS, "SNMP sending agentx-Register-PDU");
+  un_register_pdu(p, oid, bound, index, AGENTX_REGISTER_PDU, is_instance);
+}
+
+/*
+ * open_pdu - send an agentx-Open-PDU
+ * @p: SNMP protocol instance
+ * @oid: PDU OID description field value
+ *
+ * Other fields are filled based on @p configuration (timeout, subagent
+ * description).
+ */
+static void
+open_pdu(struct snmp_proto *p, struct oid *oid)
+{
+  const struct snmp_config *cf = SKIP_BACK(struct snmp_config, cf, p->p.cf);
+  sock *sk = p->sock;
+
+  TRACE(D_PACKETS, "SNMP sending agentx-Open-PDU");
+
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, sk);
+
+#define TIMEOUT_SIZE sizeof(u32) /* 1B timeout, 3B zero padding */
+
+  /* Make sure that we have enough space in TX buffer */
+  uint s = AGENTX_HEADER_SIZE + TIMEOUT_SIZE + snmp_oid_size(oid) +
+    snmp_str_size(cf->description);
+
+  (void) snmp_tbuf_reserve(&c, s);
+
+  struct agentx_header *h = (void *) c.buffer;
+  ADVANCE(c.buffer, c.size, AGENTX_HEADER_SIZE);
+  snmp_blank_header(h, AGENTX_OPEN_PDU);
+
+  STORE_U32(h->session_id, 1);
+  STORE_U32(h->transaction_id, 1);
+  STORE_U32(h->packet_id, 1);
+
+  c.size -= (4 + snmp_oid_size(oid) + snmp_str_size(cf->description));
+
+  if (p->timeout >= 1 S && p->timeout <= 255 S)
+    /* use p->timeout ceiled up to whole second */
+    c.buffer = snmp_put_fbyte(c.buffer,
+      (p->timeout % (1 S) == 0) ? p->timeout TO_S : p->timeout TO_S + 1);
+  /* out of range fallbacks */
+  else if (p->timeout < 1 TO_US)
+    c.buffer = snmp_put_fbyte(c.buffer, (u8) 1);
+  else /* p->timeout > 255 TO_US */
+    c.buffer = snmp_put_fbyte(c.buffer, (u8) 255);
+
+  snmp_oid_to_buf((struct oid *) c.buffer, oid);
+  c.buffer += snmp_oid_size(oid);
+  c.buffer = snmp_put_str(c.buffer, cf->description);
+
+  s = update_packet_size(h, c.buffer);
+  sk_send(sk, s);
+#undef TIMEOUT_SIZE
+}
+
+/*
+ * parse_close_pdu - parse an agentx-Close-PDU
+ * @p: SNMP protocol instance
+ * @pkt_start: pointer to first byte of PDU
+ *
+ * Return number of bytes parsed from RX buffer.
+ */
+static uint
+parse_close_pdu(struct snmp_proto *p, byte * const pkt_start)
+{
+  byte *pkt = pkt_start;
+
+  struct agentx_close_pdu *pdu = (void *) pkt;
+  pkt += sizeof(struct agentx_close_pdu);
+  uint pkt_size = pdu->h.payload;
+
+  if (pkt_size != sizeof(struct agentx_close_pdu))
+  {
+    TRACE(D_PACKETS, "SNMP received agentx-Close-PDU that's malformed, closing anyway");
+    snmp_simple_response(p, AGENTX_RES_GEN_ERROR, 0);
+    snmp_reset(p);
+    return 0;
+  }
+
+  if (!snmp_test_close_reason(pdu->reason))
+  {
+    TRACE(D_PACKETS, "SNMP received agentx-Close-PDU with invalid close reason %u", pdu->reason);
+    snmp_simple_response(p, AGENTX_RES_GEN_ERROR, 0);
+    snmp_reset(p);
+    return 0;
+  }
+
+  enum agentx_close_reasons reason = (enum agentx_close_reasons) pdu->reason;
+  TRACE(D_PACKETS, "SNMP received agentx-Close-PDU with close reason %u", reason);
+  snmp_simple_response(p, AGENTX_RES_NO_ERROR, 0);
+  snmp_reset(p);
+  return pkt_size + AGENTX_HEADER_SIZE;
+}
+
+/*
+ * parse_sets_pdu - common functionality for commit set and undo set PDUs
+ * @p: SNMP protocol instance
+ * @pkt_start: pointer to first byte of on of set related PDU
+ * @error: error status to use
+ *
+ * Return number of bytes parsed from RX buffer.
+ */
+static uint
+parse_sets_pdu(struct snmp_proto *p, byte * const pkt_start, enum agentx_response_errs err)
+{
+  byte *pkt = pkt_start;
+  /* Presence of full header is guaranteed by parse_pkt() caller */
+  struct agentx_header *h = (void *) pkt;
+  pkt += AGENTX_HEADER_SIZE;
+  uint pkt_size = LOAD_U32(h->payload);
+
+  if (pkt_size != 0)
+  {
+    TRACE(D_PACKETS, "SNMP received PDU is malformed (size)");
+    snmp_simple_response(p, AGENTX_RES_PARSE_ERROR, 0);
+    snmp_reset(p);
+    return 0;
+  }
+
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, p->sock);
+  (void) snmp_tbuf_reserve(&c, sizeof(struct agentx_response));
+
+  struct agentx_response *r = prepare_response(p, &c);
+
+  // TODO free resource allocated by parse_test_set_pdu()
+  // TODO do something meaningful
+  //mb_free(tr);
+  c.error = err;
+
+  TRACE(D_PACKETS, "SNMP received PDU parsed with error %u", c.error);
+  response_err_ind(p, r, c.error, 0);
+  sk_send(p->sock, AGENTX_HEADER_SIZE);
+
+  /* Reset the connection on unrecoverable error */
+  if (c.error != AGENTX_RES_NO_ERROR && c.error != err)
+  {
+    snmp_reset(p);    /* error */
+    return 0;
+  }
+
+  return pkt - pkt_start;
+}
+
+/*
+ * parse_cleanup_set_pdu - parse an agentx-CleanupSet-PDU
+ * @p: SNMP protocol instance
+ * @pkt_start: pointer to first byte of PDU inside RX buffer
+ *
+ * Return number of bytes parsed from RX buffer.
+ */
+static uint
+parse_cleanup_set_pdu(struct snmp_proto *p, byte * const pkt_start)
+{
+  byte *pkt = pkt_start;
+  struct agentx_header *h = (void *) pkt;
+  uint pkt_size = LOAD_U32(h->payload);
+
+  /* errors are dropped silently, we must not send any agentx-Response-PDU */
+  if (pkt_size != 0)
+  {
+    return AGENTX_HEADER_SIZE;
+    TRACE(D_PACKETS, "SNMP received agentx-CleanupSet-PDU is malformed");
+    snmp_reset(p);
+    return 0;
+  }
+
+  TRACE(D_PACKETS, "SNMP received agentx-CleanupSet-PDU");
+  (void)p;
+  // TODO don't forget to free resources allocated by parse_test_set_pdu()
+  //mb_free(p->tr);
+  /* No agentx-Response-PDU is sent in response to agentx-CleanupSet-PDU */
+  return pkt_size;
+}
+
+/*
+ * parse_undo_set_pdu - parse an agentx-UndoSet-PDU
+ * @p: SNMP protocol instance
+ * @pkt: pointer to first byte of PDU inside RX buffer
+ *
+ * Return number of bytes parsed from buffer.
+ */
+static inline uint
+parse_undo_set_pdu(struct snmp_proto *p, byte *pkt)
+{
+  // don't forget to free resources allocated by parse_test_set_pdu()
+  //mb_free(tr);
+  TRACE(D_PACKETS, "SNMP received agentx-UndoSet-PDU");
+  return parse_sets_pdu(p, pkt, AGENTX_RES_UNDO_FAILED);
+}
+
+/*
+ * parse_commit_set_pdu - parse an agentx-CommitSet-PDU
+ * @p: SNMP protocol instance
+ * @pkt: pointer to first byte of PDU inside RX buffer
+ *
+ * Return number of bytes parsed from RX buffer.
+ */
+static inline uint
+parse_commit_set_pdu(struct snmp_proto *p, byte *pkt)
+{
+  // don't forget to free resoures allocated by parse_test_set_pdu()
+  //mb_free(tr);
+  TRACE(D_PACKETS, "SNMP received agentx-CommitSet-PDU");
+  return parse_sets_pdu(p, pkt, AGENTX_RES_COMMIT_FAILED);
+}
+
+/*
+ * parse_test_set_pdu - parse an agentx-TestSet-PDU in buffer
+ * @p: SNMP protocol instance
+ * @pkt_start: first byte of test set PDU
+ * @size: number of bytes received from a socket
+ *
+ * Return number of bytes parsed from RX buffer.
+ */
+static inline uint
+parse_test_set_pdu(struct snmp_proto *p, byte * const pkt_start)
+{
+  TRACE(D_PACKETS, "SNMP received agentx-TestSet-PDU");
+  byte *pkt = pkt_start;  /* pointer to agentx-TestSet-PDU in RX buffer */
+  uint s; /* final packat size */
+  struct agentx_response *res; /* pointer to reponse in TX buffer */
+
+  /* Presence of full header is guaranteed by parse_pkt() caller */
+  struct agentx_header *h = (void *) pkt;
+  pkt += AGENTX_HEADER_SIZE;
+
+  sock *sk = p->sock;
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, sk);
+
+  (void) snmp_tbuf_reserve(&c, AGENTX_HEADER_SIZE);
+
+  res = prepare_response(p, &c);
+
+  /* 0 if there is piece, that we cannot set */
+  int all_possible = 0;
+  /* the all_possible is currently hard-coded with no support for writing to mib
+   * variables, when implementing the mentioned support, change the initializer
+   * to 1
+   */
+  s = update_packet_size(h, c.buffer);
+
+  if (c.error != AGENTX_RES_NO_ERROR)
+  {
+    response_err_ind(p, res, c.error, c.index + 1);
+    snmp_reset(p);
+  }
+  else if (all_possible)
+  {
+    /* All values in the agentx-TestSet-PDU are OK, realy to commit them */
+    response_err_ind(p, res, AGENTX_RES_NO_ERROR, 0);
+  }
+  else
+  {
+    // Currently the only reachable branch
+    TRACE(D_PACKETS, "SNMP SET action failed (not writable)");
+    /* This is a recoverable error, we do not need to reset the connection */
+    response_err_ind(p, res, AGENTX_RES_NOT_WRITABLE, c.index + 1);
+  }
+
+  sk_send(sk, s);
+  return pkt - pkt_start;
+}
+
+/*
+ * AgentX GetPDU, GetNextPDU and GetBulkPDU
+ */
+
+/* agentx-Get-PDU */
+void
+snmp_get_pdu(struct snmp_proto *p, struct snmp_pdu *c, struct mib_walk_state *walk)
+{
+  struct mib_leaf *leaf;
+  leaf = snmp_walk_init(p->mib_tree, walk, c);
+
+  enum snmp_search_res res;
+  res = snmp_walk_fill(leaf, walk, c);
+
+  if (res != SNMP_SEARCH_OK)
+    c->sr_vb_start->type = snmp_search_res_to_type(res);
+}
+
+/* agentx-GetNext-PDU */
+int
+snmp_get_next_pdu(struct snmp_proto *p, struct snmp_pdu *c, struct mib_walk_state *walk)
+{
+  (void) snmp_walk_init(p->mib_tree, walk, c);
+  struct mib_leaf *leaf = snmp_walk_next(p->mib_tree, walk, c);
+
+  enum snmp_search_res res;
+  res = snmp_walk_fill(leaf, walk, c);
+
+  if (res != SNMP_SEARCH_OK)
+    c->sr_vb_start->type = AGENTX_END_OF_MIB_VIEW;
+
+  return res == SNMP_SEARCH_OK;
+}
+
+/* agentx-GetBulk-PDU */
+void
+snmp_get_bulk_pdu(struct snmp_proto *p, struct snmp_pdu *c, struct mib_walk_state *walk)
+{
+  /* TODO */
+  (void) p;
+  (void) c;
+  (void) walk;
+  //if (c->index >= bulk->getbulk.non_repeaters)
+  //  bulk->repeaters++;
+
+  // store the o_start and o_end
+
+  //bulk->has_any |= snmp_get_next_pdu(p, c, walk);
+}
+
+/**
+ * parse_gets_pdu - common parsing of received gets PDUs
+ * @p: SNMP protocol instance
+ * @pkt_start: pointer to first byte of received PDU
+ *
+ * Gets PDUs are agentx-Get-PDU, agentx-GetNext-PDU, agentx-GetBulk-PDU.
+ *
+ * Return number of bytes parsed from RX buffer
+ */
+static uint
+parse_gets_pdu(struct snmp_proto *p, byte * const pkt_start)
+{
+  struct mib_walk_state walk;
+  byte *pkt = pkt_start;
+
+  struct agentx_header *h = (void *) pkt;
+  pkt += AGENTX_HEADER_SIZE;
+  uint pkt_size = LOAD_U32(h->payload);
+
+  sock *sk = p->sock;
+  struct snmp_pdu c;
+  snmp_pdu_context(&c, p, sk);
+
+  /*
+   * Get-Bulk processing stops if all the varbind have type endOfMibView
+   * has_any is true if some varbind has type other than endOfMibView
+   */
+  struct agentx_bulk_state bulk_state = { 0 };
+  if (h->type == AGENTX_GET_BULK_PDU)
+  {
+    (void)bulk_state;
+    die("bulk");
+
+#if 0
+    if (pkt_size < sizeof(struct agentx_getbulk))
+    {
+      snmp_simple_response(p, AGENTX_RES_PARSE_ERROR, 0);
+      snmp_reset(p);
+      return pkt_size + AGENTX_HEADER_SIZE;
+    }
+
+    struct agentx_getbulk *bulk_info = (void *) pkt;
+    ADVANCE(pkt, pkt_size, sizeof(struct agentx_getbulk));
+
+    bulk_state = (struct agentx_bulk_state) {
+      .getbulk = {
+       .non_repeaters = LOAD_U32(bulk_info->non_repeaters),
+       .max_repetitions = LOAD_U32(bulk_info->max_repetitions),
+      },
+      /* In contrast to the RFC, we use 0-based indices. */
+      .index = 0,
+      .repetition = 0,
+      .has_any = 0,
+    };
+#endif
+  }
+
+  struct agentx_response *response_header = prepare_response(p, &c);
+
+  lp_state tmps;
+  lp_save(tmp_linpool, &tmps);
+  while (c.error == AGENTX_RES_NO_ERROR && pkt_size > 0)
+  {
+    lp_restore(tmp_linpool, &tmps);
+
+    if (!snmp_load_oids(&pkt, &pkt_size, &c))
+    {
+      snmp_simple_response(p, c.error,
+       (c.index > UINT16_MAX) ? UINT16_MAX : c.index);
+      snmp_reset(p);
+      return 0;
+    }
+
+    switch (h->type)
+    {
+      case AGENTX_GET_PDU:
+       snmp_get_pdu(p, &c, &walk);
+       break;
+
+      case AGENTX_GET_NEXT_PDU:
+       snmp_get_next_pdu(p, &c, &walk);
+       break;
+
+      case AGENTX_GET_BULK_PDU:
+       snmp_get_bulk_pdu(p, &c, &walk);
+       break;
+
+      default:
+       die("implementation failure");
+    }
+
+    snmp_varbind_leave(c.sr_vb_start);
+
+    c.sr_vb_start = NULL;
+    c.sr_o_end = NULL;
+
+    c.index++;
+  } /* while (c.error == AGENTX_RES_NO_ERROR && size > 0) */
+
+  lp_restore(tmp_linpool, &tmps);
+
+#if 0
+  if (h->type == AGENTX_GET_BULK_PDU)
+  {
+    // TODO: an error for now
+    die("bulk");
+  }
+#endif
+
+  /* We update the error, index pair on the beginning of the packet. */
+  response_err_ind(p, response_header, c.error, c.index + 1);
+  uint s = update_packet_size(&response_header->h, c.buffer);
+
+  /* We send the message in TX buffer. */
+  sk_send(sk, s);
+
+  // TODO think through the error state
+
+  /* number of bytes parsed from RX buffer */
+  return pkt - pkt_start;
+}
+
+/*
+ * do_response - act on agentx-Response-PDU and protocol state
+ * @p: SNMP protocol instance
+ * @pkt: RX buffer with PDU bytes
+ *
+ * Return number of bytes parsed from RX buffer.
+ */
+static void
+do_response(struct snmp_proto *p, byte *pkt)
+{
+  struct agentx_response *r = (void *) pkt;
+  struct agentx_header *h = (void *) r;
+
+  switch (p->state)
+  {
+    case SNMP_INIT:
+    case SNMP_LOCKED:
+      /* silent drop of received packet */
+      break;
+
+    case SNMP_OPEN:
+      /* copy session info from received packet */
+      p->session_id = LOAD_U32(h->session_id);
+      refresh_ids(p, h);
+
+      tm_start(p->ping_timer, 0);
+
+      /* the state needs to be changed before sending registering PDUs to
+       * use correct do_response action on them
+       */
+      snmp_set_state(p, SNMP_REGISTER);
+      break;
+
+    case SNMP_REGISTER:;
+      snmp_register_ack(p, r);
+      break;
+
+    case SNMP_CONN:
+      break;
+
+    case SNMP_STOP:
+    case SNMP_DOWN:
+      break;
+
+    default:
+      die("unkonwn SNMP state");
+  }
+}
+
+/*
+ * parse_response - parse an agentx-Response-PDU
+ * @p: SNMP protocol instance
+ * @res: pointer of agentx-Response-PDU header in RX buffer
+ *
+ * Return number of bytes parsed from RX buffer.
+ */
+static uint
+parse_response(struct snmp_proto *p, byte *res)
+{
+  struct agentx_response *r = (void *) res;
+  struct agentx_header *h = (void *) r;
+
+  uint pkt_size = LOAD_U32(h->payload);
+
+  if (p->ignore_ping_id && LOAD_U32(h->packet_id) == p->ignore_ping_id)
+  {
+    p->pings--;
+    p->ignore_ping_id = 0;
+  }
+
+  /* Number of agentx-Ping-PDU without response */
+  if (p->pings > 5)
+    snmp_reset(p);
+
+  switch (r->error)
+  {
+    case AGENTX_RES_NO_ERROR:
+      if (p->verbose || LOAD_U32(h->packet_id) != p->ignore_ping_id)
+       TRACE(D_PACKETS, "SNMP received agentx-Response-PDU");
+      do_response(p, res);
+      break;
+
+    /* Registration errors */
+    case AGENTX_RES_DUPLICATE_REGISTER:
+    case AGENTX_RES_REQUEST_DENIED:
+    case AGENTX_RES_UNKNOWN_REGISTER:
+      TRACE(D_PACKETS, "SNMP received agentx-Response-PDU with error %u", r->error);
+      snmp_register_ack(p, r);
+      break;
+
+    /*
+     * We found ourselves in an unexpected situation. To enter a well defined
+     * state as well as give the AgentX master agent room to fix the errors on
+     * his side, we perform a hard reset of the connections.
+     */
+    case AGENTX_RES_NOT_OPEN:
+    case AGENTX_RES_OPEN_FAILED:
+    case AGENTX_RES_UNKNOWN_AGENT_CAPS:
+    case AGENTX_RES_UNSUPPORTED_CONTEXT:  /* currently we don't use contexts */
+    case AGENTX_RES_PARSE_ERROR:
+    case AGENTX_RES_PROCESSING_ERR:
+    default:
+      TRACE(D_PACKETS, "SNMP received agentx-Response-PDU with unexepected error %u", r->error);
+      snmp_reset(p);
+      break;
+  }
+
+  return pkt_size + AGENTX_HEADER_SIZE;
+}
+
+/**
+ * parse_pkt - parse received AgentX packet
+ * @p: SNMP protocol instance
+ * @pkt: first byte of PDU inside RX buffer
+ * @size: number of bytes received from a socket
+ *
+ * Return number of bytes parsed from RX buffer.
+ */
+static uint
+parse_pkt(struct snmp_proto *p, byte *pkt, uint size)
+{
+  if (size < AGENTX_HEADER_SIZE)
+    return 0;
+
+  struct agentx_header *h = (struct agentx_header *) pkt;
+  if (h->flags & AGENTX_NETWORK_BYTE_ORDER != SNMP_BYTE_ORDER)
+  {
+    TRACE(D_PACKETS, "SNMP received PDU with unexpected byte order");
+    if (h->type != AGENTX_RESPONSE_PDU)
+      snmp_simple_response(p, AGENTX_RES_GEN_ERROR, 0);
+    snmp_reset(p);
+    return 0;
+  }
+
+  u32 pkt_size = LOAD_U32(h->payload);
+
+  /* RX side checks - too big packet */
+  if (pkt_size > SNMP_PKT_SIZE_MAX)
+  {
+    TRACE(D_PACKETS, "SNMP received PDU is too long");
+    if (h->type != AGENTX_RESPONSE_PDU)
+      snmp_simple_response(p, AGENTX_RES_GEN_ERROR, 0);
+    snmp_reset(p);
+    return 0;
+  }
+
+  /* This guarantees that we have the full packet already received */
+  if (size < pkt_size + AGENTX_HEADER_SIZE)
+    return 0; /* no bytes parsed */
+
+  /*
+   * We need to see the responses for PDU such as
+   * agentx-Open-PDU, agentx-Register-PDU, ...
+   * even when we are outside the SNMP_CONNECTED state
+   */
+  if (h->type == AGENTX_RESPONSE_PDU)
+    return parse_response(p, pkt);
+
+  ASSERT(snmp_is_active(p));
+  if (p->state != SNMP_CONN ||
+      p->session_id != LOAD_U32(h->session_id))
+  {
+    struct agentx_header copy = {
+      .session_id = p->session_id,
+      .transaction_id = p->transaction_id,
+      .packet_id = p->packet_id,
+    };
+
+    TRACE(D_PACKETS, "SNMP received PDU with unknown session id");
+    snmp_simple_response(p, AGENTX_RES_NOT_OPEN, 0);
+
+    p->session_id = copy.session_id;
+    p->transaction_id = copy.transaction_id;
+    p->packet_id = copy.packet_id;
+
+    /*
+     * After unexpected state, we simply reset the session
+     * only sending the agentx-Response-PDU.
+     */
+    snmp_reset(p);
+    return 0;
+  }
+
+  if (h->flags & AGENTX_NON_DEFAULT_CONTEXT)
+  {
+    TRACE(D_PACKETS, "SNMP received PDU with non-default context");
+    snmp_simple_response(p, AGENTX_RES_UNSUPPORTED_CONTEXT, 0);
+    snmp_reset(p);
+    return 0;
+  }
+
+  refresh_ids(p, h);
+  switch (LOAD_U8(h->type))
+  {
+    case AGENTX_GET_PDU:
+      TRACE(D_PACKETS, "SNMP received agentx-Get-PDU");
+      return parse_gets_pdu(p, pkt);
+
+    case AGENTX_GET_NEXT_PDU:
+      TRACE(D_PACKETS, "SNMP received agentx-GetNext-PDU");
+      return parse_gets_pdu(p, pkt);
+
+    case AGENTX_GET_BULK_PDU:
+      TRACE(D_PACKETS, "SNMP received agentx-GetBulk-PDU");
+      return parse_gets_pdu(p, pkt);
+
+    case AGENTX_CLOSE_PDU:
+      return parse_close_pdu(p, pkt);
+
+    case AGENTX_TEST_SET_PDU:
+      return parse_test_set_pdu(p, pkt);
+
+    case AGENTX_COMMIT_SET_PDU:
+      return parse_commit_set_pdu(p, pkt);
+
+    case AGENTX_UNDO_SET_PDU:
+      return parse_undo_set_pdu(p, pkt);
+
+    case AGENTX_CLEANUP_SET_PDU:
+      return parse_cleanup_set_pdu(p, pkt);
+
+    default:
+      /* We reset the connection for malformed packet (Unknown packet type) */
+      TRACE(D_PACKETS, "SNMP received PDU with unknown type (%u)", LOAD_U8(h->type));
+      snmp_reset(p);
+      return 0;
+  }
+}
+
+/*
+ * snmp_register_ack - handle registration response
+ * @p: SNMP protocol instance
+ * @res: header of agentx-Response-PDU
+ */
+void
+snmp_register_ack(struct snmp_proto *p, struct agentx_response *res)
+{
+  struct snmp_registration *reg;
+  WALK_LIST(reg, p->registration_queue)
+  {
+    if (snmp_registration_match(reg, &res->h))
+    {
+      rem_node(&reg->n);
+
+      if (res->error == AGENTX_RES_NO_ERROR && reg->reg_hook_ok)
+       reg->reg_hook_ok(p, res, reg);
+      else if (res->error != AGENTX_RES_NO_ERROR && reg->reg_hook_fail)
+       reg->reg_hook_fail(p, res, reg);
+
+      mb_free(reg);
+      break;
+    }
+  }
+
+  if (EMPTY_LIST(p->registration_queue))
+    snmp_up(p);
+}
+
+/**
+ * snmp_stop_subagent - close established session
+ * @p: SNMP protocol instance
+ *
+ * Send agentx-Close-PDU on established session.
+ */
+void
+snmp_stop_subagent(struct snmp_proto *p)
+{
+  tm_stop(p->ping_timer);
+  /* This cause problems with net-snmp daemon witch halts afterwards */
+  close_pdu(p, AGENTX_CLOSE_SHUTDOWN);
+}
+
+/**
+ * snmp_register_mibs - register all MIB subtrees
+ * @p: SNMP protocol instance
+ */
+void
+snmp_register_mibs(struct snmp_proto *p)
+{
+  snmp_bgp4_register(p);
+  ASSUME(!EMPTY_LIST(p->registration_queue));
+}
+
+/**
+ * snmp_start_subagent - send session open request
+ * @p: SNMP protocol instance
+ *
+ * Send agentx-Open-PDU with configured OID and string description.
+ */
+void
+snmp_start_subagent(struct snmp_proto *p)
+{
+  ASSUME(p->state == SNMP_OPEN);
+
+  /* blank oid means unsupported */
+  STATIC_OID(0) blank = { 0 };
+  open_pdu(p, (struct oid *) &blank);
+}
+
+/*
+ * snmp_rx - handle received PDUs in RX buffer in normal operation
+ * @sk: communication socket
+ * @size: number of bytes received
+ */
+int
+snmp_rx(sock *sk, uint size)
+{
+  struct snmp_proto *p = (struct snmp_proto *) sk->data;
+  byte *pkt_start = sk->rbuf;
+  byte *end = pkt_start + size;
+
+  while (snmp_is_active(p) && end >= pkt_start + AGENTX_HEADER_SIZE)
+  {
+    uint parsed_len = parse_pkt(p, pkt_start, size);
+
+    if (parsed_len == 0)
+      break;
+
+    pkt_start += parsed_len;
+    size -= parsed_len;
+  }
+
+  /* We flush the RX buffer on errors */
+  if (!snmp_is_active(p) || pkt_start == end)
+    return 1; /* The whole RX buffer was consumed */
+
+  /* Incomplete packet parsing */
+  memmove(sk->rbuf, pkt_start, size);
+  sk->rpos = sk->rbuf + size;
+  return 0;
+}
+
+/*
+ * space_for_response - check if TX buffer has space for agentx-Response-PDU
+ * @sk: communication socket owned by SNMP protocol instance
+ *
+ * In some cases we send only the AgentX header but if we want to signal an
+ * error, we need at least space for agentx-Response-PDU. This simplifies the
+ * PDU space requirements testing.
+ */
+static inline int
+space_for_response(const sock *sk)
+{
+  return (
+    (uint) (sk->tbuf + sk->tbsize - sk->tpos) >= sizeof(struct agentx_response)
+  );
+}
+
+/*
+ * snmp_tx - handle TX buffer
+ * @sk: communication socket owned by SNMP protocol instance
+ *
+ * The snmp_tx hook is used only to delay the processing in cases we don't have
+ * enough space in TX buffer. Therefore we simply call the snmp_rx hook.
+ */
+void
+snmp_tx(sock *sk)
+{
+  /* We still not have enough space */
+  if (!space_for_response(sk))
+    return;
+
+  /* There is nothing to process, no bytes in RX buffer */
+  if (sk_tx_buffer_empty(sk))
+    return;
+
+  snmp_rx(sk, sk->tpos - sk->tbuf);
+}
+
diff --git a/proto/snmp/subagent.h b/proto/snmp/subagent.h
new file mode 100644 (file)
index 0000000..7aab462
--- /dev/null
@@ -0,0 +1,359 @@
+
+#ifndef _BIRD_SNMP_SUBAGENT_H_
+#define _BIRD_SNMP_SUBAGENT_H_
+
+#include "nest/bird.h"
+#include "snmp.h"
+#include "lib/macro.h"
+
+#define AGENTX_VERSION              1
+
+/* standard snmp internet prefix */
+#define SNMP_ISO         1           /* last of oid .1               */
+#define SNMP_ORG         3           /* last of oid .1.3             */
+#define SNMP_DOD         6           /* last of oid .1.3.6           */
+#define SNMP_INTERNET    1           /* last of oid .1.3.6.1         */
+
+#define SNMP_MGMT        2           /* last of oid .1.3.6.1.2       */
+#define SNMP_MIB_2       1           /* last of oid .1.3.6.1.2.1     */
+#define SNMP_SYSTEM      1           /* last of oid .1.3.6.1.2.1.1   */
+#define SNMP_OSPF_MIB   14           /* last of oid .1.3.6.1.2.1.14  */
+#define SNMP_BGP4_MIB   15           /* last of oid .1.3.6.1.2.1.15  */
+#define SNMP_OSPFv3_MIB        192           /* last of oid .1.3.6.1.2.1.192 */
+
+/* sysUpTime */
+#define SNMP_SYS_UP_TIME  3          /* last of oid .1.3.6.1.2.1.1.3 */
+
+/* snmpTrapOID */
+#define SNMP_V2                  6           /* last of oid .1.3.6.1.6           */
+#define SNMP_MODULES     3           /* last of oid .1.3.6.1.6.3         */
+#define SNMP_ALARM_NEXT_INDEX 1              /* last of oid .1.3.6.1.6.3.1       */
+#define SNMP_MIB_OBJECTS  1          /* last of oid .1.3.6.1.6.3.1.1     */
+#define SNMP_TRAP        4           /* last of oid .1.3.6.1.6.3.1.1.4   */
+#define SNMP_TRAP_OID    1           /* last of oid .1.3.6.1.6.3.1.1.4.1 */
+
+extern const u32 snmp_internet[4];
+
+#define SNMP_DEFAULT_CONTEXT 0
+
+enum agentx_type {
+  AGENTX_INTEGER           =   2,
+  AGENTX_OCTET_STRING      =   4,
+  AGENTX_NULL              =   5,
+  AGENTX_OBJECT_ID         =   6,
+  AGENTX_IP_ADDRESS        =  64,
+  AGENTX_COUNTER_32        =  65,
+  AGENTX_GAUGE_32          =  66,
+  AGENTX_TIME_TICKS        =  67,
+  AGENTX_OPAQUE                    =  68,
+  AGENTX_COUNTER_64        =  70,
+  AGENTX_NO_SUCH_OBJECT            = 128,
+  AGENTX_NO_SUCH_INSTANCE   = 129,
+  AGENTX_END_OF_MIB_VIEW    = 130,
+
+  AGENTX_INVALID           =   0,
+} PACKED;
+
+enum snmp_search_res {
+  SNMP_SEARCH_OK         = 0,
+  SNMP_SEARCH_NO_OBJECT          = 1,
+  SNMP_SEARCH_NO_INSTANCE = 2,
+  SNMP_SEARCH_END_OF_VIEW = 3,
+};
+
+
+#define AGENTX_PRIORITY                127
+
+#define SNMP_REGISTER_TREE 0
+#define SNMP_REGISTER_INSTANCE 1
+
+enum agentx_flags {
+  AGENTX_FLAG_BLANK                = 0x00,
+  AGENTX_FLAG_INSTANCE_REGISTRATION = 0x01,
+  AGENTX_FLAG_NEW_INDEX                    = 0x02,
+  AGENTX_FLAG_ANY_INDEX                    = 0x04,
+  AGENTX_NON_DEFAULT_CONTEXT       = 0x08,
+  AGENTX_NETWORK_BYTE_ORDER        = 0x10,
+} PACKED;
+
+#define AGENTX_FLAGS_MASK (AGENTX_FLAG_INSTANCE_REGISTRATION                 \
+  | AGENTX_FLAG_NEW_INDEX                                                    \
+  | AGENTX_FLAG_ANY_INDEX                                                    \
+  | AGENTX_NON_DEFAULT_CONTEXT                                               \
+  | AGENTX_NETWORK_BYTE_ORDER)
+
+// TODO - make me compile time option
+#define SNMP_NETWORK_BYTE_ORDER
+
+#if !(defined(SNMP_NATIVE) || defined(SNMP_NETWORK_BYTE_ORDER))
+# error "SNMP: currently support only native byte order or network byte order."
+#endif
+
+#if defined(SNMP_NATIVE) && defined(SNMP_NETWORK_BYTE_ORDER) && !defined(CPU_BIG_ENDIAN)
+# error "SNMP: couldn't use both native byte order and network byte order " \
+  "(big endian) on little endian machine."
+#endif
+
+#if (defined(SNMP_NATIVE) && defined(CPU_BIG_ENDIAN)) || defined(SNMP_NETWORK_BYTE_ORDER)
+#define SNMP_BYTE_ORDER AGENTX_NETWORK_BYTE_ORDER
+#else
+#define SNMP_BYTE_ORDER 0
+#endif
+
+/* We recommend using STORE_U32 over VALUE_U32 when possible */
+#ifdef SNMP_NATIVE
+#define STORE_U32(dest, val)  ((u32) ((dest) = (u32) (val)))
+#define STORE_U16(dest, val)  ((u16) ((dest) = (u16) (val)))
+#define STORE_U8(dest, val)   ((u8) ((dest) = (u8) (val)))
+#define STORE_PTR(ptr, val)   (*((u32 *) (ptr)) = (u32) (val))
+
+#define VALUE_U32(val)       ((u32) (val))
+#define VALUE_U16(val)       ((u16) (val))
+#define VALUE_U8(val)        ((u8) (val))
+
+#define LOAD_U32(src)        *((u32 *) &(src))
+#define LOAD_U16(src)        *((u16 *) &(src))
+#define LOAD_U8(src)         *((u8 *) &(src))
+#define LOAD_PTR(ptr)        *((u32 *) (ptr))
+#endif
+
+#if  defined(SNMP_NETWORK_BYTE_ORDER) && (!defined(SNMP_NATIVE) || defined(CPU_BIG_ENDIAN))
+#define STORE_U32(dest, val)  put_u32(&(dest), (val))
+#define STORE_U16(dest, val)  put_u16(&(dest), (val))
+#define STORE_U8(dest, val)   put_u8(&(dest), (val))
+#define STORE_PTR(ptr, val)   put_u32(ptr, val)
+
+#define VALUE_U32(val)       htonl(val)
+#define VALUE_U16(val)       htons(val)
+#define VALUE_U8(val)        ((u8) (val))
+
+
+#define LOAD_U32(src)        get_u32(&(src))
+#define LOAD_U16(src)        get_u16(&(src))
+#define LOAD_U8(src)         get_u8(&(src))
+#define LOAD_PTR(src)        get_u32(ptr)
+#endif
+
+struct agentx_header {
+  u8 version;
+  u8 type;
+  u8 flags;
+  u8 reserved;         /* always zero filled */
+  u32 session_id;      /* AgentX sessionID established by Open-PDU */
+  u32 transaction_id;  /* last transactionID seen/used */
+  u32 packet_id;       /* last packetID seen/used */
+  u32 payload;         /* payload_length of the packet without header */
+};
+
+#define AGENTX_HEADER_SIZE 20
+STATIC_ASSERT(AGENTX_HEADER_SIZE == sizeof(struct agentx_header));
+
+struct oid {
+  u8 n_subid;
+  u8 prefix;
+  u8 include;
+  u8 reserved; /* always zero filled */
+  u32 ids[];
+};
+
+#define STATIC_OID(sbids)                                                    \
+  struct {                                                                   \
+    u8 n_subid;                                                                      \
+    u8 prefix;                                                               \
+    u8 include;                                                                      \
+    u8 reserved;                                                             \
+    u32 ids[sbids];                                                          \
+  }
+
+#define STATIC_OID_INITIALIZER(sbids, pref, ...)                             \
+  {                                                                          \
+    .n_subid = sbids,                                                        \
+    .prefix = pref,                                                          \
+    .include = 0,                                                            \
+    .reserved = 0,                                                           \
+    .ids = { __VA_ARGS__ },                                                  \
+  }
+
+/* enforced by MIB tree, see mib_tree.h for more info */
+#define OID_MAX_LEN 32
+
+/*
+ * AgentX VarBind -- Variable Binding
+ * During the processing of the VarBind, the fields @type and @name are in cpu
+ * native byte order. This should be fixed by running snmp_varbind_leave()
+ * before VarBind control pointer abondonment or before packet transmission.
+ * The data following the structure should always follow the packet byte order.
+ */
+struct agentx_varbind {
+  u16 type;
+  u16 reserved; /* always zero filled */
+  /* oid part */
+  struct oid name;
+  /* AgentX variable binding data optionally here */
+};
+
+/* AgentX Octet String */
+struct agentx_octet_str {
+  u32 length;
+  byte data[0];
+};
+
+struct agentx_response {
+  struct agentx_header h;
+  u32 uptime;
+  u16 error;
+  u16 index;
+};
+
+STATIC_ASSERT(4 + 2 + 2 + AGENTX_HEADER_SIZE == sizeof(struct agentx_response));
+
+struct agentx_open_pdu {
+  struct agentx_header h;
+  u8 timeout;
+  u8 reserved1;          /* reserved u24 */
+  u16 reserved2;  /* whole u24 is always zero filled */
+};
+
+struct agentx_close_pdu {
+  struct agentx_header h;
+  u8 reason;
+  u8 reserved1; /* reserved u24 */
+  u16 reserved2; /* whole u24 is always zero filled */
+};
+
+struct agentx_un_register_hdr {
+  u8 timeout;
+  u8 priority;
+  u8 range_subid;
+  u8 reserved; /* always zero filled */
+};
+
+struct agentx_getbulk {
+  u16 non_repeaters;
+  u16 max_repetitions;
+};
+
+struct agentx_bulk_state {
+  struct agentx_getbulk getbulk;
+  u16 index;
+  u16 repetition;
+  u32 repeaters;
+  int has_any;     /* flag is clear when all responses are EndOfMibView */
+};
+
+enum agentx_pdu_types {
+  AGENTX_OPEN_PDU              =  1,     /* agentx-Open-PDU */
+  AGENTX_CLOSE_PDU             =  2,     /* agentx-Close-PDU */
+  AGENTX_REGISTER_PDU          =  3,     /* agentx-Regiter-PDU */
+  AGENTX_UNREGISTER_PDU                =  4,     /* agentx-Unregister-PDU */
+  AGENTX_GET_PDU               =  5,     /* agentx-Get-PDU */
+  AGENTX_GET_NEXT_PDU          =  6,     /* agentx-GetNext-PDU */
+  AGENTX_GET_BULK_PDU          =  7,     /* agentx-GetBulk-PDU */
+  AGENTX_TEST_SET_PDU          =  8,     /* agentx-TestSet-PDU */
+  AGENTX_COMMIT_SET_PDU                =  9,     /* agentx-CommitSet-PDU */
+  AGENTX_UNDO_SET_PDU          = 10,     /* agentx-UndoSet-PDU */
+  AGENTX_CLEANUP_SET_PDU       = 11,     /* agentx-CleanupSet-PDU */
+  AGENTX_NOTIFY_PDU            = 12,     /* agentx-Notify-PDU */
+  AGENTX_PING_PDU              = 13,     /* agentx-Ping-PDU */
+  AGENTX_INDEX_ALLOCATE_PDU     = 14,    /* agentx-IndexAllocate-PDU */
+  AGENTX_INDEX_DEALLOCATE_PDU   = 15,    /* agentx-IndexDeallocate-PDU */
+  AGENTX_ADD_AGENT_CAPS_PDU     = 16,    /* agentx-AddAgentCaps-PDU */
+  AGENTX_REMOVE_AGENT_CAPS_PDU  = 17,    /* agentx-RemoveAgentCaps-PDU */
+  AGENTX_RESPONSE_PDU          = 18,     /* agentx-Response-PDU */
+} PACKED;
+
+/* agentx-Close-PDU close reasons */
+enum agentx_close_reasons {
+  AGENTX_CLOSE_OTHER         = 1,
+  AGENTX_CLOSE_PARSE_ERROR    = 2,
+  AGENTX_CLOSE_PROTOCOL_ERROR = 3,
+  AGENTX_CLOSE_TIMEOUTS              = 4,
+  AGENTX_CLOSE_SHUTDOWN              = 5,
+  AGENTX_CLOSE_BY_MANAGER     = 6,
+} PACKED;
+
+
+/* agentx-Response-PDU - result errors */
+enum agentx_response_errs {
+  /* response error to both Administrative and SNMP messages */
+  AGENTX_RES_NO_ERROR              =   0,      /* noAgentXError */
+  /* response errors to SNMP messages */
+  AGENTX_RES_GEN_ERROR             =   5,      /* genError */
+  AGENTX_RES_NO_ACCESS             =   6,      /* noAccess */
+  AGENTX_RES_WRONG_TYPE                    =   7,      /* wrongType */
+  AGENTX_RES_WRONG_LENGTH          =   8,      /* wrongLength */
+  AGENTX_RES_WRONG_ENCODING        =   9,      /* wrongEncoding */
+  AGENTX_RES_WRONG_VALUE           =  10,      /* wrongValue*/
+  AGENTX_RES_NO_CREATION           =  11,      /* noCreation */
+  AGENTX_RES_INCONSISTENT_VALUE            =  12,      /* inconsistentValue */
+  AGENTX_RES_RESOURCE_UNAVAILABLE   =  13,     /* resourceUnavailable */
+  AGENTX_RES_COMMIT_FAILED         =  14,      /* commitFailed */
+  AGENTX_RES_UNDO_FAILED           =  15,      /* undoFailed */
+  AGENTX_RES_NOT_WRITABLE          =  17,      /* notWritable */
+  AGENTX_RES_INCONSISTENT_NAME     =  18,      /* inconsistentName */
+  /* response error to Administrative messages */
+  AGENTX_RES_OPEN_FAILED           = 256,      /* openFailed */
+  AGENTX_RES_NOT_OPEN              = 257,      /* notOpen */
+  AGENTX_RES_INDEX_WRONG_TYPE      = 258,      /* indexWrongType */
+  AGENTX_RES_INDEX_ALREADY_ALLOC    = 259,     /* indexAlreadyAlloc */
+  AGENTX_RES_INDEX_NONE_AVAIL      = 260,      /* indexNoneAvail */
+  AGENTX_RES_NOT_ALLOCATED         = 261,      /* notAllocated */
+  AGENTX_RES_UNSUPPORTED_CONTEXT    = 262,     /* unsupportedContext */
+  AGENTX_RES_DUPLICATE_REGISTER            = 263,      /* duplicateRegister */
+  AGENTX_RES_UNKNOWN_REGISTER      = 264,      /* unknownRegister */
+  AGENTX_RES_UNKNOWN_AGENT_CAPS            = 265,      /* unknownAgentCaps */
+  AGENTX_RES_PARSE_ERROR           = 266,      /* parseError */
+  AGENTX_RES_REQUEST_DENIED        = 267,      /* requestDenied */
+  AGENTX_RES_PROCESSING_ERR        = 268,      /* processingError */
+} PACKED;
+
+/* SNMP PDU info */
+struct snmp_pdu {
+  struct snmp_proto *p;
+
+  /* TX buffer */
+  byte *buffer;                            /* pointer to buffer */
+  uint size;                       /* unused space in buffer */
+
+  /* Search Range */
+  struct agentx_varbind *sr_vb_start; /* search range starting OID inside TX buffer (final storage) */
+  const struct oid *sr_o_end;        /* search range ending OID */
+
+  /* Control */
+  enum agentx_response_errs error;  /* storage for result of current action */
+  u32 index;                       /* index on which the error was found */
+};
+
+#if 0
+struct snmp_packet_info {
+  node n;
+  u8 type; // enum type
+  u32 session_id;
+  u32 transaction_id;
+  u32 packet_id;
+  void *data;
+};
+#endif
+
+void snmp_start_subagent(struct snmp_proto *p);
+void snmp_stop_subagent(struct snmp_proto *p);
+void snmp_ping(struct snmp_proto *p);
+int snmp_rx(sock *sk, uint size);
+void snmp_tx(sock *sk);
+void snmp_register(struct snmp_proto *p, struct oid *oid, uint index, uint len, u8 is_instance);
+void snmp_unregister(struct snmp_proto *p, struct oid *oid, uint index, uint len);
+void snmp_notify_pdu(struct snmp_proto *p, struct oid *oid, void *data, uint size, int include_uptime);
+void snmp_register_mibs(struct snmp_proto *p);
+struct agentx_varbind *snmp_vb_name_to_tx(struct snmp_pdu *c, const struct oid *oid);
+int snmp_tbuf_reserve(struct snmp_pdu *c, size_t bytes);
+
+static inline int
+snmp_is_active(const struct snmp_proto *p)
+{
+  /* Note: states in which we have opened socket */
+  return p->state == SNMP_OPEN || p->state == SNMP_REGISTER ||
+      p->state == SNMP_CONN;
+}
+
+
+#endif