0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-22 01:31:55 +00:00
The BIRD protocol SNMP makes it possible to retrieve management information
through SNMP. This is accomplished by implementing AgentX protocol. The BIRD
acts as an AgentX subagent, registers to master agent and provides management
information. Master agent handles SNMP communication and forwards request to
registered subagents. You will therefore need an additional component -- a SNMP
daemon capable of acting as AgentX master agent. In theory, the information
consumer don't have to support SNMP and could be very simple master agent for
logging/monitoring the BIRD state. For more detail see provided documentation.

This commit is squashed version of development history. Full development history
could be found on branch `proto-snmp'.
This commit is contained in:
Vojtech Vilimek 2024-08-21 11:31:24 +02:00
parent 6dd986587a
commit 4a500725a1
20 changed files with 8379 additions and 4 deletions

View File

@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then
fi
fi
all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki static"
all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki snmp static"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`

View File

@ -6198,6 +6198,358 @@ protocol static {
}
</code>
<sect>SNMP
<label id="snmp">
<p>The Simple Network Management Protocol is protocol for collecting and
managing network devices. Managed information is divided into so called MIBs --
Management Information Bases. Each MIB describe information and semantics it
provides. The SNMP architecture is very flexible, some MIB are standartized by
IETF, others published by independent third parties.
<p>The BIRD SNMP support is achieved by an additional component -- a SNMP daemon
with AgentX protocol support. The SNMP daemon acts as an AgentX master agent and
deal with user authentication, access control and managing MIB regions in OID
tree. BIRD instance acts as AgentX subagent, register configurated MIBs and
provides queried data. The AgentX communication protocol between master agent
and subagents does not consider security what so ever. It is therefore upon the
user to use the AgentX in a secure way. This can be achieved either by using
Unix Domain sockets on same host or by using secure tunnel. Note that following
sections containing pieces of Net-SNMP configuraiton are only meant as a helper
for cold start, or as a pointer what to search for, not as full reference. For
full reference consult the original manpages.
<sect1>SNMP Daemon configuration
<p>We recommend you to use Net-SNMP implementation of daemon and utilities, quick
guide below assume that. Net-SNMP implementation is quite popular so you should
find it's packages inside your distribution package manager.
<sect2>Example snmpd configuration
<p>
<code>
# file /etc/snmp/snmpd.conf
# minimal SNMPv3 config
agentx master
agentaddress udp:192.0.2.64
agentXSocket tcp:198.51.100.2
createUser snmp_name MD5 example_pass
rwuser snmp_name noauth
</code>
<sect2>AgentX Enabling
<p>
<code>
# File /etc/snmp/snmpd.conf
agentx master
agentXSocket [unix:|tcp:|tcp6:]&lt;address&gt;[,...]
agentXPerms &lt;sockperms&gt; [&lt;dirperms&gt; [&lt;user&gt;|&lt;uid&gt; [&lt;group&gt;|&lt;gid&gt;]]]
agentaddress [&lt;trasport-type&gt;:]&lt;trasport-address&gt;[,...]
</code>
<descrip>
<tag><label id="snmpd-agentx-master">agentx master</tag>
SNMP daemon will enable AgentX functionality and start listening on
configured AgentX address.
<tag><label id="snmpd-agentxsocket">agentXSocket
[unix:|tcp:|tcp6:]<m/trasport-address/[,...] </tag>
Define address to listen for AgentX subagent. Use one of <cf>unix:,
tcp:, tcp6:</cf> transport type. Other transport type are not supported
by BIRD and also not mentioned in AgentX RFC, see <rfc id="2741">.
Default: Unix Domain socket <file>/var/run/agentx/master</file>.
<tag><label id="snmpd-agentxperms">agentXPerms <m/sockperms/
[<m/dirperms/ [<m/user/|<m/uid/ [<m/group/|<m/gid/]]]</tag>
Define common permissions for AgentX listening Unix Domain sockets. Both
<m/sockperms/ and <m/dirperms/ must be octal digits like for
<m/chmod(1)/. Option <m/user/ is string and <m/uid/ is numeric user id.
Same for <m/group/ and <m/gid/.
<tag><label id="snmpd-agentaddress">agentaddress
[<m/transport-type/:]<m/trasport-address/[,...]</tag>
Define address, or list of addresses, to listen for SNMP requests (send
for example by <it/snmpwalk(1)/). You most likely want <m/trasport-type/ to
be one from <cf/udp:/, <cf/udp6:/, <cf/tcp:/, <cf/tcp6:/, <cf/unix:/,
<cf/ssh:/ but Net-SNMP support even more transport types.
Value <m/transport-address/ define address, Net-SNMP should be able to
derive <m/trasport-type/ from <m/transport-address/. Beware that for
Unix Domain socket derivation to work, the path must start with /. Also
note that the working directory of snmpd daemon is filesystem root.
Default: UDP on all IPv4 interfaces on port 161. (e.g.
<cf>agentaddress udp6:localhost:161</cf>,
<cf>agentaddress tcp:192.0.2.1</cf>,
<cf>agentaddress /var/run/mydir/agentx_master</cf>,
<cf>agentaddress localhost,/p/u1,/p/u2</cf>).
</descrip>
<sect2>Configure access
<p>You can use the SNMPv3 USM module for user authorization, or use simpler older
version SNMPv1/SNMPv2c with authentication by community. Other means of
authorization are also possible (e.g. external Kerberos) but out of scope of
this guide.
<sect3>SNMPv3 USM
<p>
<code>
# file /etc/snmp/snmpd.conf (continuation)
createUser [-e &lt;engineid&gt;] &lt;username&gt; (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224) &lt;authpassphrase&gt; [DES|AES] [&lt;pass&gt;]
rwuser [-s secmodel] &lt;user&gt; [noauth|auth|priv [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
rouser [-s secmodel] &lt;user&gt; [noauth|auth|priv [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
</code>
<descrip>
<tag><label id="snmpd-createuser">createUser [-e <m/engineid/]
<m/username/ (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224)
<m/authpassphrase/ [DES|AES] [<m/privpassphrase/] </tag>
Create user with in order specified username, authentication type,
authentication password, private protocol and private password. If the
private password is not used, it fallbacks to same password as the
authentication one.
<tag><label id="snmpd-rwuser">rwuser [-s <m/secmodel/] <m/user/
[noauth|auth|priv [<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
Give user with selected security level read-write permissions
to the defined <m/oid/ OID subtree or <m/view/ view,
see <ref id="snmp_view" name="view definition">. Security level
<cf>noauth</cf> does not require authentication, <cf>auth</cf> requires
authentication and <cf>priv</cf> authentication with enforced message
encryption. View is a Net-SNMP construct to name and group set of OID
subtrees with optional context. Contexts are currently not supported by
BIRD.
<tag><label id="snmpd-rouser">rouser [-s <m/secmodel/] <m/user/
[noauth|auth|priv [<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
Same as <cf>rwuser</cf> but only with read permissions.
</descrip>
<sect3>SNMPv1/SNMPv2c community-based
<p>
<code>
# file /etc/snmp/snmpd.conf (continuation)
rwcommunity &lt;community&gt; [&lt;source&gt; [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
rocommunity &lt;community&gt; [&lt;source&gt; [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
rwcommunity6 &lt;community&gt; [&lt;source&gt; [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
rocommunity6 &lt;community&gt; [&lt;source&gt; [&lt;oid&gt; | -V &lt;view&gt; [&lt;context&gt;]]]
view &lt;vname&gt; (include|exclude) &lt;oid&gt; [&lt;mask&gt;]
</code>
<descrip>
<tag><label id="snmpd-rwcommunity">rwcommunity <m/community/ [<m/source/
[<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
Create a community with read-write permissions named <m/community/.
Option <m/source/ is used to restrict senders of request, use
<cf>"default"</cf> as a placeholder in doubts. You can also restrict
accessible regions of OID tree to OID subtree of <m/oid/ or named
<m/view/, otherwise the access is unrestricted to the whole OID tree.
Contexts are currently not supported by BIRD.
<tag><label id="snmpd-rocommunity">rocommunity <m/community/> [<m/source/
[<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
Same as <cf>rwcommunity</cf> but only with read permissions.
<tag><label id="snmpd-rwcommunity6">rwcommunity6 <m/community/
[<m/source/ [<m/oid/ | -V <m/view/ [<m/context/]]] </tag>
Is <cf>rwcommunity</cf> for packet received using IPv6.
<tag><label id="snmpd-rocommunity6">rocommunity6 <m/community/
[<m/source/ [<m/oid/ | -V <m/view/ [<m/context/]]]</tag>
Same as <cf>rwcommunity6</cf> but only with read permissions.
<tag><label id="snmpd-view">view <m/vname/ (include|exclude) <m/oid/ [<m/mask/]</tag>
Define a named grouping of OIDs. May be used multiple times with same
<m/vname/.
</descrip>
<sect1>BIRD SNMP configuration
<p>
<code>
protocol snmp [&lt;name&gt;] {
agentx master address (default|&lt;unix_path&gt;|&lt;ip&gt; [port &lt;port&gt;]);
subagent description &lt;text&gt;;
source address &lt;ip&gt;;
registration priority &lt;num&gt;;
message timeout &lt;time&gt;;
start delay time &lt;time&gt;;
verbose &lt;switch&gt;;
mib bgp4 {
local as &lt;num&gt;;
local router id &lt;ip4&gt;;
peer &lt;name&gt;;
};
}
</code>
<descrip>
<tag><label id="snmp-master">agentx master address
(default|<m/unix_path/|<m/ip/ [port <m/port/])</tag>
Address of AgentX master. Default is <cf>"/var/run/agentx/master"</cf>.
String option <m/unix_path/ select transport over Unix Domain sockets
with selected path address. Option <m/ip/ select transport over TCP.
Default port for TCP transmission is 705.
<tag><label id="snmp-descr">subagent description <m/text/</tag>
Short string describing the subagent. Default: "bird".
<tag><label id="snmp-src-addr">source address <m/ip/</tag>
For TCP based AgentX communication sets socket's source address.
<tag><label id="snmp-reg-priority">registration priority <m/number/</tag>
Set AgentX registration priority for all MIBs. Lower values have higher
priority. Valid interval 0-255. Default: 127.
<tag><label id="snmp-msg-timeout">message timeout <m/time/ s|ms</tag>
Set timeout for all AgentX messages. With 1 second granurality with
values from interval 0-255. Default: 15 s.
<tag><label id="snmp-start-delay">start delay time <m/time/ s|ms</tag>
Wait <m/time/ before sending first packet after protocol start.
<tag><label id="snmp-verbose">verbose <m/switch/</tag>
Enable logging of events connected to AgentX master pinging. Default:
verbose logging is disabled.
<tag><label id="snmp-bgp4-mib">mib bgp4</tag>
Enable BGP4-MIB which is defined in <rfc id="4273">. The support is
limited to BGP4-MIB::bgpPeerTable; the BGP4-MIB::bgpRcvdPathAttrTable,
BGP4-MIB::bgpPathAttrTable, traps and notifications are not supported.
<tag><label id="snmp-bgp4-local-as">local as <m/number/</tag>
Specify Local As for BGP4 MIB (BGP4-MIB::bgpLocalAs.0). This option is
required.
<tag><label id="snmp-bgp4-router-id">local router id <m/IPv4 address/</tag>
Specify Router ID for BGP4 MIB (BGP4-MIB::bgpLocalIdentifier.0). This
option is required.
<tag><label id="snmp-bgp4-peer">peer <m/name/</tag>
Make information about BGP protocol <m/name/ accessible. This protocol
must over IPv4 (this limitation is introduced by the BGP4-MIB). May
be used multiple times.
</descrip>
<sect2>An example SNMP protocol configuration
<p>
<code>
protocol bgp ibgp1 {
local as 2;
router id 192.0.2.1;
/* ... */
}
protocol bgp ibgp2 {
local as 4;
router id 192.0.2.128;
/* ... */
}
protocol snmp snmp1 {
agentx master address 198.51.100.2;
mib bgp4 {
local as 2;
local router id 192.0.2.1;
peer ibgp1;
peer ibgp2;
}
}
</code>
<sect1>Accessing MIB data
<p>To save some keystrokes and to avoid putting passwords in shell history, you
could use common configuration file for all Net-SNMP command line utilities.
Here is an example:
<code>
# file ~/.snmp/snmp.conf
defVersion (1|2c|3)
defCommunity &lt;community&gt;
defSecurityName &lt;username&gt;
defAuthType (MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224)
defAuthPassphrase &lt;authpass&gt;
defPrivType (DES|AES)
defPrivPassphrase &lt;privpass&gt;
clientaddr [&lt;transport-type&gt;:]&lt;transport-address&gt;
</code>
<descrip>
<tag><label id="snmpcmd-version">defVersion (1|2c|3)</tag>
Select version of SNMP packets, must follow
<file>/etc/snmp/snmpd.conf</file>. Use only if community/view based
configuration.
<tag><label id="snmpcmd-community">defCommunity <m/community/</tag>
Use by default given community. Use only for SNMP version 1 and 2c as
community as kind of a username for these versions.
<tag><label id="snmpcmd-authtype">defAuthType
(MD5|SHA|SHA-512|SHA-384|SHA-256|SHA-224)</tag>
Select authentication type.
<tag><label id="snmpcmd-authpass">defAuthPassphrase <m/authpass/</tag>
Set authentication password.
<tag><label id="snmpcmd-privtype">defPrivType (DES|AES)</tag>
Select private protocols.
<tag><label id="snmpcmd-privpass">defPrivPassphrase <m/privpass/</tag>
Set private password used for encryption.
<tag><label id="snmpcmd-clientaddr">clientaddr
[<m/transport-type/:]<m/transport-address/</tag>
SNMP command line utility equivalent to <cf>agentaddress</cf> daemon
option.
</descrip>
<p>For further information, see <it/snmp.conf(5)/.
<p>Example of configuration for Net-SNMP utils.
<code>
# SNMPv3
defVersion 3
defSecurityName snmp_name
defSecurityLevel noAuthNoPriv
defAuthType MD5
defAuthPassphrase example_pass
</code>
<code>
$ # &lt;snmputil&gt; &lt;master-address&gt; &lt;oid&gt;[ &lt;oid&gt; [...]]
$ snmpget 192.0.2.64 BGP4-MIB::bgpLocalAs.0
$ snmpgetnext 192.0.2.64 BGP4-MIB::bgpPeerState BGP4-MIB::bgpPeerAdminStatus
$ snmpwalk 192.0.2.64 BGP4-MIB::bgp
$ snmptable 192.0.2.64 BGP4-MIB::bgpPeerTable
</code>
<p>We recommend you to check manpages for Net-SNMP utilities mentioned above,
such as <it/snmp_config(5)/, <it/snmpd.conf(5)/, <it/snmpd(8)/,
<it/snmp.conf(5)/, <it/snmpcmds(1)/, <it/snmpget(1)/, <it/snmpgetnext(1)/,
<it/snmpbulkget(1)/, <it/snmpwalk(1)/, <it/snmptable(1)/.
<chapt>Conclusions
<label id="conclusion">

5
doc/snmp.conf.example Normal file
View File

@ -0,0 +1,5 @@
defSecurityName snmp_name
defAuthType MD5
defAuthPassphrase test_pass
defSecurityLevel noAuthNoPriv
defVersion 3

18
doc/snmpd.conf.example Normal file
View File

@ -0,0 +1,18 @@
#
# snmpd configuration file
#
# SNMP connection address
agentaddress 127.0.0.1
# enable AgentX protocol
master agentx
# AgentX connection address
agentXSocket tcp:localhost:705
# Create example user
createUser snmp_name MD5 test_pass
rwuser snmp_name noauth

View File

@ -56,6 +56,7 @@ enum protocol_class {
PROTOCOL_RADV,
PROTOCOL_RIP,
PROTOCOL_RPKI,
PROTOCOL_SNMP,
PROTOCOL_STATIC,
PROTOCOL__MAX
};
@ -105,9 +106,9 @@ void protos_dump_all(void);
*/
extern struct protocol
proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
proto_ospf, proto_perf, proto_l3vpn, proto_aggregator,
proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
proto_device, proto_radv, proto_rip, proto_static, proto_mrt, proto_ospf,
proto_perf, proto_l3vpn, proto_aggregator, proto_pipe, proto_bgp, proto_bmp,
proto_bfd, proto_babel, proto_rpki, proto_snmp;
/*
* Routing Protocol Instance

View File

@ -8,5 +8,6 @@ C pipe
C radv
C rip
C rpki
C snmp
C static
S ../nest/rt-dev.c

5
proto/snmp/Doc Normal file
View File

@ -0,0 +1,5 @@
S snmp.c
S subagent.c
S snmp_utils.c
S mib_tree.c
S bgp4_mib.c

8
proto/snmp/Makefile Normal file
View File

@ -0,0 +1,8 @@
src := snmp.c snmp_utils.c subagent.c mib_tree.c bgp4_mib.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
tests_src := snmp_test.c
tests_targets := $(tests_targets) $(tests-target-files)
tests_objs := $(tests_objs) $(src-o-files)

1009
proto/snmp/bgp4_mib.c Normal file

File diff suppressed because it is too large Load Diff

97
proto/snmp/bgp4_mib.h Normal file
View File

@ -0,0 +1,97 @@
#ifndef _BIRD_SNMP_BGP4_MIB_H_
#define _BIRD_SNMP_BGP4_MIB_H_
#include "snmp.h"
#include "proto/bgp/bgp.h"
#include "subagent.h"
/* BGP4-MIB root identifier is found in snmp.h as SNMP_BGP4_MIB */
/* peers attributes */
enum bgp4_mib_peer_entry_row {
BGP4_MIB_PEER_IDENTIFIER = 1,
BGP4_MIB_STATE = 2,
BGP4_MIB_ADMIN_STATUS = 3, /* in read-only mode */
BGP4_MIB_NEGOTIATED_VERSION = 4,
BGP4_MIB_LOCAL_ADDR = 5,
BGP4_MIB_LOCAL_PORT = 6,
BGP4_MIB_REMOTE_ADDR = 7,
BGP4_MIB_REMOTE_PORT = 8,
BGP4_MIB_REMOTE_AS = 9,
BGP4_MIB_RX_UPDATES = 10, /* in updates */
BGP4_MIB_TX_UPDATES = 11, /* out updates */
BGP4_MIB_RX_MESSAGES = 12, /* in total messages */
BGP4_MIB_TX_MESSAGES = 13, /* out total messages */
BGP4_MIB_LAST_ERROR = 14,
BGP4_MIB_FSM_TRANSITIONS = 15, /* FSM established transitions */
BGP4_MIB_FSM_ESTABLISHED_TIME = 16,
BGP4_MIB_RETRY_INTERVAL = 17,
BGP4_MIB_HOLD_TIME = 18, /* in read-only mode */
BGP4_MIB_KEEPALIVE = 19, /* in read-only mode */
BGP4_MIB_HOLD_TIME_CONFIGURED = 20,
BGP4_MIB_KEEPALIVE_CONFIGURED = 21,
BGP4_MIB_ORIGINATION_INTERVAL = 22, /* UNSUPPORTED - 0 */
BGP4_MIB_MIN_ROUTE_ADVERTISEMENT = 23, /* UNSUPPORTED - 0 */
BGP4_MIB_IN_UPDATE_ELAPSED_TIME = 24,
} PACKED;
/* version of BGP, here BGP-4 */
#define BGP4_VERSIONS ((char[]) { 0x10 }) /* OID BGP4-MIB::bgpVersion */
/* values for BGP4-MIB::bgpPeerNegotiatedVersion */
#define BGP4_MIB_NEGOTIATED_VER_VALUE 4
#define BGP4_MIB_NEGOTIATED_VER_NO_VALUE 0
/* values for BGP4-MIB::bgpPeerAdminStatus */
enum bgp4_admin_status {
BGP4_ADMIN_STOP = 1,
BGP4_ADMIN_START = 2,
};
void snmp_bgp4_start(struct snmp_proto *p, int with_mib);
void snmp_bgp4_register(struct snmp_proto *p);
void snmp_bgp4_show_info(struct snmp_proto *p);
enum snmp_search_res snmp_bgp_search(struct snmp_proto *p, struct agentx_varbind **vb_search, const struct oid *o_end, struct snmp_pdu *c);
enum snmp_search_res snmp_bgp_search2(struct snmp_proto *p, struct oid **searched, const struct oid *o_end, uint contid);
void snmp_bgp_fill(struct snmp_proto *p, struct agentx_varbind **vb, struct snmp_pdu *c);
//int snmp_bgp_testset(struct snmp_proto *p, const struct agentx_varbind *vb, void* tr, struct oid *oid, uint pkt_type);
void snmp_bgp_notify_established(struct snmp_proto *p, struct bgp_proto *bgp);
void snmp_bgp_notify_backward_trans(struct snmp_proto *p, struct bgp_proto *bgp);
enum bgp4_mib_rows {
BGP4_MIB_VERSION = 1,
BGP4_MIB_LOCAL_AS = 2,
BGP4_MIB_PEER_TABLE = 3, /* subtable */
BGP4_MIB_IDENTIFIER = 4, /* BGP4-MIB::bgpIdentifier local router id */
};
enum bgp4_mib_peer_table_rows {
BGP4_MIB_PEER_ENTRY = 1,
};
/* valid values for BGP4_MIB_STATE */
enum bgp4_mib_bgp_states {
BGP4_MIB_IDLE = 1,
BGP4_MIB_CONNECT = 2,
BGP4_MIB_ACTIVE = 3,
BGP4_MIB_OPENSENT = 4,
BGP4_MIB_OPENCONFIRM = 5,
BGP4_MIB_ESTABLISHED = 6,
};
STATIC_ASSERT(BGP4_MIB_IDLE == BS_IDLE + 1);
STATIC_ASSERT(BGP4_MIB_CONNECT == BS_CONNECT + 1);
STATIC_ASSERT(BGP4_MIB_ACTIVE == BS_ACTIVE + 1);
STATIC_ASSERT(BGP4_MIB_OPENSENT == BS_OPENSENT + 1);
STATIC_ASSERT(BGP4_MIB_OPENCONFIRM == BS_OPENCONFIRM + 1);
STATIC_ASSERT(BGP4_MIB_ESTABLISHED == BS_ESTABLISHED + 1);
/* Traps OID sub-identifiers */
enum bgp4_traps_subids {
BGP4_MIB_ESTABLISHED_NOTIFICATION = 1,
BGP4_MIB_BACKWARD_TRANS_NOTIFICATION = 2,
};
#endif

171
proto/snmp/config.Y Normal file
View File

@ -0,0 +1,171 @@
/*
* BIRD -- Statistics Protocol Configuration
*
* (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
* (c) 2022 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
CF_HDR
#include "proto/snmp/snmp.h"
#include "proto/snmp/subagent.h"
CF_DEFINES
#define SNMP_CFG ((struct snmp_config *) this_proto)
CF_DECLS
CF_KEYWORDS(SNMP, PROTOCOL, LOCAL, AS, REMOTE, ADDRESS, PORT, DESCRIPTION,
TIMEOUT, PRIORITY, CONTEXT, DEFAULT, MESSAGE, VERBOSE, AGENTX,
SUBAGENT, MASTER, BGP4, MIB, REGISTRATION, PEER)
CF_GRAMMAR
proto: snmp_proto ;
snmp_proto:
snmp_proto_start proto_name '{' snmp_proto_opts '}' ;
snmp_proto_item:
proto_item
| bgp4_mib
| SOURCE ADDRESS ipa { SNMP_CFG->local_ip = $3; }
| AGENTX MASTER ADDRESS DEFAULT {
if (SNMP_CFG->trans_type != SNMP_TRANS_DEFAULT)
cf_error("Duplicit option remote address");
}
| AGENTX MASTER ADDRESS text {
if (SNMP_CFG->trans_type != SNMP_TRANS_DEFAULT)
cf_error("Duplicit option remote address");
if (strcmp($4, agentx_master_addr)) {
SNMP_CFG->master_path = $4;
SNMP_CFG->trans_type = SNMP_TRANS_UNIX;
}
}
| AGENTX MASTER ADDRESS ipa {
if (SNMP_CFG->trans_type != SNMP_TRANS_DEFAULT)
cf_error("Duplicit option agentx master address");
SNMP_CFG->master_ip = $4;
SNMP_CFG->trans_type = SNMP_TRANS_TCP;
}
| AGENTX MASTER ADDRESS ipa PORT expr {
if (SNMP_CFG->trans_type != SNMP_TRANS_DEFAULT)
cf_error("Duplicit option agentx master address");
if (($6 < 1) || ($6 > UINT16_MAX)) cf_error("Invalid port number");
SNMP_CFG->master_ip = $4;
SNMP_CFG->trans_type = SNMP_TRANS_TCP;
SNMP_CFG->master_port = $6;
}
| SUBAGENT DESCRIPTION text {
if (strlen($3) > UINT16_MAX - 1) cf_error("Description is too long");
SNMP_CFG->description = $3;
}
| REGISTRATION PRIORITY expr {
if ($3 > 255) cf_error("Registration priority must be in range 0-255");
SNMP_CFG->priority = $3;
}
| MESSAGE TIMEOUT expr_us {
/* TODO */
if ($3 TO_S > 255 || $3 TO_S < 1)
log(L_WARN "resolution of AgentX message timeout is from 1 to 255 s.");
if (($3 TO_S) - (int)($3 TO_S) > 0)
log(L_WARN "AgentX message timeout cannot use second fraction, "
"will use rounded up value.");
SNMP_CFG->timeout = $3;
}
/*
| ERROR TIMEOUT expr_us {
/ * TODO * /
SNMP_CFG->error_timeout = $3;
}
| RETRY TIME expr_us {
/ * TODO * /
SNMP_CFG->retry_timeout = $3;
}
*/
| START DELAY TIME expr_us { SNMP_CFG->startup_delay = $4; }
| VERBOSE bool { SNMP_CFG->verbose = $2; }
;
bgp4_mib:
MIB BGP4 '{' bgp4_mib_items '}'
| BGP4 MIB '{' bgp4_mib_items '}'
;
bgp4_mib_items:
bgp4_mib_items_opt bgp4_mib_as bgp4_mib_items_opt bgp4_mib_id bgp4_mib_items_opt
;
bgp4_mib_as:
LOCAL AS expr ';' {
if ($3 < 1 || $3 > UINT16_MAX) cf_error("Invalid local AS for BGP4-MIB");
SNMP_CFG->bgp4_local_as = $3;
}
/* TODO add option to follow some bgp peer local as */
;
bgp4_mib_id:
LOCAL ROUTER ID idval ';' { SNMP_CFG->bgp4_local_id = $4; }
/* TODO add option to inherit global router id, or follow some bgp peer */
;
bgp4_mib_items_opt:
/* empty */
| bgp4_mib_items_opt bgp4_mib_peer ';'
;
bgp4_mib_peer: PEER symbol
{
/* the snmp_context rule sets the correct value of this_bond */
cf_assert_symbol($2, SYM_PROTO);
if (!$2->proto) cf_error("BGP protocol %s not found", $2->name);
cf_assert($2->proto->protocol == &proto_bgp,
"SNMP BGP bond accepts only BGP protocols");
struct snmp_bond *this_bond = cfg_alloc(sizeof(struct snmp_bond));
this_bond->type = SNMP_BGP;
this_bond->config = $2->proto;
add_tail(&SNMP_CFG->bgp_entries, &this_bond->n);
SNMP_CFG->bonds++;
}
snmp_proto_opts:
/* empty */
| snmp_proto_opts snmp_proto_item ';'
;
snmp_proto_start: proto_start SNMP
{
this_proto = proto_config_new(&proto_snmp, $1);
init_list(&SNMP_CFG->bgp_entries);
SNMP_CFG->bonds = 0;
SNMP_CFG->local_ip = IPA_NONE;
SNMP_CFG->master_ip = IPA_NONE;
SNMP_CFG->master_port = SNMP_PORT;
SNMP_CFG->master_path = agentx_master_addr;
SNMP_CFG->trans_type = SNMP_TRANS_DEFAULT;
SNMP_CFG->bgp4_local_as = 0;
SNMP_CFG->bgp4_local_id = 0;
SNMP_CFG->verbose = 0;
SNMP_CFG->description = "bird";
SNMP_CFG->timeout = 15;
SNMP_CFG->priority = AGENTX_PRIORITY;
}
CF_CODE
CF_END

877
proto/snmp/mib_tree.c Normal file
View File

@ -0,0 +1,877 @@
#include "mib_tree.h"
#include "snmp_utils.h"
#ifdef allocz
#undef allocz
#endif
#define alloc(size) mb_alloc(p, size)
#define allocz(size) mb_allocz(p, size)
#define free(ptr) mb_free(ptr)
#define realloc(ptr, newsize) mib_mb_realloc(p, ptr, newsize)
/*
* mib_mb_realloc - fix mb_realloc for NULL
* @p: pool to use for NULL pointers
* @ptr: old pointer to be reallocated
* @size: new size of allocated memory block
*
* The mb_realloc() does not work with NULL as ptr.
*/
static inline void *
mib_mb_realloc(pool *p, void *ptr, unsigned size)
{
if (!ptr)
return mb_alloc(p, size);
return mb_realloc(ptr, size);
}
/**
* mib_tree_init - Initialize a MIB tree
* @p: allocation source pool
* @t: pointer to a tree being initialized
*
* By default the standard SNMP internet prefix (.1.3.6.1) is inserted into the
* tree.
*/
void
mib_tree_init(pool *p, struct mib_tree *t)
{
struct mib_node *node = &t->root;
node->c.id = 0;
node->c.flags = 0;
node->children = NULL;
node->child_len = 0;
struct oid *oid = tmp_alloc(
snmp_oid_size_from_len((uint) ARRAY_SIZE(snmp_internet)));
oid->n_subid = ARRAY_SIZE(snmp_internet);
oid->prefix = 0;
oid->include = 0;
oid->reserved = 0;
for (size_t i = 0; i < ARRAY_SIZE(snmp_internet); i++)
oid->ids[i] = snmp_internet[i];
(void) mib_tree_add(p, t, oid, 0);
}
/**
* mib_tree_hint - preallocate child array for given OID
* @p: memory pool to allocated from
* @t: MIB tree to hint
* @oid: MIB subtree root with upto @size children
* @size: number of children in @oid subtree
*/
int
mib_tree_hint(pool *p, struct mib_tree *t, const struct oid *oid, uint size)
{
mib_node_u *node = mib_tree_add(p, t, oid, 0);
if (!node || mib_node_is_leaf(node))
return 0;
struct mib_node *inner = &node->inner;
if (inner->child_len >= size + 1)
return 1;
u32 old_len = inner->child_len;
inner->child_len = size + 1;
inner->children = realloc(inner->children,
inner->child_len * sizeof(mib_node_u *));
for (u32 i = old_len; i < inner->child_len; i++)
inner->children[i] = NULL;
return 1;
}
/**
* mib_tree_add - Insert a new node to the tree
* @p: allocation source pool
* @t: MIB tree to insert to
* @oid: identification of inserted node.
* @is_leaf: flag signaling that inserted OID should be leaf node.
*
* Reinsertion only return already valid node pointer, no allocations are done
* in this case. Return pointer to node in the MIB tree @t or NULL if the
* requested insertion is invalid. Insertion is invalid if we want to insert
* node below a leaf or insert a leaf in place taken by normal node.
*
*/
mib_node_u *
mib_tree_add(pool *p, struct mib_tree *t, const struct oid *oid, int is_leaf)
{
// TODO may not function properly in snmp_internet prefix region
struct mib_walk_state walk;
mib_node_u *node;
/* The empty prefix is associated with the root tree node */
if (snmp_is_oid_empty(oid) && !is_leaf)
return (mib_node_u *) &t->root;
else if (snmp_is_oid_empty(oid))
return NULL;
mib_tree_walk_init(&walk, t);
node = mib_tree_find(t, &walk, oid);
ASSERT(walk.id_pos <= oid->n_subid + 1);
if (node)
{
if (mib_node_is_leaf(node) == is_leaf)
return node;
/* we are trying to insert a leaf node in place of inner node,
* or vice versa */
return NULL;
}
ASSERT(walk.id_pos < oid->n_subid + 1);
node = walk.stack[walk.stack_pos - 1];
/* we encounter leaf node before end of OID's id path */
if (mib_node_is_leaf(node))
return NULL;
struct mib_node *node_inner = &node->inner;
if (snmp_oid_is_prefixed(oid) &&
walk.stack_pos <= ARRAY_SIZE(snmp_internet) + 1)
{
ASSUME(walk.stack_pos && walk.stack[0] == (mib_node_u *) &t->root);
for (u32 id = walk.stack_pos - 1; id < ARRAY_SIZE(snmp_internet); id++)
{
if (snmp_internet[id] >= node_inner->child_len)
{
u32 old_len = node_inner->child_len;
node_inner->child_len = snmp_internet[id] + 1;
node_inner->children = realloc(node_inner->children,
node_inner->child_len * sizeof(mib_node_u *));
for (u32 i = old_len; i < node_inner->child_len; i++)
node_inner->children[i] = NULL;
}
node = allocz(sizeof(struct mib_node));
/* assign child into a parent's children array */
node_inner->children[snmp_internet[id]] = node;
node_inner = &node->inner;
node_inner->c.id = snmp_internet[id];
/* node_inner's fields c.flags, child_len, children defaults to zero or
* NULL respectively */
walk.stack[walk.stack_pos++] = node;
}
if (walk.stack_pos == ARRAY_SIZE(snmp_internet) + 1)
{
u32 old_len = node_inner->child_len;
node_inner->child_len = MAX(old_len, (u32) oid->prefix + 1);
node_inner->children = realloc(node_inner->children,
node_inner->child_len * sizeof(mib_node_u *));
for (u32 i = old_len; i < node_inner->child_len; i++)
node_inner->children[i] = NULL;
if (is_leaf && !oid->n_subid)
{
node = allocz(sizeof(struct mib_leaf));
node->empty.flags = MIB_TREE_LEAF;
}
else
{
node = allocz(sizeof(struct mib_node));
node->empty.flags = 0;
}
node->empty.id = oid->prefix;
/* add node into the parent's children array */
node_inner->children[oid->prefix] = node;
node_inner = &node->inner;
walk.stack[walk.stack_pos++] = node;
}
}
/* snmp_internet + 2 = empty + snmp_internet + prefix */
if (snmp_oid_is_prefixed(oid) &&
walk.stack_pos == ARRAY_SIZE(snmp_internet) + 2 &&
oid->n_subid == 0 &&
mib_node_is_leaf(node) == is_leaf)
return node;
if (mib_node_is_leaf(node))
return node;
u8 subids = oid->n_subid;
u32 old_len = node_inner->child_len;
u32 child_id = oid->ids[walk.id_pos];
node_inner->child_len = MAX(old_len, child_id + 1);
node_inner->children = realloc(node_inner->children,
node_inner->child_len * sizeof(mib_node_u *));
for (u32 i = old_len; i < node_inner->child_len; i++)
node_inner->children[i] = NULL;
struct mib_node *parent;
/* break to loop before last node in the oid */
for (; walk.id_pos < subids - 1;)
{
parent = node_inner;
node_inner = allocz(sizeof(struct mib_node));
parent->children[child_id] = (mib_node_u *) node_inner;
node_inner->c.id = child_id;
child_id = oid->ids[++walk.id_pos];
node_inner->child_len = child_id + 1;
node_inner->children = allocz(node_inner->child_len * sizeof(mib_node_u *));
}
parent = node_inner;
mib_node_u *last;
if (is_leaf)
{
last = allocz(sizeof(struct mib_leaf));
struct mib_leaf *leaf = &last->leaf;
parent->children[child_id] = (mib_node_u *) leaf;
leaf->c.id = child_id;
leaf->c.flags = MIB_TREE_LEAF;
}
else
{
last = allocz(sizeof(struct mib_node));
node_inner = &last->inner;
parent->children[child_id] = (mib_node_u *) node_inner;
node_inner->c.id = child_id;
/* fields c.flags, child_len and children are set by zeroed allocz() */
}
return last;
}
/**
* mib_tree_delete - delete a MIB subtree
* @t: MIB tree
* @walk: MIB tree walk state that specify the subtree
*
* Return number of nodes deleted in the subtree. It is possible to delete an empty
* prefix which leads to deletion of all nodes inside the MIB tree. Note that
* the empty prefix (tree root) node itself could be deleted therefore 0 may be
* returned in case of empty prefix deletion.
*/
int
mib_tree_delete(struct mib_tree *t, struct mib_walk_state *walk)
{
int deleted = 0;
if (!t)
return 0;
/* (walk->stack_pos < 2) It is impossible to delete root node */
if (!walk || walk->stack_pos == 0)
return 0;
if (walk->stack_pos == 1)
{
for (u32 child = 0; child < t->root.child_len; child++)
{
if (!t->root.children[child])
continue;
walk->stack_pos = 2;
walk->stack[0] = (mib_node_u*) &t->root;
walk->stack[1] = t->root.children[child];
deleted += mib_tree_delete(t, walk);
}
return deleted;
}
struct mib_node *parent = &walk->stack[walk->stack_pos - 2]->inner;
mib_node_u *node = walk->stack[walk->stack_pos - 1];
struct mib_walk_state delete = {
.id_pos = walk->id_pos,
.stack_pos = 2,
.stack = {
(mib_node_u *) parent,
node,
NULL,
},
};
u32 last_id = 0;
while (delete.stack_pos > 1)
{
continue_while: /* like outer continue, but skip always true condition */
parent = (struct mib_node *) delete.stack[delete.stack_pos - 2];
if (mib_node_is_leaf(node))
{
/* Free leaf node */
last_id = node->leaf.c.id;
parent->children[last_id] = NULL;
delete.stack[--delete.stack_pos] = NULL;
free(node);
deleted++;
node = delete.stack[delete.stack_pos - 1];
continue; /* here, we couldn't skip the while condition */
}
struct mib_node *node_inner = &node->inner;
mib_node_u *child = NULL;
for (u32 id = last_id; id < node_inner->child_len; id++)
{
/* Recursively traverse child nodes */
child = node_inner->children[id];
if (!child)
continue;
last_id = 0;
delete.stack[delete.stack_pos++] = child;
parent = node_inner;
node = child;
goto continue_while; /* outer continue */
}
/* Free inner node without any children */
last_id = node_inner->c.id;
parent->children[last_id] = NULL;
delete.stack[--delete.stack_pos] = NULL;
free(node_inner->children);
free(node_inner);
deleted++;
node = (mib_node_u *) parent;
/* skip check for deleted node in loop over children */
last_id++;
}
/* delete the node from original stack */
walk->stack[--walk->stack_pos] = NULL;
node = walk->stack[walk->stack_pos - 1];
struct mib_node *node_inner = &node->inner;
u32 id;
for (id = 0; id < node_inner->child_len; id++)
{
if (node_inner->children[id] != NULL)
break;
}
if (id == node_inner->child_len)
{
/* all the children are NULL */
free(node_inner->children);
node_inner->children = NULL;
node_inner->child_len = 0;
}
return deleted;
}
/**
* mib_tree_remove - delete a MIB subtree
* @t: MIB tree
* @oid: object identifier specifying the subtree
*
* This is a convenience wrapper around mib_tree_delete(). The mib_tree_remove()
* finds the corresponding node and deletes it. Return 0 if the OID was not
* found. Otherwise return number of deleted nodes (see mib_tree_delete() for
* more details).
*/
int
mib_tree_remove(struct mib_tree *t, const struct oid *oid)
{
struct mib_walk_state walk = { };
mib_node_u *node = mib_tree_find(t, &walk, oid);
if (!node)
return 0;
return mib_tree_delete(t, &walk);
}
/**
* mib_tree_find - Find a OID node in MIB tree
* @t: searched tree
* @walk: output search state
* @oid: searched node identification
*
* Return valid pointer to node in MIB tree or NULL. The search state @walk is
* always updated and contains the longest possible prefix of @oid present
* inside the tree @t. The @walk must not be NULL and must be blank (only
* initialized).
*/
mib_node_u *
mib_tree_find(const struct mib_tree *t, struct mib_walk_state *walk, const struct oid *oid)
{
ASSERT(t && walk);
if (!oid || snmp_is_oid_empty(oid))
{
walk->stack_pos = 1;
walk->stack[0] = (mib_node_u *) &t->root;
return (snmp_is_oid_empty(oid)) ? (mib_node_u *) &t->root : NULL;
}
mib_node_u *node;
struct mib_node *node_inner;
/* the OID id index to use */
u8 oid_pos = walk->id_pos;
if (walk->stack_pos > 0)
node = walk->stack[walk->stack_pos - 1];
else
node = walk->stack[walk->stack_pos++] = (mib_node_u *) &t->root;
if (mib_node_is_leaf(node))
{
/* In any of cases below we did not move in the tree therefore the
* walk->id_pos is left untouched. */
if (snmp_oid_is_prefixed(oid) &&
oid->n_subid + ARRAY_SIZE(snmp_internet) + 1 == walk->id_pos)
return node;
else if (snmp_oid_is_prefixed(oid) &&
oid->n_subid + ARRAY_SIZE(snmp_internet) + 1 > walk->id_pos)
return NULL;
else if (!snmp_oid_is_prefixed(oid) && oid->n_subid + 1 == walk->id_pos)
return node;
}
node_inner = &node->inner;
ASSERT(node); /* node may be leaf if OID is not in tree t */
/* Handling of prefixed OID */
if (snmp_oid_is_prefixed(oid) && walk->stack_pos < 6)
{
/* The movement inside implicit SNMP internet and following prefix is not
* projected to walk->id_pos. */
uint i = (uint) walk->stack_pos - 1;
/* walking the snmp_internet prefix itself */
for (; i < ARRAY_SIZE(snmp_internet); i++)
{
if (node_inner->child_len <= snmp_internet[i])
return NULL;
node = node_inner->children[snmp_internet[i]];
node_inner = &node->inner;
if (!node)
return NULL;
ASSERT(node->empty.id == snmp_internet[i]);
walk->stack[walk->stack_pos++] = node;
if (mib_node_is_leaf(node))
return NULL;
}
/* walking the prefix continuation (OID field oid->prefix) */
u8 prefix = oid->prefix;
if (node_inner->child_len <= prefix)
return NULL;
node = node_inner->children[prefix];
node_inner = &node->inner;
if (!node)
return NULL;
ASSERT(node->empty.id == prefix);
walk->stack[walk->stack_pos++] = node;
if (mib_node_is_leaf(node) && oid->n_subid > 0)
return NULL;
}
u8 subids = oid->n_subid;
if (subids == 0)
return (node == (mib_node_u *) &t->root) ? NULL : node;
/* loop for all OID's ids except the last one */
for (; oid_pos < subids - 1 && walk->stack_pos < MIB_WALK_STACK_SIZE + 1; oid_pos++)
{
u32 id = oid->ids[oid_pos];
if (node_inner->child_len <= id)
{
/* The walk->id_pos points after the last accepted OID id.
* This is correct because we did not find the last OID in the tree. */
walk->id_pos = oid_pos;
return NULL;
}
node = node_inner->children[id];
node_inner = &node->inner;
if (!node)
{
/* Same as above, the last node is not valid therefore the walk->is_pos
* points after the last accepted OID id. */
walk->id_pos = oid_pos;
return NULL;
}
ASSERT(node->empty.id == id);
walk->stack[walk->stack_pos++] = node;
if (mib_node_is_leaf(node))
{
/* We need to increment the oid_pos because the walk->is_pos suppose the
* pointer after the last valid OID id. */
walk->id_pos = ++oid_pos;
return NULL;
}
}
walk->id_pos = oid_pos;
u32 last_id = oid->ids[oid_pos];
if (node_inner->child_len <= last_id ||
walk->stack_pos >= MIB_WALK_STACK_SIZE + 1)
return NULL;
node = node_inner->children[last_id];
node_inner = &node->inner;
if (!node)
return NULL;
/* here, the check of node being a leaf is intentionally omitted
* because we may need to search for a inner node */
ASSERT(node->empty.id == last_id);
/* We need to increment the oid_pos because the walk->is_pos suppose the
* pointer after the last valid OID id. */
walk->id_pos = ++oid_pos;
return walk->stack[walk->stack_pos++] = node;
}
/**
* mib_tree_walk_init - Initialize MIB tree walk state
* @walk: MIB tree walk state to init
* @t: optional MIB tree
*/
void
mib_tree_walk_init(struct mib_walk_state *walk, const struct mib_tree *t)
{
walk->id_pos = 0;
walk->stack_pos = (t != NULL) ? 1 : 0;
memset(&walk->stack, 0, sizeof(walk->stack));
if (t != NULL)
walk->stack[0] = (mib_node_u *) &t->root;
}
/*
* walk_is_prefixable - test prefixability of MIB walk state
* @walk: MIB tree walk state to use
*
* This function is functionally equivalent to snmp_oid_is_prefixable().
*/
static inline int
walk_is_prefixable(const struct mib_walk_state *walk)
{
/* empty prefix and oid->prefix (+2) */
if (walk->stack_pos < ARRAY_SIZE(snmp_internet) + 2)
return 0;
for (uint i = 0; i < ARRAY_SIZE(snmp_internet); i++)
{
if (walk->stack[i + 1]->empty.id != snmp_internet[i])
return 0;
}
u32 id = walk->stack[ARRAY_SIZE(snmp_internet) + 1]->empty.id;
return id > 0 && id <= UINT8_MAX;
}
/*
* mib_tree_walk_to_oid - retrieve OID from MIB tree walk state
* @walk: MIB tree walk state to transform
* @result: destination OID
* @subids: Maximal number of subids in available space
*
* Return 1 if the space is insufficient, 0 otherwise.
*/
int
mib_tree_walk_to_oid(const struct mib_walk_state *walk, struct oid *result, u32 subids)
{
ASSERT(walk && result);
/* the stack_pos point after last valid index, and the first is always empty
* prefix */
if (walk->stack_pos <= 1)
{
/* create a null valued OID; sets all n_subid, prefix, include and reserved */
memset(result, 0, sizeof(struct oid));
return 0;
}
u32 index;
if (walk_is_prefixable(walk))
{
if (walk->stack_pos - 2 > subids - (ARRAY_SIZE(snmp_internet) + 1))
return 1;
/* skip empty prefix, whole snmp_internet .1.3.6.1 and oid->prefix */
index = 2 + ARRAY_SIZE(snmp_internet);
result->n_subid = walk->stack_pos - (ARRAY_SIZE(snmp_internet) + 2);
result->prefix = \
walk->stack[ARRAY_SIZE(snmp_internet) + 1]->empty.id;
}
else
{
if (walk->stack_pos - 2 > subids)
return 1;
index = 1; /* skip empty prefix */
result->n_subid = walk->stack_pos - 1;
result->prefix = 0;
}
result->include = 0;
result->reserved = 0;
u32 i = 0;
/* the index could point after last stack array element */
for (; index < walk->stack_pos && index < MIB_WALK_STACK_SIZE; index++)
result->ids[i++] = walk->stack[index]->empty.id;
return 0;
}
/*
* mib_tree_walk_oid_compare - compare MIB tree walk state and OID
* @walk: left relation operand
* @oid: Object Identifier in cpu native byte order
*
* Return value semantics is the same as snmp_oid_compare().
*/
int
mib_tree_walk_oid_compare(const struct mib_walk_state *walk, const struct oid *oid)
{
/* code is very similar to snmp_oid_compare() */
if (!walk->stack_pos)
return -1;
uint walk_idx = 1;
u8 walk_subids = walk->stack_pos; /* left_subids */
u8 oid_subids = oid->n_subid; /* right_subids */
const u8 oid_prefix = oid->prefix;
if (oid_prefix != 0)
{
for (; walk_idx < walk_subids && walk_idx < ARRAY_SIZE(snmp_internet) + 1; walk_idx++)
{
u32 id = walk->stack[walk_idx]->empty.id;
if (id < snmp_internet[walk_idx - 1])
return -1;
else if (id > snmp_internet[walk_idx - 1])
return 1;
}
if (walk_idx < ARRAY_SIZE(snmp_internet) + 1)
return -1;
const u8 walk_prefix = walk->stack[walk_idx++]->empty.id;
if (walk_prefix < oid_prefix)
return -1;
else if (walk_prefix > oid_prefix)
return 1;
}
uint i = 0;
for (; i < oid_subids && walk_idx < walk_subids; i++, walk_idx++)
{
u32 walk_id = walk->stack[walk_idx]->empty.id;
u32 oid_id = oid->ids[i];
if (walk_id < oid_id)
return -1;
else if (walk_id > oid_id)
return 1;
}
if (walk_idx == walk_subids && i == oid_subids)
return 0;
else if (walk_idx == walk_subids)
return -1;
else /* if (i == oid_subids) */
return 1;
}
/**
* mib_tree_walk_is_oid_descendant - check if OID is in walk subtree
* @walk: MIB tree walk state
* @oid: OID to use
*
* Return 0 if @walk specify same path in MIB tree as @oid, return +1 if @oid is
* in @walk subtree, return -1 otherwise.
*/
int
mib_tree_walk_is_oid_descendant(const struct mib_walk_state *walk, const struct oid *oid)
{
/* walk stack index skipped zero prefix and OID subidentifier index */
u32 i = 1, j = 0;
if (!walk->stack_pos && snmp_is_oid_empty(oid))
return 0;
if (snmp_oid_is_prefixed(oid))
{
for (; i < MIN(walk->stack_pos - 1, ARRAY_SIZE(snmp_internet) + 1); i++)
{
if (walk->stack[i]->empty.id != snmp_internet[i - 1])
return -1;
}
if (i == walk->stack_pos)
return +1;
if (i < walk->stack_pos &&
walk->stack[i]->empty.id != (u32) oid->prefix)
return -1;
i++;
}
u32 ids = oid->n_subid;
for (; i < walk->stack_pos && j < ids; i++, j++)
{
if (walk->stack[i]->empty.id != oid->ids[j])
return -1;
}
if (i < walk->stack_pos)
return -1;
else if (i == walk->stack_pos && j == ids)
return 0;
else if (i == walk->stack_pos)
return +1;
else
{
die("unreachable");
return -1;
}
}
/**
* mib_tree_walk_next - find MIB tree node successor in lexicagraphically ordered MIB tree
* @t: MIB tree to use
* @walk: MIB tree walk state to use
*/
mib_node_u *
mib_tree_walk_next(const struct mib_tree *t, struct mib_walk_state *walk)
{
ASSERT(t && walk);
u32 next_id = 0;
if (walk->stack_pos == 0)
return NULL;
mib_node_u *node = walk->stack[walk->stack_pos - 1];
if (mib_node_is_leaf(node))
{
next_id = node->leaf.c.id + 1;
walk->stack[--walk->stack_pos] = NULL;
node = walk->stack[walk->stack_pos - 1];
}
while (walk->stack_pos > 0)
{
node = walk->stack[walk->stack_pos - 1];
if (mib_node_is_leaf(node))
{
walk->stack[walk->stack_pos++] = node;
return node;
}
struct mib_node *node_inner = &node->inner;
for (u32 id = next_id; id < node_inner->child_len; id++)
{
mib_node_u *child = node_inner->children[id];
if (!child)
continue;
walk->stack[walk->stack_pos++] = child;
return child;
}
next_id = node_inner->c.id + 1;
walk->stack[--walk->stack_pos] = NULL;
}
return NULL;
}
/**
* mib_tree_walk_next_leaf - wrapper around mib_tree_walk_next returning only leafs
* @t: MIB tree to use
* @walk: MIB tree walk state
* @skip: lower value bound for next OID id
*/
struct mib_leaf *
mib_tree_walk_next_leaf(const struct mib_tree *t, struct mib_walk_state *walk, u32 skip)
{
(void)t;
if (walk->stack_pos == 0)
return NULL;
u32 next_id = skip;
mib_node_u *node = walk->stack[walk->stack_pos - 1];
if (mib_node_is_leaf(node) && walk->stack_pos > 1)
{
next_id = node->leaf.c.id + 1;
walk->stack[--walk->stack_pos] = NULL;
node = walk->stack[walk->stack_pos - 1];
}
else if (mib_node_is_leaf(node))
{
/* walk->stack_pos == 1, so we NULL out the last stack field */
walk->stack[--walk->stack_pos] = NULL;
return NULL;
}
while (walk->stack_pos > 0)
{
continue_while:
node = walk->stack[walk->stack_pos - 1];
if (mib_node_is_leaf(node))
return (struct mib_leaf *) node;
struct mib_node *node_inner = &node->inner;
for (u32 id = next_id; id < node_inner->child_len; id++)
{
mib_node_u *child = node_inner->children[id];
if (!child)
continue;
next_id = 0;
walk->stack[walk->stack_pos++] = child;
/* node is assign at the beginning of the while loop (from stack) */
goto continue_while;
}
next_id = node->empty.id + 1;
walk->stack[--walk->stack_pos] = NULL;
}
return NULL;
}

122
proto/snmp/mib_tree.h Normal file
View File

@ -0,0 +1,122 @@
#ifndef _BIRD_SNMP_MIB_TREE_
#define _BIRD_SNMP_MIB_TREE_
#include "subagent.h"
#include "lib/resource.h"
#include "lib/lists.h"
#include "lib/birdlib.h"
#define MIB_TREE_NO_FLAGS 0x00
#define MIB_TREE_LEAF 0x01
#define MIB_TREE_HAS_HOOKS 0x02
typedef union mib_node_union mib_node_u;
struct mib_node_core {
u32 id;
u8 flags;
};
struct mib_node {
struct mib_node_core c;
mib_node_u **children;
u32 child_len;
};
struct mib_walk_state;
struct mib_leaf {
struct mib_node_core c;
/**
* filler - hook for filling VarBind data value
* @state: self referencing MIB tree walk state
* @data: box holding destiantion VarBind and SNMP protocol instance
*
* If corresponding leaf node has filled in AgentX type and/or size, it is
* guaranteed that PDU buffer have enough space. Hook mustn't be NULL.
* If the leaf node has set valid type, the varbind type will be automatically
* set by the snmp_walk_fill() servicing routine. If the field type is set to
* AGENTX_INVALID, it is expected that filler() hook will also fill
* the VarBind type.
*/
enum snmp_search_res (*filler)(struct mib_walk_state *state, struct snmp_pdu *context);
/**
* call_next - signal multileaf
* @state: self referencing MIB tree walk state
* @data: box holding destination VarBind and SNMP protocol insntace
*
* MIB modules can implement subtrees by a single leaf node in MIB node tree.
* When the tree is walked, the specific leaf node has to be returned multiple
* times. The @call_next hook determines if we should move to next leaf node.
* It is expected that call_next() hook may change the VarBind to be filled.
*
* Hook may be NULL meaning the leaf node is not multileaf/subtree.
*
*/
int (*call_next)(struct mib_walk_state *state, struct snmp_pdu *context);
/**
* type of produced VarBind, may be replaced in packet instanciation by
* AGENTX_NO_SUCH_OBJECT, AGENTX_NO_SUCH_INSTANCE or AGENTX_END_OF_MIB_VIEW
* The field is unspecified if equal to AGENTX_INVALID.
*/
enum agentx_type type;
/*
* Specify upper bound of VarBind data size. If set to -1, all handling must
* be done in filler() hook. In all other cases the filler() hook has
* guaranteed that the space is available.
*/
int size;
};
union mib_node_union {
struct mib_node_core empty;
struct mib_node inner;
struct mib_leaf leaf;
};
/*
* The stack size include empty prefix (mib tree root).
*/
#define MIB_WALK_STACK_SIZE 33
STATIC_ASSERT(OID_MAX_LEN < MIB_WALK_STACK_SIZE);
/* walk state for MIB tree */
struct mib_walk_state {
u8 id_pos; /* points after last matching subid in OID */
u32 stack_pos; /* points after last valid stack node */
mib_node_u *stack[MIB_WALK_STACK_SIZE];
};
struct mib_tree {
struct mib_node root;
};
void mib_tree_init(pool *p, struct mib_tree *t);
void mib_tree_walk_init(struct mib_walk_state *state, const struct mib_tree *t);
int mib_tree_walk_to_oid(const struct mib_walk_state *state, struct oid *result, u32 subids);
int mib_tree_walk_oid_compare(const struct mib_walk_state *state, const struct oid *oid);
mib_node_u *mib_tree_add(pool *p, struct mib_tree *tree, const struct oid *oid, int is_leaf);
int mib_tree_remove(struct mib_tree *t, const struct oid *oid);
int mib_tree_delete(struct mib_tree *t, struct mib_walk_state *state);
mib_node_u *mib_tree_find(const struct mib_tree *tree, struct mib_walk_state *walk, const struct oid *oid);
mib_node_u *mib_tree_walk_next(const struct mib_tree *t, struct mib_walk_state *walk);
struct mib_leaf *mib_tree_walk_next_leaf(const struct mib_tree *t, struct mib_walk_state *walk, u32 skip);
int mib_tree_hint(pool *p, struct mib_tree *t, const struct oid *oid, uint size);
int mib_tree_walk_is_oid_descendant(const struct mib_walk_state *walk, const struct oid *oid);
static inline int
mib_node_is_leaf(const mib_node_u *node)
{
ASSUME(node);
return node->empty.flags & MIB_TREE_LEAF;
}
#endif

686
proto/snmp/snmp.c Normal file
View File

@ -0,0 +1,686 @@
/*
* BIRD -- Simple Network Management Procotol (SNMP)
*
* (c) 2024 Vojtech Vilimek <vojtech.vilimek@nic.cz>
* (c) 2024 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
/**
* DOC: Simple Network Management Protocol
*
* The SNMP protocol is divided into several parts: |snmp.c| which implements
* integration with BIRD core, |subagent.c| provides AgentX subagent behaviour
* as well as functions for creating and parsing packets. In file |mib_tree.c|
* is implemented OID prefix tree for storing supported MIBs. File |bgp4_mib.c|
* implements parts of BGP4-MIB, |snmp_utils.c| is collection of helper
* functions for whole SNMP protocol.
*
* Althrough called SNMP the BIRD does not implement SNMP directly but acts as
* an AgentX subagent. AgentX subagent connects to AgentX master agent that
* processes incomming SNMP requests and passes them down to the correct
* subagent. Therefore you need also a running master agent somewhere.
* Advantages of this design are that you are capable of doing aggregation of
* statuses of multiple BIRDs at the master agent level and much simpler
* implementation.
*
* Before any of the SNMP request could be processed, the SNMP need to
* established AgentX session with the master agent and need to register all
* subtrees to make them accessible from the master agent. The establishement of
* the of session is handled by snmp_start(), snmp_start_locked() and
* snmp_start_subagent(). Then we register all MIBs from configuration in
* snmp_register_mibs().
*
* The AgentX request are handled only during MIB subtree registrations and
* after then on established session (in states SNMP_REGISTER and SNMP_CONN, see
* below). It is also guaranteed that no request is received before MIB subtree
* registration because the specific subagent is not authoratitave and also the
* master agent has no info about MIB subtree supported by subagent. The AgentX
* requests are handled by function snmp_rx() in |subagent.c|.
*
*
*
*/
/*
* SNMP State Machine
*
* States with main transitions
*
*
* +-----------------+
* | SNMP_INIT | entry state after call snmp_start()
* +-----------------+
* |
* | acquiring object lock for tcp communication socket
* V
* +-----------------+
* | SNMP_LOCKED | object lock aquired
* +-----------------+
* |
* | opening communication socket
* V
* +-----------------+
* | SNMP_OPEN | socket created, starting subagent
* +-----------------+
* |
* | BIRD receive response for agentx-Open-PDU
* V
* +-----------------+
* | SNMP_REGISTER | session was established, subagent registers MIBs
* +-----------------+
* |
* | subagent received response for any registration requests
* V
* +-----------------+
* | SNMP_CONN | everything is set
* +-----------------+
* |
* | received malformed PDU, protocol disabled,
* | BIRD sends agentx-Close-PDU or agentx-Response-PDU with an error
* V
* +-----------------+
* | SNMP_STOP | waiting until the prepared PDUs are sent
* +-----------------+
* |
* | cleaning protocol state
* V
* +-----------------+
* | SNMP_DOWN | session is closed
* +-----------------+
*
*
*
* Erroneous transitions:
* SNMP is UP (PS_UP) in states SNMP_CONN and also in SNMP_REGISTER because
* the session is establised and the GetNext request should be responsed
* without regards to MIB registration.
*
* Reconfiguration is done in similar fashion to BGP, the reconfiguration
* request is declined, the protocols is stoped and started with new
* configuration.
*
*/
#include "nest/bird.h"
#include "nest/cli.h"
#include "nest/locks.h"
#include "lib/socket.h"
#include "lib/lists.h"
#include "snmp.h"
#include "subagent.h"
#include "snmp_utils.h"
#include "mib_tree.h"
#include "bgp4_mib.h"
const char agentx_master_addr[] = AGENTX_MASTER_ADDR;
static const char *snmp_state_str[] = {
[SNMP_INIT] = "acquiring address lock",
[SNMP_LOCKED] = "address lock acquired",
[SNMP_OPEN] = "starting AgentX subagent",
[SNMP_REGISTER] = "registering MIBs",
[SNMP_CONN] = "AgentX session established",
[SNMP_STOP] = "stopping AgentX subagent",
[SNMP_DOWN] = "protocol down",
};
/*
* Callbacks
*/
/*
* snmp_sock_err - handle errors on socket by reopenning the socket
* @sk: socket owned by SNMP protocol instance
* @err: socket error code
*/
static void
snmp_sock_err(sock *sk, int UNUSED err)
{
struct snmp_proto *p = sk->data;
if (err != 0)
TRACE(D_EVENTS, "SNMP socket error (%d)", err);
snmp_set_state(p, SNMP_DOWN);
}
/*
* snmp_ping_timeout - send a agentx-Ping-PDU
* @tm: the ping_timer holding the SNMP protocol instance.
*
* Send an agentx-Ping-PDU. This function is periodically called by ping
* timer.
*/
static void
snmp_ping_timeout(timer *tm)
{
struct snmp_proto *p = tm->data;
snmp_ping(p);
}
/*
* snmp_stop_timeout - a timeout for non-responding master agent
* @tm: the startup_timer holding the SNMP protocol instance.
*
* We are trying to empty the TX buffer of communication socket. But if it is
* not done in reasonable amount of time, the function is called by timeout
* timer. We down the whole SNMP protocol with cleanup of associated data
* structures.
*/
static void
snmp_stop_timeout(timer *tm)
{
struct snmp_proto *p = tm->data;
snmp_set_state(p, SNMP_DOWN);
}
/*
* snmp_connected - start AgentX session on created socket
* @sk: socket owned by SNMP protocol instance
*
* Starts the AgentX communication by sending an agentx-Open-PDU.
* This function is internal and shouldn't be used outside the SNMP module.
*/
void
snmp_connected(sock *sk)
{
struct snmp_proto *p = sk->data;
snmp_set_state(p, SNMP_OPEN);
}
/*
* snmp_start_locked - open the socket on locked address
* @lock: object lock guarding the communication mean (address, ...)
*
* This function is called when the object lock is acquired. Main goal is to set
* socket parameters and try to open configured socket. Function
* snmp_connected() handles next stage of SNMP protocol start. When the socket
* coundn't be opened, a new try is scheduled after a small delay.
*/
static void
snmp_start_locked(struct object_lock *lock)
{
struct snmp_proto *p = lock->data;
if (p->startup_delay)
{
ASSERT(p->startup_timer);
p->startup_timer->hook = snmp_startup_timeout;
tm_start(p->startup_timer, p->startup_delay);
}
else
snmp_set_state(p, SNMP_LOCKED);
}
/*
* snmp_startup_timeout - start the initiliazed SNMP protocol
* @tm: the startup_timer holding the SNMP protocol instance.
*
* When the timer rings, the function snmp_startup() is invoked.
* This function is internal and shouldn't be used outside the SNMP module.
* Used when we delaying the start procedure, or we want to retry opening
* the communication socket.
*/
void
snmp_startup_timeout(timer *tm)
{
struct snmp_proto *p = tm->data;
snmp_set_state(p, SNMP_LOCKED);
}
/*
* snmp_rx_skip - skip all received data
* @sk: communication socket
* @size: size of received PDUs
*
* Socket rx_hook used when we are reseting the connection due to malformed PDU.
*/
static int
snmp_rx_skip(sock UNUSED *sk, uint UNUSED size)
{
return 1;
}
/*
* snmp_tx_skip - handle empty TX buffer during session reset
* @sk: communication socket
*
* The socket tx_hook is called when the TX buffer is empty, i.e. all data was
* send. This function is used only when we found malformed PDU and we are
* resetting the established session. If called, we perform a SNMP protocol
* state change.
*/
static void
snmp_tx_skip(sock *sk)
{
struct snmp_proto *p = sk->data;
snmp_set_state(p, SNMP_STOP);
}
/*
* snmp_cleanup - free all resources allocated by SNMP protocol
* @p: SNMP protocol instance
*
* This function forcefully stops and cleans all resources and memory acqiured
* by given SNMP protocol instance, such as timers, lists, hash tables etc.
*/
static inline void
snmp_cleanup(struct snmp_proto *p)
{
/* Function tm_stop() is called inside rfree() */
rfree(p->startup_timer);
p->startup_timer = NULL;
rfree(p->ping_timer);
p->ping_timer = NULL;
rfree(p->sock);
p->sock = NULL;
rfree(p->lock);
p->lock = NULL;
struct snmp_registration *r, *r2;
WALK_LIST_DELSAFE(r, r2, p->registration_queue)
{
rem_node(&r->n);
mb_free(r);
r = NULL;
}
HASH_FREE(p->bgp_hash);
rfree(p->lp);
p->lp = NULL;
/* bgp_trie is allocated exclusively from linpool lp */
p->bgp_trie = NULL;
struct mib_walk_state *walk = tmp_alloc(sizeof(struct mib_walk_state));
mib_tree_walk_init(walk, p->mib_tree);
(void) mib_tree_delete(p->mib_tree, walk);
p->mib_tree = NULL;
p->state = SNMP_DOWN;
}
/*
* snmp_set_state - change state with associated actions
* @p: SNMP protocol instance
* @state: new SNMP protocol state
*
* This function does not notify the bird about protocol state. Return current
* protocol state (PS_UP, ...).
*/
int
snmp_set_state(struct snmp_proto *p, enum snmp_proto_state state)
{
enum snmp_proto_state last = p->state;
const struct snmp_config *cf = (struct snmp_config *) p->p.cf;
p->state = state;
switch (state)
{
case SNMP_INIT:
/* We intentionally do not log anything */
ASSERT(last == SNMP_DOWN);
proto_notify_state(&p->p, PS_START);
if (cf->trans_type == SNMP_TRANS_TCP)
{
/* We need to lock the IP address */
struct object_lock *lock;
lock = p->lock = olock_new(p->pool);
lock->addr = p->master_ip;
lock->port = p->master_port;
lock->type = OBJLOCK_TCP;
lock->hook = snmp_start_locked;
lock->data = p;
olock_acquire(lock);
return PS_START;
}
last = SNMP_INIT;
p->state = state = SNMP_LOCKED;
/* Fall thru */
case SNMP_LOCKED:
TRACE(D_EVENTS, "SNMP Address lock acquired");
ASSERT(last == SNMP_INIT);
sock *s = sk_new(p->pool);
if (cf->trans_type == SNMP_TRANS_TCP)
{
s->type = SK_TCP_ACTIVE;
s->daddr = p->master_ip;
s->dport = p->master_port;
s->rbsize = SNMP_RX_BUFFER_SIZE;
s->tbsize = SNMP_TX_BUFFER_SIZE;
}
else
{
s->type = SK_UNIX_ACTIVE;
s->host = cf->master_path; /* daddr */
s->rbsize = SNMP_RX_BUFFER_SIZE;
s->tbsize = SNMP_TX_BUFFER_SIZE;
}
s->tx_hook = snmp_connected;
s->err_hook = snmp_sock_err;
p->sock = s;
s->data = p;
/* Try opening the socket, schedule a retry on fail */
if (sk_open(s) < 0)
{
TRACE(D_EVENTS, "SNMP Opening of communication socket failed");
rfree(s);
p->sock = NULL;
// TODO handle 0 timeout
tm_start(p->startup_timer, p->timeout);
}
return PS_START;
case SNMP_OPEN:
TRACE(D_EVENTS, "SNMP Communication socket opened, starting AgentX subagent");
ASSERT(last == SNMP_LOCKED);
p->sock->rx_hook = snmp_rx;
p->sock->tx_hook = NULL;
snmp_start_subagent(p);
p->startup_timer->hook = snmp_stop_timeout;
tm_start(p->startup_timer, 1 S);
return PS_START;
case SNMP_REGISTER:
TRACE(D_EVENTS, "SNMP Registering MIBs");
ASSERT(last == SNMP_OPEN);
tm_stop(p->startup_timer); /* stop timeout */
p->sock->rx_hook = snmp_rx;
p->sock->tx_hook = snmp_tx;
snmp_register_mibs(p);
// TODO timer for CONN
return PS_START;
case SNMP_CONN:
TRACE(D_EVENTS, "MIBs registered, AgentX session established");
ASSERT(last == SNMP_REGISTER);
proto_notify_state(&p->p, PS_UP);
return PS_UP;
case SNMP_STOP:
if (p->sock && p->state != SNMP_OPEN && !sk_tx_buffer_empty(p->sock))
{
TRACE(D_EVENTS, "SNMP Closing AgentX session");
if (p->state == SNMP_OPEN || p->state == SNMP_REGISTER ||
p->state == SNMP_CONN)
snmp_stop_subagent(p);
p->sock->rx_hook = snmp_rx_skip;
p->sock->tx_hook = snmp_tx_skip;
p->startup_timer->hook = snmp_stop_timeout;
tm_start(p->startup_timer, 150 MS);
proto_notify_state(&p->p, PS_STOP);
return PS_STOP;
}
p->state = state = SNMP_DOWN;
/* Fall thru */
case SNMP_DOWN:
TRACE(D_EVENTS, "SNMP AgentX session closed");
snmp_cleanup(p);
proto_notify_state(&p->p, PS_DOWN);
return PS_DOWN;
default:
die("unknown SNMP state transition");
return PS_DOWN;
}
}
/*
* snmp_reset - reset AgentX session
* @p: SNMP protocol instance
*
* We wait until the last PDU written into the socket is send while ignoring all
* incomming PDUs. Then we hard reset the connection by socket closure. The
* protocol instance is automatically restarted by nest.
*
* Return protocol state (PS_STOP, ...).
*/
int
snmp_reset(struct snmp_proto *p)
{
return snmp_set_state(p, SNMP_STOP);
}
/*
* snmp_up - AgentX session has registered all MIBs, protocols is up
* @p: SNMP protocol instance
*/
void
snmp_up(struct snmp_proto *p)
{
if (p->state == SNMP_REGISTER)
snmp_set_state(p, SNMP_CONN);
}
/*
* snmp_shutdown - Forcefully stop the SNMP protocol instance
* @P: SNMP protocol generic handle
*
* Simple cast-like wrapper around snmp_reset(), see more info there.
*/
static int
snmp_shutdown(struct proto *P)
{
struct snmp_proto *p = SKIP_BACK(struct snmp_proto, p, P);
return snmp_reset(p);
}
/*
* snmp_show_proto_info - print basic information about SNMP protocol instance
* @P: SNMP protocol generic handle
*/
static void
snmp_show_proto_info(struct proto *P)
{
struct snmp_proto *p = (void *) P;
cli_msg(-1006, " SNMP state: %s", snmp_state_str[p->state]);
cli_msg(-1006, " MIBs");
snmp_bgp4_show_info(p);
}
/*
* snmp_reconfigure_logic - find changes in configuration
* @p: SNMP protocol instance
* @new: new SNMP protocol configuration
*
* Return 1 if only minor changes have occured, 0 if we need full down-up cycle.
*/
static inline int
snmp_reconfigure_logic(struct snmp_proto *p, const struct snmp_config *new)
{
const struct snmp_config *old = SKIP_BACK(struct snmp_config, cf, p->p.cf);
if ((old->trans_type != SNMP_TRANS_TCP) && (new->trans_type == SNMP_TRANS_TCP)
|| (old->trans_type == SNMP_TRANS_TCP) && (new->trans_type != SNMP_TRANS_TCP))
return 0;
if (old->trans_type == SNMP_TRANS_TCP &&
(ipa_compare(old->master_ip, new->master_ip)
|| old->master_port != new->master_port))
return 0;
if (old->trans_type != SNMP_TRANS_TCP &&
bstrcmp(old->master_path, new->master_path))
return 0;
return (old->bgp4_local_id != new->bgp4_local_id
|| old->bgp4_local_as != new->bgp4_local_as
|| old->timeout != new->timeout // TODO distinguish message timemout
//(Open.timeout and timeout for timer)
|| old->priority != new->priority
|| strncmp(old->description, new->description, UINT16_MAX - 1));
}
/*
* snmp_reconfigure - Indicate instance reconfigurability
* @P - SNMP protocol generic handle, current state
* @CF - SNMP protocol configuration generic handle carring new values
*
* We accept the reconfiguration if the new configuration @CF is identical with
* the currently deployed configuration. Otherwise we deny reconfiguration because
* the implementation would be cumbersome.
*/
static int
snmp_reconfigure(struct proto *P, struct proto_config *CF)
{
struct snmp_proto *p = SKIP_BACK(struct snmp_proto, p, P);
const struct snmp_config *new = SKIP_BACK(struct snmp_config, cf, CF);
/* We are searching for configuration changes */
int reconfigurable = snmp_reconfigure_logic(p, new);
if (reconfigurable)
{
/* copy possibly changed values */
p->startup_delay = new->startup_delay;
p->verbose = new->verbose;
ASSERT(p->ping_timer);
int active = tm_active(p->ping_timer);
rfree(p->ping_timer);
p->ping_timer = tm_new_init(p->pool, snmp_ping_timeout, p, p->timeout, 0);
if (active)
tm_start(p->ping_timer, p->timeout);
HASH_FREE(p->bgp_hash);
HASH_INIT(p->bgp_hash, p->pool, 10);
rfree(p->lp);
p->lp = lp_new(p->pool);
p->bgp_trie = f_new_trie(p->lp, 0);
/* We repopulate BGP related data structures (bgp_hash, bgp_trie). */
snmp_bgp4_start(p, 0);
}
return reconfigurable;
}
/*
* snmp_start - Initialize the SNMP protocol instance
* @P: SNMP protocol generic handle
*
* The first step in AgentX subagent startup is protocol initialition.
* We must prepare lists, find BGP peers and finally asynchronously start
* a AgentX subagent session.
*/
static int
snmp_start(struct proto *P)
{
struct snmp_proto *p = (void *) P;
struct snmp_config *cf = (struct snmp_config *) P->cf;
p->local_ip = cf->local_ip;
p->master_ip = cf->master_ip;
p->master_port = cf->master_port;
p->bgp4_local_as = cf->bgp4_local_as;
p->bgp4_local_id = cf->bgp4_local_id;
p->timeout = cf->timeout;
p->startup_delay = cf->startup_delay;
p->verbose = cf->verbose;
p->pool = p->p.pool;
p->lp = lp_new(p->pool);
p->bgp_trie = f_new_trie(p->lp, 0);
p->mib_tree = mb_alloc(p->pool, sizeof(struct mib_tree));
p->startup_timer = tm_new_init(p->pool, snmp_startup_timeout, p, 0, 0);
p->ping_timer = tm_new_init(p->pool, snmp_ping_timeout, p, p->timeout, 0);
init_list(&p->registration_queue);
/* We create copy of bonds to BGP protocols. */
HASH_INIT(p->bgp_hash, p->pool, 10);
mib_tree_init(p->pool, p->mib_tree);
snmp_bgp4_start(p, 1);
return snmp_set_state(p, SNMP_INIT);
}
/*
* snmp_init - preinitialize SNMP instance
* @CF: SNMP configuration generic handle
*
* Returns a generic handle pointing to preinitialized SNMP procotol
* instance.
*/
static struct proto *
snmp_init(struct proto_config *CF)
{
struct proto *P = proto_new(CF);
struct snmp_proto *p = SKIP_BACK(struct snmp_proto, p, P);
p->rl_gen = (struct tbf) TBF_DEFAULT_LOG_LIMITS;
p->state = SNMP_DOWN;
return P;
}
/*
* snmp_postconfig - Check configuration correctness
* @CF: SNMP procotol configuration generic handle
*/
static void
snmp_postconfig(struct proto_config *CF)
{
const struct snmp_config *cf = (struct snmp_config *) CF;
/* Walk the BGP protocols and cache their references. */
if (cf->bgp4_local_as == 0)
cf_error("local as not specified");
}
/*
* Protocol infrastructure
*/
struct protocol proto_snmp = {
.name = "SNMP",
.template = "snmp%d",
.class = PROTOCOL_SNMP,
.channel_mask = 0,
.proto_size = sizeof(struct snmp_proto),
.config_size = sizeof(struct snmp_config),
.postconfig = snmp_postconfig,
.init = snmp_init,
.start = snmp_start,
.reconfigure = snmp_reconfigure,
.show_proto_info = snmp_show_proto_info,
.shutdown = snmp_shutdown,
};
void
snmp_build(void)
{
proto_build(&proto_snmp);
}

174
proto/snmp/snmp.h Normal file
View File

@ -0,0 +1,174 @@
/*
* BIRD -- Simple Network Management Protocol (SNMP)
*
* (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
* (c) 2022 CZ.NIC z.s.p.o
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#ifndef _BIRD_SNMP_H_
#define _BIRD_SNMP_H_
#include "nest/bird.h"
#include "nest/protocol.h"
#include "lib/resource.h"
#include "lib/ip.h"
#include "lib/socket.h"
#include "lib/timer.h"
#include "filter/data.h"
#define SNMP_UNDEFINED 0
#define SNMP_BGP 1
#define SNMP_OSPF 2
#define SNMP_INVALID 255
#define SNMP_PORT 705
#define SNMP_RX_BUFFER_SIZE 8192
#define SNMP_TX_BUFFER_SIZE 8192
#define SNMP_PKT_SIZE_MAX 4098
#define AGENTX_MASTER_ADDR "/var/agentx/master"
enum snmp_proto_state {
SNMP_DOWN = 0,
SNMP_INIT = 1,
SNMP_LOCKED,
SNMP_OPEN,
SNMP_REGISTER,
SNMP_CONN,
SNMP_STOP,
};
struct snmp_bond {
node n;
struct proto_config *config;
u8 type;
};
enum snmp_transport_type {
SNMP_TRANS_DEFAULT,
SNMP_TRANS_UNIX,
SNMP_TRANS_TCP,
};
#define SNMP_BGP_P_REGISTERING 0x01
#define SNMP_BGP_P_REGISTERED 0x02
struct snmp_bgp_peer {
const struct bgp_proto *bgp_proto;
ip4_addr peer_ip; /* used as hash key */
struct snmp_bgp_peer *next;
};
struct snmp_config {
struct proto_config cf;
enum snmp_transport_type trans_type;
ip_addr local_ip;
ip_addr master_ip; /* master agentx IP address for TCP transport */
u16 master_port;
const char *master_path; /* master agentx UNIX socket name */
u32 bgp4_local_id; /* BGP4-MIB related fields */
u32 bgp4_local_as;
btime timeout;
btime startup_delay;
u8 priority;
u32 bonds;
const char *description; /* The order of fields is not arbitrary */
list bgp_entries; /* We want dynamically allocated fields to be
* at the end of the config struct.
* We use this fact to check differences of
* nonallocated parts of configs with memcpy
*/
//const struct oid *oid_identifier; TODO
int verbose;
};
struct snmp_proto {
struct proto p;
struct object_lock *lock;
pool *pool; /* a shortcut to the procotol mem. pool */
linpool *lp; /* linpool for bgp_trie nodes */
enum snmp_proto_state state;
ip_addr local_ip;
ip_addr master_ip;
u16 master_port;
/* TODO add struct for grouping BGP4-MIB data */
u32 bgp4_local_id; /* BGP4-MIB related fields */
u32 bgp4_local_as;
sock *sock;
btime timeout; /* timeout is part of MIB registration. It
specifies how long should the master
agent wait for request responses. */
u32 session_id;
u32 transaction_id;
u32 packet_id;
list registration_queue; /* list containing snmp_register records */
// map
struct f_trie *bgp_trie;
HASH(struct snmp_bgp_peer) bgp_hash;
struct tbf rl_gen;
list pending_pdus;
timer *ping_timer;
btime startup_delay;
timer *startup_timer;
struct mib_tree *mib_tree;
int verbose;
uint pings;
u32 ignore_ping_id;
};
enum agentx_mibs {
BGP4_MIB_ID,
AGENTX_MIB_COUNT,
AGENTX_MIB_UNKNOWN,
};
struct snmp_registration;
struct agentx_response; /* declared in subagent.h */
typedef void (*snmp_reg_hook_t)(struct snmp_proto *p, const struct agentx_response *res, struct snmp_registration *reg);
struct snmp_registration {
node n;
enum agentx_mibs mib;
u32 session_id;
u32 transaction_id;
u32 packet_id;
snmp_reg_hook_t reg_hook_ok; /* hook called when successful response to OID registration is recieved */
snmp_reg_hook_t reg_hook_fail; /* hook called when OID registration fail */
};
void snmp_startup(struct snmp_proto *p);
void snmp_connected(sock *sk);
void snmp_startup_timeout(timer *tm);
void snmp_reconnect(timer *tm);
int snmp_set_state(struct snmp_proto *p, enum snmp_proto_state state);
int snmp_reset(struct snmp_proto *p);
void snmp_up(struct snmp_proto *p);
extern const char agentx_master_addr[sizeof(AGENTX_MASTER_ADDR)];
static inline int
proto_is_snmp(const struct proto *P)
{
extern struct protocol proto_snmp;
return P->proto == &proto_snmp;
}
#endif

1965
proto/snmp/snmp_test.c Normal file

File diff suppressed because it is too large Load Diff

972
proto/snmp/snmp_utils.c Normal file
View File

@ -0,0 +1,972 @@
/*
* BIRD -- Simple Network Management Protocol (SNMP) helper functions
*
* (c) 2022 Vojtech Vilimek <vojtech.vilimek@nic.cz>
* (c) 2022 CZ.NIC z.s.p.o
*
* Can be freely distributed and used under the terms of the GNU GPL.
*
*/
#include "snmp_utils.h"
#include <stdio.h>
inline void
snmp_pdu_context(struct snmp_pdu *pdu, struct snmp_proto *p, sock *sk)
{
pdu->p = p;
pdu->error = AGENTX_RES_NO_ERROR;
pdu->buffer = sk->tpos;
pdu->size = sk->tbuf + sk->tbsize - sk->tpos;
pdu->index = 0;
pdu->sr_vb_start = NULL;
pdu->sr_o_end = NULL;
}
/*
* snmp_session - store packet ids from protocol to header
* @p: source SNMP protocol instance
* @h: dest PDU header
*/
inline void
snmp_session(const struct snmp_proto *p, struct agentx_header *h)
{
STORE_U32(h->session_id, p->session_id);
STORE_U32(h->transaction_id, p->transaction_id);
STORE_U32(h->packet_id, p->packet_id);
}
inline void *
snmp_varbind_data(const struct agentx_varbind *vb)
{
uint name_size = snmp_oid_size(&vb->name);
return (void *) &vb->name + name_size;
}
/*
* snmp_is_oid_empty - check if oid is null-valued
* @oid: object identifier to check
*
* Test if the oid header is full of zeroes. For NULL-pointer @oid returns 0.
* We ignore include field to prevent weird behaviour.
*/
inline int
snmp_is_oid_empty(const struct oid *oid)
{
/* We intentionaly ignore padding that should be zeroed */
if (oid != NULL)
return oid->n_subid == 0 && oid->prefix == 0;
else
return 0;
}
/*
* snmp_oid_is_prefixable - check for prefixed form conversion possibility
* @oid: Object Identifier in packet byte order to check
*
* Check if it is possible to convert @oid to prefixed form. The condition of
* that is standart .1.3.6.1 internet prefix and 5-th id that fits in one byte.
*/
inline int
snmp_pkt_oid_is_prefixable(const struct oid *oid)
{
if (LOAD_U8(oid->n_subid) < 5)
return 0;
for (int i = 0; i < 4; i++)
if (LOAD_U32(oid->ids[i]) != snmp_internet[i])
return 0;
if (LOAD_U32(oid->ids[4]) >= 256)
return 0;
return 1;
}
/*
* snmp_oid_copy - copy OID from one place to another
* @dest: destination to use
* @src: OID to be copied from
*/
void
snmp_oid_copy(struct oid *dest, const struct oid *src)
{
dest->n_subid = src->n_subid;
dest->prefix = src->prefix;
dest->include = src->include ? 1 : 0;
dest->reserved = 0;
memcpy(dest->ids, src->ids, src->n_subid * sizeof(u32));
}
/*
* snmp_oid_from_buf - copy OID from RX buffer to dest in native byte order
* @dst: destination to use (native byte order)
* @src: OID to be copied from (packet byte order)
*/
void
snmp_oid_from_buf(struct oid *dst, const struct oid *src)
{
dst->n_subid = LOAD_U8(src->n_subid);
dst->prefix = LOAD_U8(src->prefix);
dst->include = LOAD_U8(src->include) ? 1 : 0;
dst->reserved = 0;
for (uint i = 0; i < dst->n_subid; i++)
dst->ids[i] = LOAD_U32(src->ids[i]);
}
/*
* snmp_oid_to_buf - copy OID to TX buffer with packet byte order
* @dst: destination to use (packet byte order)
* @src: OID to be copied from (native byte order)
*/
void
snmp_oid_to_buf(struct oid *dst, const struct oid *src)
{
STORE_U8(dst->n_subid, src->n_subid);
STORE_U8(dst->prefix, src->prefix);
STORE_U8(dst->include, (src->include) ? 1 : 0);
STORE_U8(dst->reserved, 0);
for (uint i = 0; i < src->n_subid; i++)
STORE_U32(dst->ids[i], src->ids[i]);
}
/*
* snmp_str_size_from_len - return in-buffer octet string size
* @len: length of C-string, returned from strlen()
*/
inline size_t
snmp_str_size_from_len(uint len)
{
return 4 + BIRD_ALIGN(len, 4);
}
/*
* snmp_str_size - return in packet size of supplied string
* @str: measured string
*
* Returned value is string length aligned to 4 byte with 32bit length
* annotation included.
*/
inline size_t
snmp_str_size(const char *str)
{
return snmp_str_size_from_len(strlen(str));
}
/*
* snmp_oid_size - measure size of OID in bytes
* @o: object identifier to use
*
* Work for both packet and cpu native byte orders.
*/
uint
snmp_oid_size(const struct oid *o)
{
/* LOAD_U8() is in both cases basic mem read */
return 4 + (LOAD_U8(o->n_subid) * 4);
}
/*
* snmp_oid_size_from_len - return size of OID with @n_subid subids in bytes
* @n_subid: number of subids in ids array
*/
inline size_t
snmp_oid_size_from_len(uint n_subid)
{
return sizeof(struct oid) + n_subid * sizeof(u32);
}
static inline uint
snmp_get_octet_size(const struct agentx_octet_str *str)
{
return str->length;
}
/*
* snmp_varbind_header_size - measure size of VarBind without data in bytes
* @vb_name: VarBind OID name
*
* Return size including whole OID as well as the VarBind header.
*/
uint
snmp_varbind_header_size(const struct oid *vb_name)
{
ASSUME(vb_name);
return snmp_oid_size(vb_name) + OFFSETOF(struct agentx_varbind, name);
}
uint
snmp_varbind_size_unsafe(const struct agentx_varbind *vb)
{
ASSUME(snmp_test_varbind_type(vb->type));
int value_size = agentx_type_size(vb->type);
uint vb_header = snmp_varbind_header_size(&vb->name);
if (value_size == 0)
return vb_header;
if (value_size > 0)
return vb_header + value_size;
switch (vb->type)
{
case AGENTX_OBJECT_ID:;
struct oid *oid = snmp_varbind_data(vb);
return vb_header + snmp_oid_size(oid);
case AGENTX_OCTET_STRING:
case AGENTX_IP_ADDRESS:
case AGENTX_OPAQUE:;
struct agentx_octet_str *string = snmp_varbind_data(vb);
return vb_header + snmp_get_octet_size(string);
default:
/* Shouldn't happen */
die("getting size of VarBind with unknown type (%u)", vb->type);
return 0;
}
}
/*
* snmp_varbind_size_from_len - get size in-buffer VarBind for known OID and data
* @n_subid: number of subidentifiers of the VarBind's OID name
* @type: type of VarBind
* @len: length of variably long data
*
* For types with fixed size the @len is not used. For types such as Octet
* String, or OID the @len is used directly.
*
* Return number of bytes used by VarBind in specified form.
*/
inline size_t
snmp_varbind_size_from_len(uint n_subid, enum agentx_type type, uint len)
{
size_t sz = snmp_oid_size_from_len(n_subid)
+ sizeof(struct agentx_varbind) - sizeof(struct oid);
int data_sz = agentx_type_size(type);
if (data_sz < 0)
sz += len;
else
sz += data_sz;
return sz;
}
/*
* snmp_test_varbind - test validity of VarBind type
* @type: Type of VarBind in cpu native byte order
*/
int
snmp_test_varbind_type(u16 type)
{
if (type == AGENTX_INTEGER ||
type == AGENTX_OCTET_STRING ||
type == AGENTX_NULL ||
type == AGENTX_OBJECT_ID ||
type == AGENTX_IP_ADDRESS ||
type == AGENTX_COUNTER_32 ||
type == AGENTX_GAUGE_32 ||
type == AGENTX_TIME_TICKS ||
type == AGENTX_OPAQUE ||
type == AGENTX_COUNTER_64 ||
type == AGENTX_NO_SUCH_OBJECT ||
type == AGENTX_NO_SUCH_INSTANCE ||
type == AGENTX_END_OF_MIB_VIEW)
return 1;
else
return 0;
}
/*
* snmp_valid_ip4_index - check IPv4 address validity in oid
* @o: object identifier holding ip address
* @start: index of first address id
*/
int
snmp_valid_ip4_index(const struct oid *o, uint start)
{
if (start + 3 < o->n_subid)
return snmp_valid_ip4_index_unsafe(o, start);
else
return 0;
}
/*
* snmp_valid_ip4_index_unsafe - check validity of IPv4 address in oid
* @o: object identifier holding ip address
* @start: index of first address id
*
* This function is unsafe - no checks of object identifier ids
* length sufficiency is done.
*/
int
snmp_valid_ip4_index_unsafe(const struct oid *o, uint start)
{
for (int i = 0; i < 4; i++)
if (o->ids[start + i] >= 256)
return 0;
return 1;
}
/*
* snmp_put_nstr - copy c-string into buffer with limit
* @buf: destination buffer
* @str: string to use
* @len: number of characters to use from string
*/
byte *
snmp_put_nstr(byte *buf, const char *str, uint len)
{
uint alen = BIRD_ALIGN(len, 4);
struct agentx_octet_str *octet = (void *) buf;
STORE_U32(octet->length, len);
memcpy(&octet->data, str, len);
buf += len + sizeof(octet->length);
/* Insert zero padding in the gap at the end */
for (uint i = 0; i < alen - len; i++)
buf[i] = '\0';
return buf + (alen - len);
}
/*
* snmp_put_str - put string into SNMP PDU transcieve buffer
* @buf: pointer to first unoccupied buffer byte
* @str: string to place
*
* Handles all conditions specified by RFC, namely string length annotation
* and padding 4 byte alignment with zeroes. Return NULL if string is too large
* for SNMP message.
*/
byte *
snmp_put_str(byte *buf, const char *str)
{
uint len = strlen(str);
return snmp_put_nstr(buf, str, len);
}
byte *
snmp_put_ip4(byte *buf, ip4_addr addr)
{
/* octet string has size 4 bytes */
STATIC_ASSERT(sizeof(ip4_addr) == sizeof(u32));
STORE_PTR(buf, sizeof(ip4_addr));
/* Always use Network byte order */
put_u32(buf+4, ip4_to_u32(addr));
return buf + 8;
}
byte *
snmp_put_blank(byte *buf)
{
STORE_PTR(buf, 0);
return buf + 4;
}
/*
* snmp_put_fbyte - put one padded byte to SNMP PDU transcieve buffer
* @buf: pointer to free buffer byte
* @data: byte to use
*
* Put @data into buffer @buf with 3B zeroed padding.
*/
byte *
snmp_put_fbyte(byte *buf, u8 data)
{
STORE_U8(*buf++, data);
memset(buf, 0, 3); /* we fill the 24bit padding with zeros */
return buf + 3;
}
/**
* snmp_oid_compare - find the lexicographical order relation between @left and @right
* @left: left object id relation operand
* @right: right object id relation operand
*
* both @left and @right has to be non-blank.
* function returns 0 if left == right,
* -1 if left < right,
* and 1 otherwise
*/
int
snmp_oid_compare(const struct oid *left, const struct oid *right)
{
const u8 left_subids = left->n_subid;
u8 right_subids = right->n_subid; /* see hack for more info */
const u8 left_prefix = left->prefix;
const u8 right_prefix = right->prefix;
if (left_prefix == 0 && right_prefix == 0)
goto test_ids;
if (right_prefix == 0)
return (-1) * snmp_oid_compare(right, left);
if (left_prefix == 0)
{
uint bound = MIN((uint) left_subids, (uint) ARRAY_SIZE(snmp_internet));
for (uint idx = 0; idx < bound; idx++)
{
u32 id = left->ids[idx];
if (id < snmp_internet[idx])
return -1;
else if (id > snmp_internet[idx])
return 1;
}
if (left_subids <= ARRAY_SIZE(snmp_internet))
return -1;
/* check prefix */
if (left->ids[4] < (u32) right_prefix)
return -1;
else if (left->ids[4] > (u32) right_prefix)
return 1;
/* the right prefix is already checked (+1) */
int limit = MIN(left_subids - (int) (ARRAY_SIZE(snmp_internet) + 1),
(int) right_subids);
for (int i = 0; i < limit; i++)
{
u32 left_id = left->ids[i + ARRAY_SIZE(snmp_internet) + 1];
u32 right_id = right->ids[i];
if (left_id < right_id)
return -1;
else if (left_id > right_id)
return 1;
}
/* hack: we known at this point that right has >= 5 subids
* (implicit in snmp_internet and oid->prefix), so
* we simplify to common case by altering left_subids */
right_subids += 5;
goto all_same;
}
if (left_prefix < right_prefix)
return -1;
else if (left_prefix > right_prefix)
return 1;
test_ids:
for (int i = 0; i < MIN(left->n_subid, right->n_subid); i++)
{
u32 left_id = left->ids[i];
u32 right_id = right->ids[i];
if (left_id < right_id)
return -1;
else if (left_id > right_id)
return 1;
}
all_same:
/* shorter sequence is before longer in lexicografical order */
if (left_subids < right_subids)
return -1;
else if (left_subids > right_subids)
return 1;
else
return 0;
}
struct snmp_registration *
snmp_registration_create(struct snmp_proto *p, enum agentx_mibs mib)
{
struct snmp_registration *r;
r = mb_alloc(p->p.pool, sizeof(struct snmp_registration));
r->n.prev = r->n.next = NULL;
r->session_id = p->session_id;
r->transaction_id = p->transaction_id;
/* will be incremented by snmp_session() macro during packet assembly */
r->packet_id = p->packet_id + 1;
r->mib = mib;
add_tail(&p->registration_queue, &r->n);
return r;
}
int
snmp_registration_match(struct snmp_registration *r, struct agentx_header *h)
{
return (LOAD_U32(r->session_id) == h->session_id) &&
(LOAD_U32(r->transaction_id) == h->transaction_id) &&
(LOAD_U32(r->packet_id) == h->packet_id);
}
/*
* agentx_type_size - get in packet VarBind type size
* @type: VarBind type
*
* Returns length of agentx_type @type in bytes, Variable length types result in
* -1.
*/
int
agentx_type_size(enum agentx_type type)
{
/*
* AGENTX_NULL, AGENTX_NO_SUCH_OBJECT, AGENTX_NO_SUCH_INSTANCE,
* AGENTX_END_OF_MIB_VIEW
*/
if (type >= AGENTX_NO_SUCH_OBJECT || type == AGENTX_NULL)
return 0;
/* AGENTX_INTEGER, AGENTX_COUNTER_32, AGENTX_GAUGE_32, AGENTX_TIME_TICKS */
if (type >= AGENTX_COUNTER_32 && type <= AGENTX_TIME_TICKS ||
type == AGENTX_INTEGER)
return 4;
if (type == AGENTX_COUNTER_64)
return 8;
if (AGENTX_IP_ADDRESS)
return snmp_str_size_from_len(4);
/* AGENTX_OBJECT_ID, AGENTX_OCTET_STRING, AGENTX_OPAQUE */
else
return -1;
}
static inline void
snmp_varbind_type32(struct agentx_varbind *vb, struct snmp_pdu *c, enum agentx_type type, u32 val)
{
ASSUME(agentx_type_size(type) == 4); /* type as 4B representation */
vb->type = type;
u32 *data = snmp_varbind_data(vb);
STORE_PTR(data, val);
data++;
c->buffer = (byte *) data;
}
inline void
snmp_varbind_int(struct snmp_pdu *c, u32 val)
{
snmp_varbind_type32(c->sr_vb_start, c, AGENTX_INTEGER, val);
}
inline void
snmp_varbind_counter32(struct snmp_pdu *c, u32 val)
{
snmp_varbind_type32(c->sr_vb_start, c, AGENTX_COUNTER_32, val);
}
inline void
snmp_varbind_ticks(struct snmp_pdu *c, u32 val)
{
snmp_varbind_type32(c->sr_vb_start, c, AGENTX_TIME_TICKS, val);
}
inline void
snmp_varbind_gauge32(struct snmp_pdu *c, s64 time)
{
snmp_varbind_type32(c->sr_vb_start, c,
AGENTX_GAUGE_32, MAX(0, MIN(time, UINT32_MAX)));
}
inline void
snmp_varbind_ip4(struct snmp_pdu *c, ip4_addr addr)
{
c->sr_vb_start->type = AGENTX_IP_ADDRESS;
c->buffer = snmp_put_ip4(snmp_varbind_data(c->sr_vb_start), addr);
}
/*
* snmp_varbind_nstr - fill varbind context with octet string
* @vb: VarBind to use
* @c: PDU information
* @str: C-string to put as the VarBind data
* @len: length of the string @str
*
* Beware: this function assumes there is enough space in the underlaying
* TX buffer. The caller has to provide that, see snmp_str_size_from_len() for
* more info.
*/
void
snmp_varbind_nstr(struct snmp_pdu *c, const char *str, uint len)
{
c->sr_vb_start->type = AGENTX_OCTET_STRING;
c->buffer = snmp_put_nstr(snmp_varbind_data(c->sr_vb_start), str, len);
}
/*
* snmp_varbind_oid - fill VarBind data with OID @oid_val
* @oid_val - Object Identifier in cpu native byte order
*
* Function puts the @oid_val to the packet byte order.
*/
void
snmp_varbind_oid(struct snmp_pdu *c, const struct oid *oid_val)
{
c->sr_vb_start->type = AGENTX_OBJECT_ID;
snmp_oid_to_buf(snmp_varbind_data(c->sr_vb_start), oid_val);
}
inline enum agentx_type
snmp_search_res_to_type(enum snmp_search_res r)
{
ASSUME(r != SNMP_SEARCH_OK);
enum agentx_type type_arr[] = {
[SNMP_SEARCH_NO_OBJECT] = AGENTX_NO_SUCH_OBJECT,
[SNMP_SEARCH_NO_INSTANCE] = AGENTX_NO_SUCH_INSTANCE,
[SNMP_SEARCH_END_OF_VIEW] = AGENTX_END_OF_MIB_VIEW,
};
return type_arr[r];
}
inline int
snmp_test_close_reason(byte value)
{
if (value >= (byte) AGENTX_CLOSE_OTHER &&
value <= (byte) AGENTX_CLOSE_BY_MANAGER)
return 1;
else
return 0;
}
/*
* Debugging
*/
void UNUSED
snmp_oid_dump(const struct oid *oid)
{
log(L_WARN "OID DUMP ========");
if (oid == NULL)
{
log(L_WARN "is eqaul to NULL");
log(L_WARN "OID DUMP END ====");
log(L_WARN ".");
return;
}
else if (snmp_is_oid_empty(oid))
{
log(L_WARN "is empty");
log(L_WARN "OID DUMP END ====");
log(L_WARN ".");
return;
}
log(L_WARN " #ids: %4u prefix %3u include: %5s",
oid->n_subid, oid->prefix, (oid->include)? "true" : "false");
log(L_WARN "IDS -------------");
for (int i = 0; i < oid->n_subid; i++)
log(L_WARN " %2u: %11u ~ 0x%08X", i, oid->ids[i], oid->ids[i]);
log(L_WARN "OID DUMP END ====");
log(L_WARN);
}
void UNUSED
snmp_oid_log(const struct oid *oid)
{
char buf[1024] = { };
char *pos = buf;
if (snmp_oid_is_prefixed(oid))
{
for (uint i = 0; i < ARRAY_SIZE(snmp_internet); i++)
pos += snprintf(pos, buf + 1024 - pos, ".%u", snmp_internet[i]);
pos += snprintf(pos, buf + 1024 - pos, ".%u", oid->prefix);
}
for (int id = 0; id < oid->n_subid; id++)
pos += snprintf(pos, buf + 1024 - pos, ".%u", oid->ids[id]);
log(L_WARN, "%s", buf);
}
/*
* snmp_oid_common_ancestor - find a common ancestor
* @left: first OID
* @right: second OID
* @out: buffer for result
*
* The @out must be large enough to always fit the resulting OID, a safe value
* is minimum between number of left subids and right subids. The result might
* be NULL OID in cases where there is no common subid. The result could be also
* viewed as longest common prefix. Note that if both @left and @right are
* prefixable but not prefixed the result in @out will also not be prefixed.
*
* This function is used intensively by |snmp_test.c|.
*/
void
snmp_oid_common_ancestor(const struct oid *left, const struct oid *right, struct oid *out)
{
ASSERT(left && right && out);
out->include = 0;
out->reserved = 0;
out->prefix = 0;
u32 offset = 0;
u8 left_ids = left->n_subid, right_ids = right->n_subid;
int l = snmp_oid_is_prefixed(left), r = snmp_oid_is_prefixed(right);
if (l && r)
{
if (left->prefix != right->prefix)
{
out->n_subid = 4;
for (uint id = 0; id < ARRAY_SIZE(snmp_internet); id++)
out->ids[id] = snmp_internet[id];
return;
}
out->prefix = left->prefix;
}
else if (!l && r)
{
if (left_ids == 0)
{
/* finish creating NULL OID */
out->n_subid = 0;
return;
}
for (uint id = 0; id < MIN(ARRAY_SIZE(snmp_internet), left_ids); id++)
{
if (left->ids[id] != snmp_internet[id])
{
out->n_subid = id;
return;
}
out->ids[id] = snmp_internet[id];
}
if (left_ids <= ARRAY_SIZE(snmp_internet))
{
out->n_subid = left_ids;
return;
}
/* index 4 is conresponding to the prefix in prefixed OID */
if (left->ids[4] != (u32) right->prefix)
{
out->n_subid = ARRAY_SIZE(snmp_internet);
return;
}
/* delete snmp_internet from out->ids and store OID prefix */
offset = ARRAY_SIZE(snmp_internet) + 1;
out->n_subid = out->n_subid - ARRAY_SIZE(snmp_internet);
out->prefix = right->prefix;
}
else if (l && !r)
{
snmp_oid_common_ancestor(right, left, out);
return;
}
ASSERT(offset <= left_ids);
u8 subids = 0;
for (u32 id = 0; id < MIN(left_ids - offset, right_ids); id++)
{
if (left->ids[offset + id] == right->ids[id])
{
subids++;
out->ids[id] = right->ids[id];
}
else
break;
}
out->n_subid = subids;
}
/*
* SNMP MIB tree walking
*/
/**
* snmp_walk_init - Try to find exactly matching OID packat VarBind in MIB tree
* @tree: MIB tree to use
* @walk: MIB tree walk state storage
* @c: AgentX PDU creation context
*
* Populate the @walk state and try to find MIB tree leaf equivalent to
* c->sr_vb_start which is requested VarBind to fill based on it's OID name.
* Return value is either pointer to valid MIB tree leaf or NULL if no leaf
* matched.
*/
struct mib_leaf *
snmp_walk_init(struct mib_tree *tree, struct mib_walk_state *walk, struct snmp_pdu *c)
{
mib_tree_walk_init(walk, tree);
mib_node_u *node = mib_tree_find(tree, walk, &c->sr_vb_start->name);
// TODO hide me in mib_tree code
/* mib_tree_find() returns NULL if the oid is longer than existing any path */
if (node == NULL && walk->stack_pos > 0)
node = walk->stack[walk->stack_pos - 1];
return (!node || !mib_node_is_leaf(node)) ? NULL : &node->leaf;
}
/**
* snmp_walk_next - wrapper around MIB tree mib_walk_next() for single call
* @tree: MIB tree to use
* @walk: MIB tree walk state storage
* @c: AgentX PDU creation context
*
* The snmp_walk_next() function searches MIB tree with updates of the VarBind
* OID name with.
*/
struct mib_leaf *
snmp_walk_next(struct mib_tree *tree, struct mib_walk_state *walk, struct snmp_pdu *c)
{
ASSUME(tree && walk);
if (!walk->stack_pos)
return NULL;
mib_node_u *node = walk->stack[walk->stack_pos - 1];
int found = 0;
struct mib_leaf *leaf = &node->leaf;
if (mib_node_is_leaf(node) && leaf->call_next)
{
const struct oid *oid = &c->sr_vb_start->name;
if (mib_tree_walk_oid_compare(walk, oid) > 0)
{
int old = snmp_oid_size(&c->sr_vb_start->name);
if (mib_tree_walk_to_oid(walk,
&c->sr_vb_start->name, 20 * sizeof(u32)))
return NULL;
int new = snmp_oid_size(&c->sr_vb_start->name);
c->buffer += (new - old);
}
found = !leaf->call_next(walk, c);
}
else if (mib_node_is_leaf(node) && c->sr_vb_start->name.include)
{
found = 1;
c->sr_vb_start->name.include = 0;
}
const struct oid *oid = &c->sr_vb_start->name;
u32 skip = (walk->id_pos < oid->n_subid) ?
oid->ids[walk->id_pos] : 0;
while (!found && (leaf = mib_tree_walk_next_leaf(tree, walk, skip)) != NULL)
{
/* mib_tree_walk_next() forces VarBind's name OID overwriting */
int old = snmp_oid_size(&c->sr_vb_start->name);
// TODO autogrow
if (mib_tree_walk_to_oid(walk, &c->sr_vb_start->name, 20 * sizeof(u32)))
return NULL;
int new = snmp_oid_size(&c->sr_vb_start->name);
c->buffer += (new - old);
if (leaf->call_next && !leaf->call_next(walk, c))
found = 1;
else if (!leaf->call_next)
found = 1;
oid = &c->sr_vb_start->name;
skip = (walk->id_pos < oid->n_subid) ?
oid->ids[walk->id_pos] : 0;
}
if (!found)
return NULL;
return leaf;
}
/**
* snmp_walk_fill - fill current VarBind by filler hook invocation
* @leaf: MIB tree leaf with filler hook
* @walk: MIB tree walk state
* @c: AgentX PDU creation context
*
* The function takes responsibility for VarBind type setting (for known VB
* types) and for buffer space allocated for VarBind data (based on type or
* configured size). This simplifies code of filler hooks in most cases.
* We also allow the @leaf to be NULL, in which case we set the VarBind to
* error type noSuchObject.
*/
enum snmp_search_res
snmp_walk_fill(struct mib_leaf *leaf, struct mib_walk_state *walk, struct snmp_pdu *c)
{
struct agentx_varbind *vb = c->sr_vb_start;
enum snmp_search_res res;
/* The OID c->sr_vb_start->name is either left untouched for agentx-Get-PDU,
* or updated by snmp_walk_next() for agentx-GetNext-PDU and agentx-GetBulk-PDU
*
* The null OID in c->sr_o_end means no limits. The OID c->sr_o_end is always
* null for agentx-Get-PDU and therefore evaluates to 0.
*/
if (!snmp_check_search_limit(&c->sr_vb_start->name, c->sr_o_end))
{
res = SNMP_SEARCH_END_OF_VIEW;
vb->type = snmp_search_res_to_type(res);
return res;
}
if (!leaf)
return SNMP_SEARCH_NO_OBJECT;
uint size = 0;
enum agentx_type type = AGENTX_NULL;
if (leaf->size >= 0)
{
if (leaf->type == AGENTX_OCTET_STRING || leaf->type == AGENTX_OPAQUE ||
leaf->type == AGENTX_OBJECT_ID)
{
type = leaf->type;
size = leaf->size;
}
else if (leaf->type != AGENTX_INVALID)
{
type = leaf->type;
size = agentx_type_size(leaf->type);
}
else
size = leaf->size;
}
(void) snmp_tbuf_reserve(c, size);
vb->type = (u16) type;
res = leaf->filler(walk, c);
vb = c->sr_vb_start;
if (res != SNMP_SEARCH_OK)
vb->type = snmp_search_res_to_type(res);
ASSUME(vb->type == leaf->type || vb->type == AGENTX_END_OF_MIB_VIEW ||
vb->type == AGENTX_NO_SUCH_OBJECT || vb->type == AGENTX_NO_SUCH_INSTANCE);
return res;
}

127
proto/snmp/snmp_utils.h Normal file
View File

@ -0,0 +1,127 @@
#ifndef _BIRD_SNMP_UTILS_H_
#define _BIRD_SNMP_UTILS_H_
#include "subagent.h"
#include "mib_tree.h"
/*
*
* AgentX Variable Biding (VarBind) utils
*
*/
/*
* AgentX - Variable Binding (VarBind) type utils
*/
int agentx_type_size(enum agentx_type t);
/* type Octet String */
size_t snmp_str_size_from_len(uint len);
size_t snmp_str_size(const char *str);
/* type OID - Object Identifier */
int snmp_is_oid_empty(const struct oid *oid);
int snmp_pkt_oid_is_prefixable(const struct oid *oid);
uint snmp_oid_size(const struct oid *o);
size_t snmp_oid_size_from_len(uint n_subid);
void snmp_oid_copy(struct oid *dest, const struct oid *src);
int snmp_oid_compare(const struct oid *first, const struct oid *second);
void snmp_oid_common_ancestor(const struct oid *left, const struct oid *right, struct oid *result);
void snmp_oid_from_buf(struct oid *dest, const struct oid *src);
void snmp_oid_to_buf(struct oid *dest, const struct oid *src);
static inline int
snmp_check_search_limit(const struct oid *search, const struct oid *limit)
{
ASSERT(search && limit);
return snmp_is_oid_empty(limit) || snmp_oid_compare(search, limit) < 0;
}
/*
* snmp_oid_is_prefixed - test if OID is prefixed
* @oid: OID to use
*
* Works for both cpu native and packet byte order.
*/
static inline int
snmp_oid_is_prefixed(const struct oid *oid)
{
/* LOAD_U8() is in both cases basic mem load */
return LOAD_U8(oid->prefix) != 0;
}
/* type IPv4 */
int snmp_valid_ip4_index(const struct oid *o, uint start);
int snmp_valid_ip4_index_unsafe(const struct oid *o, uint start);
/*
* AgentX - Variable Binding (VarBind) manupulation
*/
uint snmp_varbind_header_size(const struct oid *vb_name);
uint snmp_varbind_size_unsafe(const struct agentx_varbind *vb);
size_t snmp_varbind_size_from_len(uint n_subid, enum agentx_type t, uint len);
int snmp_test_varbind_type(u16 type);
void *snmp_varbind_data(const struct agentx_varbind *vb);
/*
* AgentX - PDU headers, types, contexts
*/
void snmp_session(const struct snmp_proto *p, struct agentx_header *h);
void snmp_pdu_context(struct snmp_pdu *pdu, struct snmp_proto *p, sock *sk);
static inline int
snmp_has_context(const struct agentx_header *h)
{
return LOAD_U8(h->flags) & AGENTX_NON_DEFAULT_CONTEXT;
}
int snmp_test_close_reason(byte value);
/*
* AgentX - TX buffer manipulation
*/
/* Functions filling buffer a typed value */
void snmp_varbind_int(struct snmp_pdu *c, u32 val);
void snmp_varbind_counter32(struct snmp_pdu *c, u32 val);
void snmp_varbind_gauge32(struct snmp_pdu *c, s64 time);
void snmp_varbind_ticks(struct snmp_pdu *c, u32 val);
void snmp_varbind_ip4(struct snmp_pdu *c, ip4_addr addr);
void snmp_varbind_nstr(struct snmp_pdu *c, const char *str, uint len);
void snmp_varbind_oid(struct snmp_pdu *c, const struct oid *oid_val);
/* Raw */
byte *snmp_no_such_object(byte *buf, struct agentx_varbind *vb, struct oid *oid);
byte *snmp_no_such_instance(byte *buf, struct agentx_varbind *vb, struct oid *oid);
byte *snmp_put_str(byte *buf, const char *str);
byte *snmp_put_nstr(byte *buf, const char *str, uint len);
byte *snmp_put_blank(byte *buf);
byte *snmp_put_ip4(byte *buf, ip4_addr ip4);
byte *snmp_put_fbyte(byte *buf, u8 data);
/*
*
* Helpers, Misc, Debugging
*
*/
struct snmp_registration *snmp_registration_create(struct snmp_proto *p, enum agentx_mibs mib);
int snmp_registration_match(struct snmp_registration *r, struct agentx_header *h);
void snmp_oid_dump(const struct oid *oid);
void snmp_oid_log(const struct oid *oid);
enum agentx_type snmp_search_res_to_type(enum snmp_search_res res);
#define AGENTX_TYPE_INT_SIZE ((uint) agentx_type_size(AGENTX_INTEGER))
#define AGENTX_TYPE_IP4_SIZE ((uint) agentx_type_size(AGENTX_IP_ADDRESS))
#define AGENTX_TYPE_COUNTER32_SIZE ((uint) agentx_type_size(AGENTX_COUNTER_32))
/*
* SNMP MIB tree walking
*/
struct mib_leaf *snmp_walk_init(struct mib_tree *tree, struct mib_walk_state *state, struct snmp_pdu *context);
struct mib_leaf *snmp_walk_next(struct mib_tree *tree, struct mib_walk_state *state, struct snmp_pdu *context);
enum snmp_search_res snmp_walk_fill(struct mib_leaf *leaf, struct mib_walk_state *state, struct snmp_pdu *context);
#endif

1426
proto/snmp/subagent.c Normal file

File diff suppressed because it is too large Load Diff

359
proto/snmp/subagent.h Normal file
View File

@ -0,0 +1,359 @@
#ifndef _BIRD_SNMP_SUBAGENT_H_
#define _BIRD_SNMP_SUBAGENT_H_
#include "nest/bird.h"
#include "snmp.h"
#include "lib/macro.h"
#define AGENTX_VERSION 1
/* standard snmp internet prefix */
#define SNMP_ISO 1 /* last of oid .1 */
#define SNMP_ORG 3 /* last of oid .1.3 */
#define SNMP_DOD 6 /* last of oid .1.3.6 */
#define SNMP_INTERNET 1 /* last of oid .1.3.6.1 */
#define SNMP_MGMT 2 /* last of oid .1.3.6.1.2 */
#define SNMP_MIB_2 1 /* last of oid .1.3.6.1.2.1 */
#define SNMP_SYSTEM 1 /* last of oid .1.3.6.1.2.1.1 */
#define SNMP_OSPF_MIB 14 /* last of oid .1.3.6.1.2.1.14 */
#define SNMP_BGP4_MIB 15 /* last of oid .1.3.6.1.2.1.15 */
#define SNMP_OSPFv3_MIB 192 /* last of oid .1.3.6.1.2.1.192 */
/* sysUpTime */
#define SNMP_SYS_UP_TIME 3 /* last of oid .1.3.6.1.2.1.1.3 */
/* snmpTrapOID */
#define SNMP_V2 6 /* last of oid .1.3.6.1.6 */
#define SNMP_MODULES 3 /* last of oid .1.3.6.1.6.3 */
#define SNMP_ALARM_NEXT_INDEX 1 /* last of oid .1.3.6.1.6.3.1 */
#define SNMP_MIB_OBJECTS 1 /* last of oid .1.3.6.1.6.3.1.1 */
#define SNMP_TRAP 4 /* last of oid .1.3.6.1.6.3.1.1.4 */
#define SNMP_TRAP_OID 1 /* last of oid .1.3.6.1.6.3.1.1.4.1 */
extern const u32 snmp_internet[4];
#define SNMP_DEFAULT_CONTEXT 0
enum agentx_type {
AGENTX_INTEGER = 2,
AGENTX_OCTET_STRING = 4,
AGENTX_NULL = 5,
AGENTX_OBJECT_ID = 6,
AGENTX_IP_ADDRESS = 64,
AGENTX_COUNTER_32 = 65,
AGENTX_GAUGE_32 = 66,
AGENTX_TIME_TICKS = 67,
AGENTX_OPAQUE = 68,
AGENTX_COUNTER_64 = 70,
AGENTX_NO_SUCH_OBJECT = 128,
AGENTX_NO_SUCH_INSTANCE = 129,
AGENTX_END_OF_MIB_VIEW = 130,
AGENTX_INVALID = 0,
} PACKED;
enum snmp_search_res {
SNMP_SEARCH_OK = 0,
SNMP_SEARCH_NO_OBJECT = 1,
SNMP_SEARCH_NO_INSTANCE = 2,
SNMP_SEARCH_END_OF_VIEW = 3,
};
#define AGENTX_PRIORITY 127
#define SNMP_REGISTER_TREE 0
#define SNMP_REGISTER_INSTANCE 1
enum agentx_flags {
AGENTX_FLAG_BLANK = 0x00,
AGENTX_FLAG_INSTANCE_REGISTRATION = 0x01,
AGENTX_FLAG_NEW_INDEX = 0x02,
AGENTX_FLAG_ANY_INDEX = 0x04,
AGENTX_NON_DEFAULT_CONTEXT = 0x08,
AGENTX_NETWORK_BYTE_ORDER = 0x10,
} PACKED;
#define AGENTX_FLAGS_MASK (AGENTX_FLAG_INSTANCE_REGISTRATION \
| AGENTX_FLAG_NEW_INDEX \
| AGENTX_FLAG_ANY_INDEX \
| AGENTX_NON_DEFAULT_CONTEXT \
| AGENTX_NETWORK_BYTE_ORDER)
// TODO - make me compile time option
#define SNMP_NETWORK_BYTE_ORDER
#if !(defined(SNMP_NATIVE) || defined(SNMP_NETWORK_BYTE_ORDER))
# error "SNMP: currently support only native byte order or network byte order."
#endif
#if defined(SNMP_NATIVE) && defined(SNMP_NETWORK_BYTE_ORDER) && !defined(CPU_BIG_ENDIAN)
# error "SNMP: couldn't use both native byte order and network byte order " \
"(big endian) on little endian machine."
#endif
#if (defined(SNMP_NATIVE) && defined(CPU_BIG_ENDIAN)) || defined(SNMP_NETWORK_BYTE_ORDER)
#define SNMP_BYTE_ORDER AGENTX_NETWORK_BYTE_ORDER
#else
#define SNMP_BYTE_ORDER 0
#endif
/* We recommend using STORE_U32 over VALUE_U32 when possible */
#ifdef SNMP_NATIVE
#define STORE_U32(dest, val) ((u32) ((dest) = (u32) (val)))
#define STORE_U16(dest, val) ((u16) ((dest) = (u16) (val)))
#define STORE_U8(dest, val) ((u8) ((dest) = (u8) (val)))
#define STORE_PTR(ptr, val) (*((u32 *) (ptr)) = (u32) (val))
#define VALUE_U32(val) ((u32) (val))
#define VALUE_U16(val) ((u16) (val))
#define VALUE_U8(val) ((u8) (val))
#define LOAD_U32(src) *((u32 *) &(src))
#define LOAD_U16(src) *((u16 *) &(src))
#define LOAD_U8(src) *((u8 *) &(src))
#define LOAD_PTR(ptr) *((u32 *) (ptr))
#endif
#if defined(SNMP_NETWORK_BYTE_ORDER) && (!defined(SNMP_NATIVE) || defined(CPU_BIG_ENDIAN))
#define STORE_U32(dest, val) put_u32(&(dest), (val))
#define STORE_U16(dest, val) put_u16(&(dest), (val))
#define STORE_U8(dest, val) put_u8(&(dest), (val))
#define STORE_PTR(ptr, val) put_u32(ptr, val)
#define VALUE_U32(val) htonl(val)
#define VALUE_U16(val) htons(val)
#define VALUE_U8(val) ((u8) (val))
#define LOAD_U32(src) get_u32(&(src))
#define LOAD_U16(src) get_u16(&(src))
#define LOAD_U8(src) get_u8(&(src))
#define LOAD_PTR(src) get_u32(ptr)
#endif
struct agentx_header {
u8 version;
u8 type;
u8 flags;
u8 reserved; /* always zero filled */
u32 session_id; /* AgentX sessionID established by Open-PDU */
u32 transaction_id; /* last transactionID seen/used */
u32 packet_id; /* last packetID seen/used */
u32 payload; /* payload_length of the packet without header */
};
#define AGENTX_HEADER_SIZE 20
STATIC_ASSERT(AGENTX_HEADER_SIZE == sizeof(struct agentx_header));
struct oid {
u8 n_subid;
u8 prefix;
u8 include;
u8 reserved; /* always zero filled */
u32 ids[];
};
#define STATIC_OID(sbids) \
struct { \
u8 n_subid; \
u8 prefix; \
u8 include; \
u8 reserved; \
u32 ids[sbids]; \
}
#define STATIC_OID_INITIALIZER(sbids, pref, ...) \
{ \
.n_subid = sbids, \
.prefix = pref, \
.include = 0, \
.reserved = 0, \
.ids = { __VA_ARGS__ }, \
}
/* enforced by MIB tree, see mib_tree.h for more info */
#define OID_MAX_LEN 32
/*
* AgentX VarBind -- Variable Binding
* During the processing of the VarBind, the fields @type and @name are in cpu
* native byte order. This should be fixed by running snmp_varbind_leave()
* before VarBind control pointer abondonment or before packet transmission.
* The data following the structure should always follow the packet byte order.
*/
struct agentx_varbind {
u16 type;
u16 reserved; /* always zero filled */
/* oid part */
struct oid name;
/* AgentX variable binding data optionally here */
};
/* AgentX Octet String */
struct agentx_octet_str {
u32 length;
byte data[0];
};
struct agentx_response {
struct agentx_header h;
u32 uptime;
u16 error;
u16 index;
};
STATIC_ASSERT(4 + 2 + 2 + AGENTX_HEADER_SIZE == sizeof(struct agentx_response));
struct agentx_open_pdu {
struct agentx_header h;
u8 timeout;
u8 reserved1; /* reserved u24 */
u16 reserved2; /* whole u24 is always zero filled */
};
struct agentx_close_pdu {
struct agentx_header h;
u8 reason;
u8 reserved1; /* reserved u24 */
u16 reserved2; /* whole u24 is always zero filled */
};
struct agentx_un_register_hdr {
u8 timeout;
u8 priority;
u8 range_subid;
u8 reserved; /* always zero filled */
};
struct agentx_getbulk {
u16 non_repeaters;
u16 max_repetitions;
};
struct agentx_bulk_state {
struct agentx_getbulk getbulk;
u16 index;
u16 repetition;
u32 repeaters;
int has_any; /* flag is clear when all responses are EndOfMibView */
};
enum agentx_pdu_types {
AGENTX_OPEN_PDU = 1, /* agentx-Open-PDU */
AGENTX_CLOSE_PDU = 2, /* agentx-Close-PDU */
AGENTX_REGISTER_PDU = 3, /* agentx-Regiter-PDU */
AGENTX_UNREGISTER_PDU = 4, /* agentx-Unregister-PDU */
AGENTX_GET_PDU = 5, /* agentx-Get-PDU */
AGENTX_GET_NEXT_PDU = 6, /* agentx-GetNext-PDU */
AGENTX_GET_BULK_PDU = 7, /* agentx-GetBulk-PDU */
AGENTX_TEST_SET_PDU = 8, /* agentx-TestSet-PDU */
AGENTX_COMMIT_SET_PDU = 9, /* agentx-CommitSet-PDU */
AGENTX_UNDO_SET_PDU = 10, /* agentx-UndoSet-PDU */
AGENTX_CLEANUP_SET_PDU = 11, /* agentx-CleanupSet-PDU */
AGENTX_NOTIFY_PDU = 12, /* agentx-Notify-PDU */
AGENTX_PING_PDU = 13, /* agentx-Ping-PDU */
AGENTX_INDEX_ALLOCATE_PDU = 14, /* agentx-IndexAllocate-PDU */
AGENTX_INDEX_DEALLOCATE_PDU = 15, /* agentx-IndexDeallocate-PDU */
AGENTX_ADD_AGENT_CAPS_PDU = 16, /* agentx-AddAgentCaps-PDU */
AGENTX_REMOVE_AGENT_CAPS_PDU = 17, /* agentx-RemoveAgentCaps-PDU */
AGENTX_RESPONSE_PDU = 18, /* agentx-Response-PDU */
} PACKED;
/* agentx-Close-PDU close reasons */
enum agentx_close_reasons {
AGENTX_CLOSE_OTHER = 1,
AGENTX_CLOSE_PARSE_ERROR = 2,
AGENTX_CLOSE_PROTOCOL_ERROR = 3,
AGENTX_CLOSE_TIMEOUTS = 4,
AGENTX_CLOSE_SHUTDOWN = 5,
AGENTX_CLOSE_BY_MANAGER = 6,
} PACKED;
/* agentx-Response-PDU - result errors */
enum agentx_response_errs {
/* response error to both Administrative and SNMP messages */
AGENTX_RES_NO_ERROR = 0, /* noAgentXError */
/* response errors to SNMP messages */
AGENTX_RES_GEN_ERROR = 5, /* genError */
AGENTX_RES_NO_ACCESS = 6, /* noAccess */
AGENTX_RES_WRONG_TYPE = 7, /* wrongType */
AGENTX_RES_WRONG_LENGTH = 8, /* wrongLength */
AGENTX_RES_WRONG_ENCODING = 9, /* wrongEncoding */
AGENTX_RES_WRONG_VALUE = 10, /* wrongValue*/
AGENTX_RES_NO_CREATION = 11, /* noCreation */
AGENTX_RES_INCONSISTENT_VALUE = 12, /* inconsistentValue */
AGENTX_RES_RESOURCE_UNAVAILABLE = 13, /* resourceUnavailable */
AGENTX_RES_COMMIT_FAILED = 14, /* commitFailed */
AGENTX_RES_UNDO_FAILED = 15, /* undoFailed */
AGENTX_RES_NOT_WRITABLE = 17, /* notWritable */
AGENTX_RES_INCONSISTENT_NAME = 18, /* inconsistentName */
/* response error to Administrative messages */
AGENTX_RES_OPEN_FAILED = 256, /* openFailed */
AGENTX_RES_NOT_OPEN = 257, /* notOpen */
AGENTX_RES_INDEX_WRONG_TYPE = 258, /* indexWrongType */
AGENTX_RES_INDEX_ALREADY_ALLOC = 259, /* indexAlreadyAlloc */
AGENTX_RES_INDEX_NONE_AVAIL = 260, /* indexNoneAvail */
AGENTX_RES_NOT_ALLOCATED = 261, /* notAllocated */
AGENTX_RES_UNSUPPORTED_CONTEXT = 262, /* unsupportedContext */
AGENTX_RES_DUPLICATE_REGISTER = 263, /* duplicateRegister */
AGENTX_RES_UNKNOWN_REGISTER = 264, /* unknownRegister */
AGENTX_RES_UNKNOWN_AGENT_CAPS = 265, /* unknownAgentCaps */
AGENTX_RES_PARSE_ERROR = 266, /* parseError */
AGENTX_RES_REQUEST_DENIED = 267, /* requestDenied */
AGENTX_RES_PROCESSING_ERR = 268, /* processingError */
} PACKED;
/* SNMP PDU info */
struct snmp_pdu {
struct snmp_proto *p;
/* TX buffer */
byte *buffer; /* pointer to buffer */
uint size; /* unused space in buffer */
/* Search Range */
struct agentx_varbind *sr_vb_start; /* search range starting OID inside TX buffer (final storage) */
const struct oid *sr_o_end; /* search range ending OID */
/* Control */
enum agentx_response_errs error; /* storage for result of current action */
u32 index; /* index on which the error was found */
};
#if 0
struct snmp_packet_info {
node n;
u8 type; // enum type
u32 session_id;
u32 transaction_id;
u32 packet_id;
void *data;
};
#endif
void snmp_start_subagent(struct snmp_proto *p);
void snmp_stop_subagent(struct snmp_proto *p);
void snmp_ping(struct snmp_proto *p);
int snmp_rx(sock *sk, uint size);
void snmp_tx(sock *sk);
void snmp_register(struct snmp_proto *p, struct oid *oid, uint index, uint len, u8 is_instance);
void snmp_unregister(struct snmp_proto *p, struct oid *oid, uint index, uint len);
void snmp_notify_pdu(struct snmp_proto *p, struct oid *oid, void *data, uint size, int include_uptime);
void snmp_register_mibs(struct snmp_proto *p);
struct agentx_varbind *snmp_vb_name_to_tx(struct snmp_pdu *c, const struct oid *oid);
int snmp_tbuf_reserve(struct snmp_pdu *c, size_t bytes);
static inline int
snmp_is_active(const struct snmp_proto *p)
{
/* Note: states in which we have opened socket */
return p->state == SNMP_OPEN || p->state == SNMP_REGISTER ||
p->state == SNMP_CONN;
}
#endif