/* * BIRD -- Simple Network Management Protocol (SNMP) * * (c) 2022 Vojtech Vilimek * (c) 2022 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 * the BIRD intergration, |subagent.c| contains functions for creating and * parsing packets, |bgp_mib.c| takes care of the bgp MIB subtree of standard * BGP4-MIB and |snmp_utils.c| which is collections of helper functions for * working with OIDs, VarBinds. * * 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 communication socket * V * +-----------------+ * | SNMP_LOCKED | object lock aquired * +-----------------+ * | * | opening communication socket * V * +-----------------+ * | SNMP_OPEN | socket created, starting subagent * +-----------------+ * | * | BIRD receive response for Open-PDU * V * +-----------------+ * | SNMP_REGISTER | session was established, subagent registers MIBs * +-----------------+ * | * | subagent received responses for all registration requests * V * +-----------------+ * | SNMP_CONN | everything is set * +-----------------+ * | * | function snmp_shutdown() is called, BIRD sends Close-PDU * V * +-----------------+ * | SNMP_STOP | waiting for response * +-----------------+ * | * | cleaning old state information * V * +-----------------+ * | SNMP_DOWN | session is closed * +-----------------+ * * * +-----------------+ * | SNMP_RESET | waiting to transmit response to malformed packet * +-----------------+ * | * | response was send, reseting the session (with socket) * | * \--> SNMP_LOCKED * * * Erroneous transitions: * SNMP is UP in states SNMP_CONN and also in SNMP_REGISTER because the * session is establised and the GetNext request should be responsed * without regard to MIB registration. * * When the session has been closed for some reason (socket error, receipt of * Close-PDU) SNMP cleans the session information and message queue and goes * back to the SNMP_LOCKED state. * * 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" static void snmp_start_locked(struct object_lock *lock); static void snmp_sock_err(sock *sk, int err); static void snmp_stop_timeout(timer *tm); static void snmp_cleanup(struct snmp_proto *p); /* * 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 - 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 direcly reset the session. */ static void snmp_tx(sock *sk) { struct snmp_proto *p = sk->data; snmp_sock_disconnect(p, 1); } /* * snmp_set_state - change state with associated actions * @p - SNMP protocol instance * @state - new SNMP protocol state */ void snmp_set_state(struct snmp_proto *p, enum snmp_proto_state state) { enum snmp_proto_state last = p->state; TRACE(D_EVENTS, "SNMP changing state to %u", state); p->state = state; if (last == SNMP_RESET) rfree(p->sock); switch (state) { case SNMP_INIT: ASSERT(last == SNMP_DOWN); struct object_lock *lock; lock = p->lock = olock_new(p->pool); /* * lock->addr * lock->port * lock->iface * lock->vrf */ lock->type = OBJLOCK_TCP; lock->hook = snmp_start_locked; lock->data = p; olock_acquire(lock); break; case SNMP_LOCKED: ASSERT(last == SNMP_INIT || SNMP_RESET); sock *s = sk_new(p->pool); s->type = SK_TCP_ACTIVE; s->saddr = p->local_ip; s->daddr = p->remote_ip; s->dport = p->remote_port; s->rbsize = SNMP_RX_BUFFER_SIZE; s->tbsize = SNMP_TX_BUFFER_SIZE; /* s->tos = IP_PREC_INTERNET_CONTROL */ 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) { rfree(s); p->sock = NULL; tm_start(p->startup_timer, p->timeout); } break; case SNMP_OPEN: ASSERT(last == SNMP_LOCKED); p->sock->rx_hook = snmp_rx; p->sock->tx_hook = NULL; snmp_start_subagent(p); // handle no response (for long time) break; case SNMP_REGISTER: ASSERT(last == SNMP_OPEN); snmp_register_mibs(p); break; case SNMP_CONN: ASSERT(last == SNMP_REGISTER); proto_notify_state(&p->p, PS_UP); break; case SNMP_STOP: ASSUME(last == SNMP_REGISTER || last == SNMP_CONN); snmp_stop_subagent(p); p->startup_timer->hook = snmp_stop_timeout; tm_start(p->startup_timer, p->timeout); proto_notify_state(&p->p, PS_STOP); break; case SNMP_DOWN: //ASSUME(last == SNMP_STOP || SNMP_INIT || SNMP_LOCKED || SNMP_OPEN); snmp_cleanup(p); // FIXME: handle the state in which we call proto_notify_state and // immediately return PS_DOWN from snmp_shutdown() proto_notify_state(&p->p, PS_DOWN); break; case SNMP_RESET: ASSUME(last == SNMP_REGISTER || last == SNMP_CONN); ASSERT(p->sock); p->sock->rx_hook = snmp_rx_skip; p->sock->tx_hook = snmp_tx; break; default: die("unknown state transition"); } } /* * snmp_init - preinitialize SNMP instance * @CF - SNMP configuration generic handle * * Return value is 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; /* default starting state */ //p->state = SNMP_DOWN; // (0) return P; } /* * 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. * Function snmp_cleanup() does not change the protocol state to PS_DOWN for * practical reasons, it should be done by the caller. */ 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; } struct snmp_registered_oid *ro, *ro2; WALK_LIST_DELSAFE(ro, ro2, p->bgp_registered) { rem_node(&r->n); mb_free(ro); ro = NULL; } HASH_FREE(p->bgp_hash); rfree(p->lp); p->bgp_trie = NULL; } #if 0 /* * snmp_down - stop the SNMP protocol and free resources * @p - SNMP protocol instance * * AgentX session is destroyed by closing underlying socket and all resources * are freed. Afterwards, the PS_DOWN protocol state is announced. */ void snmp_down(struct snmp_proto *p) { snmp_cleanup(p); proto_notify_state(&p->p, PS_DOWN); } #endif /* * snmp_connected - start AgentX session on established channel * @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_sock_disconnect - end or reset socket connection * @p - SNMP protocol instance * * If the @reconnect flags is set, we close the socket and then reestablish * the AgentX session by reentering the start procedure as from the * snmp_start_locked() function. * Otherwise we simply shutdown the SNMP protocol if the flag is clear. * This function is internal and shouldn't be used outside the SNMP module. */ void snmp_sock_disconnect(struct snmp_proto *p, int reconnect) { tm_stop(p->ping_timer); if (!reconnect) { snmp_set_state(p, SNMP_DOWN); return; } //proto_notify_state(&p->p, PS_START); rfree(p->sock); p->sock = NULL; // TODO - wouldn't be better to use inter jump state RESET (soft reset) ? snmp_set_state(p, SNMP_DOWN); /* We try to reconnect after a short delay */ //p->startup_timer->hook = snmp_startup_timeout; //tm_start(p->startup_timer, 4); // TODO make me configurable } /* * snmp_sock_err - handle errors on socket by reopenning the socket * @sk - socket owned by SNMP protocol instance * @err - socket error errno */ static void snmp_sock_err(sock *sk, int UNUSED err) { struct snmp_proto *p = sk->data; TRACE(D_EVENTS, "SNMP socket error %d", err); snmp_sock_disconnect(p, 1); } /* * 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; log(L_INFO "SNMP startup timer or btime %t ? %ld", p->startup_delay, p->startup_timer); 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_reconnect - helper restarting the AgentX session on packet errors * @tm - the startup_timer holding the SNMP protocol instance * * Rerun the SNMP module start procedure. Used in situations when the master * agent returns an agentx-Response-PDU with 'Not Opened' error. We do not close * the socket if have one. */ void snmp_reconnect(timer *tm) { struct snmp_proto *p = tm->data; // TODO snmp_set_state(p, SNMP_DOWN); return; if (p->state == SNMP_STOP || p->state == SNMP_DOWN) return; // TO-DO is SNMP_RESET really needed ? if (p->state == SNMP_INIT || p->state == SNMP_RESET) //snmp_startup(p); {} if (!p->sock) snmp_start_locked(p->lock); else snmp_connected(p->sock); } #if 0 /* * snmp_startup - start initialized SNMP protocol * @p - SNMP protocol to start * * Starting of SNMP protocols begins with address acqusition through object * lock. Next step is handled by snmp_start_locked() function. * This function is internal and shouldn't be used outside the SNMP * module. */ void snmp_startup(struct snmp_proto *p) { // TODO inline to snmp_start() if (p->state == SNMP_OPEN || p->state == SNMP_REGISTER || p->state == SNMP_CONN) { return; } if (p->lock) { snmp_start_locked(p->lock); return; } p->state = SNMP_INIT; struct object_lock *lock; lock = p->lock = olock_new(p->pool); // lock->addr // lock->port // lock->iface // lock->vrf lock->type = OBJLOCK_TCP; lock->hook = snmp_start_locked; lock->data = p; olock_acquire(lock); } #endif /* * 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 resend * an agentx-Open-PDU for non-responding master agent. */ void snmp_startup_timeout(timer *tm) { struct snmp_proto *p = tm->data; snmp_set_state(p, SNMP_LOCKED); } /* * snmp_stop_timeout - a timeout for nonresponding master agent * @tm - the startup_timer holding the SNMP protocol instance. * * We are shutting down the SNMP protocol instance and we sent the * agentx-Close-PDU. This function forcefully closes the AgentX session and * stops the SNMP protocol instance. Used only when we did not receive any * agentx-Response-PDU for the sent closed packet (before timeout). */ static void snmp_stop_timeout(timer *tm) { struct snmp_proto *p = tm->data; 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 and reset the timer for next ping. */ static void snmp_ping_timeout(timer *tm) { struct snmp_proto *p = tm->data; snmp_ping(p); } /* * 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 asynchornously open * a AgentX subagent session through snmp_startup() function call. */ 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->remote_ip = cf->remote_ip; p->local_port = cf->local_port; p->remote_port = cf->remote_port; p->bgp_local_as = cf->bgp_local_as; p->bgp_local_id = cf->bgp_local_id; p->timeout = cf->timeout; // add default value for startup_delay inside bison .Y file p->startup_delay = cf->startup_delay; p->pool = p->p.pool; p->lp = lp_new(p->pool); p->bgp_trie = f_new_trie(p->lp, 0); 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); init_list(&p->bgp_registered); /* We create copy of bonds to BGP protocols. */ HASH_INIT(p->bgp_hash, p->pool, 10); snmp_bgp_start(p); p->last_header = NULL; snmp_set_state(p, SNMP_INIT); return PS_START; } /* * snmp_reconfigure - Test if SNMP instance is reconfigurable * @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. Otherwise we deny reconfiguration because * the implementation would be cumbersome. */ static int snmp_reconfigure(struct proto *P, struct proto_config *CF) { // remove lost bonds, add newly created struct snmp_proto *p = SKIP_BACK(struct snmp_proto, p, P); const struct snmp_config *new = SKIP_BACK(struct snmp_config, cf, CF); const struct snmp_config *old = SKIP_BACK(struct snmp_config, cf, p->p.cf); struct snmp_bond *b1, *b2; WALK_LIST(b1, new->bgp_entries) { WALK_LIST(b2, old->bgp_entries) { if (!strcmp(b1->config->name, b2->config->name)) goto skip; } return 0; skip:; } return !memcmp(((byte *) old) + sizeof(struct proto_config), ((byte *) new) + sizeof(struct proto_config), OFFSETOF(struct snmp_config, description) - sizeof(struct proto_config)) && ! strncmp(old->description, new->description, UINT32_MAX); } /* * 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 %u", p->state); cli_msg(-1006, " MIBs"); // TODO move me into the bgp_mib.c cli_msg(-1006, " BGP4-MIB"); cli_msg(-1006, " enabled true"); // TODO cli_msg(-1006, " Local AS %u", p->bgp_local_as); cli_msg(-1006, " Local router id %R", p->bgp_local_id); cli_msg(-1006, " BGP peers"); HASH_WALK(p->bgp_hash, next, peer) { cli_msg(-1006, " protocol name: %s", peer->bgp_proto->p.name); cli_msg(-1006, " remote IPv4 address: %I4", peer->peer_ip); } HASH_WALK_END; } /* * snmp_postconfig - Check configuration correctness * @CF - SNMP procotol configuration generic handle */ static void snmp_postconfig(struct proto_config *CF) { /* Walk the BGP protocols and cache their references. */ if (((struct snmp_config *) CF)->bgp_local_as == 0) cf_error("local as not specified"); } /* * snmp_shutdown - Forcefully stop the SNMP protocol instance * @P - SNMP protocol generic handle * * If we have established connection, we firstly stop the subagent and then * later cleanup the protocol. The subagent stopping consist of sending the * agentx-Close-PDU and changing the current protocol state to PS_STOP. * If we have no connection created, we simple do the cleanup. * The cleanup is transition straight to PS_DOWN state with snmp_cleanup() call. */ static int snmp_shutdown(struct proto *P) { struct snmp_proto *p = SKIP_BACK(struct snmp_proto, p, P); if (p->state == SNMP_REGISTER || p->state == SNMP_CONN) { /* We have a connection established (at leased send out Open-PDU). */ snmp_set_state(p, SNMP_STOP); return PS_STOP; } else { /* We did not create a connection, we clean the lock and other stuff. */ snmp_set_state(p, SNMP_DOWN); return PS_DOWN; } } /* * Protocol infrastructure */ struct protocol proto_snmp = { .name = "SNMP", .template = "snmp%d", .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, .shutdown = snmp_shutdown, .show_proto_info = snmp_show_proto_info, }; void snmp_build(void) { proto_build(&proto_snmp); }