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'`
}
</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:]<address>[,...]
+agentXPerms <sockperms> [<dirperms> [<user>|<uid> [<group>|<gid>]]]
+agentaddress [<trasport-type>:]<trasport-address>[,...]
+</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 <engineid>] <username> (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224) <authpassphrase> [DES|AES] [<pass>]
+rwuser [-s secmodel] <user> [noauth|auth|priv [<oid> | -V <view> [<context>]]]
+rouser [-s secmodel] <user> [noauth|auth|priv [<oid> | -V <view> [<context>]]]
+</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 <community> [<source> [<oid> | -V <view> [<context>]]]
+rocommunity <community> [<source> [<oid> | -V <view> [<context>]]]
+rwcommunity6 <community> [<source> [<oid> | -V <view> [<context>]]]
+rocommunity6 <community> [<source> [<oid> | -V <view> [<context>]]]
+view <vname> (include|exclude) <oid> [<mask>]
+</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 [<name>] {
+ agentx master address (default|<unix_path>|<ip> [port <port>]);
+ subagent description <text>;
+ source address <ip>;
+ registration priority <num>;
+ message timeout <time>;
+ start delay time <time>;
+ verbose <switch>;
+
+ mib bgp4 {
+ local as <num>;
+ local router id <ip4>;
+ peer <name>;
+ };
+}
+</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 <community>
+defSecurityName <username>
+defAuthType (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224)
+defAuthPassphrase <authpass>
+defPrivType (DES|AES)
+defPrivPassphrase <privpass>
+clientaddr [<transport-type>:]<transport-address>
+</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>
+$ # <snmputil> <master-address> <oid>[ <oid> [...]]
+$ 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">
--- /dev/null
+defSecurityName snmp_name
+defAuthType MD5
+defAuthPassphrase test_pass
+defSecurityLevel noAuthNoPriv
+defVersion 3
--- /dev/null
+#
+# 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
+
PROTOCOL_RADV,
PROTOCOL_RIP,
PROTOCOL_RPKI,
+ PROTOCOL_SNMP,
PROTOCOL_STATIC,
PROTOCOL__MAX
};
*/
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
C radv
C rip
C rpki
+C snmp
C static
S ../nest/rt-dev.c
--- /dev/null
+S snmp.c
+S subagent.c
+S snmp_utils.c
+S mib_tree.c
+S bgp4_mib.c
--- /dev/null
+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)
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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
+
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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(®->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);
+}
+
--- /dev/null
+
+#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