summaryrefslogtreecommitdiffstats
path: root/ldpd/notification.c
diff options
context:
space:
mode:
Diffstat (limited to 'ldpd/notification.c')
-rw-r--r--ldpd/notification.c377
1 files changed, 377 insertions, 0 deletions
diff --git a/ldpd/notification.c b/ldpd/notification.c
new file mode 100644
index 0000000..1709098
--- /dev/null
+++ b/ldpd/notification.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: ISC
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ */
+
+#include <zebra.h>
+
+#include "ldpd.h"
+#include "ldp.h"
+#include "log.h"
+#include "ldpe.h"
+#include "ldp_debug.h"
+
+static int gen_returned_tlvs(struct ibuf *, uint16_t, uint16_t, char *);
+static void log_msg_notification(int, struct nbr *, struct notify_msg *);
+
+void
+send_notification_full(struct tcp_conn *tcp, struct notify_msg *nm)
+{
+ struct ibuf *buf;
+ uint16_t size;
+ int err = 0;
+
+ /* calculate size */
+ size = LDP_HDR_SIZE + LDP_MSG_SIZE + STATUS_SIZE;
+ if (CHECK_FLAG(nm->flags, F_NOTIF_PW_STATUS))
+ size += PW_STATUS_TLV_SIZE;
+ if (CHECK_FLAG(nm->flags, F_NOTIF_FEC))
+ size += len_fec_tlv(&nm->fec);
+ if (CHECK_FLAG(nm->flags, F_NOTIF_RETURNED_TLVS))
+ size += TLV_HDR_SIZE * 2 + nm->rtlvs.length;
+
+ if ((buf = ibuf_open(size)) == NULL)
+ fatal(__func__);
+
+ SET_FLAG(err, gen_ldp_hdr(buf, size));
+ size -= LDP_HDR_SIZE;
+ SET_FLAG(err, gen_msg_hdr(buf, MSG_TYPE_NOTIFICATION, size));
+ SET_FLAG(err, gen_status_tlv(buf, nm->status_code, nm->msg_id, nm->msg_type));
+ /* optional tlvs */
+ if (CHECK_FLAG(nm->flags, F_NOTIF_PW_STATUS))
+ SET_FLAG(err, gen_pw_status_tlv(buf, nm->pw_status));
+ if (CHECK_FLAG(nm->flags, F_NOTIF_FEC))
+ SET_FLAG(err, gen_fec_tlv(buf, &nm->fec));
+ if (CHECK_FLAG(nm->flags, F_NOTIF_RETURNED_TLVS))
+ SET_FLAG(err, gen_returned_tlvs(buf, nm->rtlvs.type, nm->rtlvs.length,
+ nm->rtlvs.data));
+ if (err) {
+ ibuf_free(buf);
+ return;
+ }
+
+ if (tcp->nbr) {
+ log_msg_notification(1, tcp->nbr, nm);
+ nbr_fsm(tcp->nbr, NBR_EVT_PDU_SENT);
+ tcp->nbr->stats.notif_sent++;
+ }
+
+ /* update SNMP session counters */
+ switch (nm->status_code) {
+ case S_NO_HELLO:
+ leconf->stats.session_rejects_hello++;
+ break;
+ case S_BAD_LDP_ID:
+ leconf->stats.bad_ldp_id++;
+ break;
+ case S_BAD_PDU_LEN:
+ leconf->stats.bad_pdu_len++;
+ break;
+ case S_BAD_MSG_LEN:
+ leconf->stats.bad_msg_len++;
+ break;
+ case S_BAD_TLV_LEN:
+ leconf->stats.bad_tlv_len++;
+ break;
+ case S_BAD_TLV_VAL:
+ leconf->stats.malformed_tlv++;
+ break;
+ case S_KEEPALIVE_TMR:
+ leconf->stats.keepalive_timer_exp++;
+ break;
+ case S_SHUTDOWN:
+ leconf->stats.shutdown_send_notify++;
+ break;
+ default:
+ break;
+ }
+
+ evbuf_enqueue(&tcp->wbuf, buf);
+}
+
+/* send a notification without optional tlvs */
+void
+send_notification(struct tcp_conn *tcp, uint32_t status_code, uint32_t msg_id,
+ uint16_t msg_type)
+{
+ struct notify_msg nm;
+
+ memset(&nm, 0, sizeof(nm));
+ nm.status_code = status_code;
+ nm.msg_id = msg_id;
+ nm.msg_type = msg_type;
+
+ send_notification_full(tcp, &nm);
+}
+
+void
+send_notification_rtlvs(struct nbr *nbr, uint32_t status_code, uint32_t msg_id,
+ uint16_t msg_type, uint16_t tlv_type, uint16_t tlv_len, char *tlv_data)
+{
+ struct notify_msg nm;
+
+ memset(&nm, 0, sizeof(nm));
+ nm.status_code = status_code;
+ nm.msg_id = msg_id;
+ nm.msg_type = msg_type;
+ /* do not append the given TLV if it's too big (shouldn't happen) */
+ if (tlv_len < 1024) {
+ nm.rtlvs.type = tlv_type;
+ nm.rtlvs.length = tlv_len;
+ nm.rtlvs.data = tlv_data;
+ SET_FLAG(nm.flags, F_NOTIF_RETURNED_TLVS);
+ }
+
+ send_notification_full(nbr->tcp, &nm);
+}
+
+int
+recv_notification(struct nbr *nbr, char *buf, uint16_t len)
+{
+ struct ldp_msg msg;
+ struct status_tlv st;
+ struct notify_msg nm;
+ int tlen;
+
+ memcpy(&msg, buf, sizeof(msg));
+ buf += LDP_MSG_SIZE;
+ len -= LDP_MSG_SIZE;
+
+ if (len < STATUS_SIZE) {
+ session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type);
+ leconf->stats.bad_msg_len++;
+ return (-1);
+ }
+ memcpy(&st, buf, sizeof(st));
+
+ if (ntohs(st.length) > STATUS_SIZE - TLV_HDR_SIZE ||
+ ntohs(st.length) > len - TLV_HDR_SIZE) {
+ session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+ leconf->stats.bad_tlv_len++;
+ return (-1);
+ }
+ buf += STATUS_SIZE;
+ len -= STATUS_SIZE;
+
+ memset(&nm, 0, sizeof(nm));
+ nm.status_code = ntohl(st.status_code);
+
+ /* Optional Parameters */
+ while (len > 0) {
+ struct tlv tlv;
+ uint16_t tlv_type;
+ uint16_t tlv_len;
+
+ if (len < sizeof(tlv)) {
+ session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+ leconf->stats.bad_tlv_len++;
+ return (-1);
+ }
+
+ memcpy(&tlv, buf, TLV_HDR_SIZE);
+ tlv_type = ntohs(tlv.type);
+ tlv_len = ntohs(tlv.length);
+ if (tlv_len + TLV_HDR_SIZE > len) {
+ session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+ leconf->stats.bad_tlv_len++;
+ return (-1);
+ }
+ buf += TLV_HDR_SIZE;
+ len -= TLV_HDR_SIZE;
+
+ switch (tlv_type) {
+ case TLV_TYPE_EXTSTATUS:
+ case TLV_TYPE_RETURNEDPDU:
+ case TLV_TYPE_RETURNEDMSG:
+ /* TODO is there any use for this? */
+ break;
+ case TLV_TYPE_PW_STATUS:
+ if (tlv_len != 4) {
+ session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+ return (-1);
+ }
+
+ nm.pw_status = ntohl(*(uint32_t *)buf);
+ SET_FLAG(nm.flags, F_NOTIF_PW_STATUS);
+ break;
+ case TLV_TYPE_FEC:
+ if ((tlen = tlv_decode_fec_elm(nbr, &msg, buf,
+ tlv_len, &nm.fec)) == -1)
+ return (-1);
+ /* allow only one fec element */
+ if (tlen != tlv_len) {
+ session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type);
+ leconf->stats.bad_tlv_len++;
+ return (-1);
+ }
+ SET_FLAG(nm.flags, F_NOTIF_FEC);
+ break;
+ default:
+ if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) {
+ nbr->stats.unknown_tlv++;
+ send_notification_rtlvs(nbr, S_UNKNOWN_TLV,
+ msg.id, msg.type, tlv_type, tlv_len, buf);
+ }
+ /* ignore unknown tlv */
+ break;
+ }
+ buf += tlv_len;
+ len -= tlv_len;
+ }
+
+ /* sanity checks */
+ switch (nm.status_code) {
+ case S_PW_STATUS:
+ if (!CHECK_FLAG(nm.flags, (F_NOTIF_PW_STATUS|F_NOTIF_FEC))) {
+ send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type);
+ return (-1);
+ }
+
+ switch (nm.fec.type) {
+ case MAP_TYPE_PWID:
+ break;
+ default:
+ send_notification(nbr->tcp, S_BAD_TLV_VAL, msg.id, msg.type);
+ return (-1);
+ }
+ break;
+ case S_ENDOFLIB:
+ if (!CHECK_FLAG(nm.flags, F_NOTIF_FEC)) {
+ send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type);
+ return (-1);
+ }
+ if (nm.fec.type != MAP_TYPE_TYPED_WCARD) {
+ send_notification(nbr->tcp, S_BAD_TLV_VAL, msg.id, msg.type);
+ return (-1);
+ }
+ break;
+ default:
+ break;
+ }
+
+ log_msg_notification(0, nbr, &nm);
+
+ if (CHECK_FLAG(st.status_code, htonl(STATUS_FATAL))) {
+ if (nbr->state == NBR_STA_OPENSENT)
+ nbr_start_idtimer(nbr);
+
+ /*
+ * RFC 5036 - Section 3.5.1.1:
+ * "When an LSR receives a Shutdown message during session
+ * initialization, it SHOULD transmit a Shutdown message and
+ * then close the transport connection".
+ */
+ if (nbr->state != NBR_STA_OPER && nm.status_code == S_SHUTDOWN) {
+ leconf->stats.session_attempts++;
+ send_notification(nbr->tcp, S_SHUTDOWN, msg.id, msg.type);
+ }
+
+ leconf->stats.shutdown_rcv_notify++;
+ nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
+ return (-1);
+ }
+
+ /* lde needs to know about a few notification messages
+ * and update SNMP session counters
+ */
+ switch (nm.status_code) {
+ case S_PW_STATUS:
+ case S_ENDOFLIB:
+ ldpe_imsg_compose_lde(IMSG_NOTIFICATION, nbr->peerid, 0, &nm, sizeof(nm));
+ break;
+ case S_NO_HELLO:
+ leconf->stats.session_rejects_hello++;
+ break;
+ case S_PARM_ADV_MODE:
+ leconf->stats.session_rejects_ad++;
+ break;
+ case S_MAX_PDU_LEN:
+ leconf->stats.session_rejects_max_pdu++;
+ break;
+ case S_PARM_L_RANGE:
+ leconf->stats.session_rejects_lr++;
+ break;
+ case S_BAD_LDP_ID:
+ leconf->stats.bad_ldp_id++;
+ break;
+ case S_BAD_PDU_LEN:
+ leconf->stats.bad_pdu_len++;
+ break;
+ case S_BAD_MSG_LEN:
+ leconf->stats.bad_msg_len++;
+ break;
+ case S_BAD_TLV_LEN:
+ leconf->stats.bad_tlv_len++;
+ break;
+ case S_BAD_TLV_VAL:
+ leconf->stats.malformed_tlv++;
+ break;
+ case S_SHUTDOWN:
+ leconf->stats.shutdown_rcv_notify++;
+ break;
+ default:
+ break;
+ }
+
+ return (0);
+}
+
+int
+gen_status_tlv(struct ibuf *buf, uint32_t status_code, uint32_t msg_id,
+ uint16_t msg_type)
+{
+ struct status_tlv st;
+
+ memset(&st, 0, sizeof(st));
+ st.type = htons(TLV_TYPE_STATUS);
+ st.length = htons(STATUS_TLV_LEN);
+ st.status_code = htonl(status_code);
+ /*
+ * For convenience, msg_id and msg_type are already in network
+ * byte order.
+ */
+ st.msg_id = msg_id;
+ st.msg_type = msg_type;
+
+ return (ibuf_add(buf, &st, STATUS_SIZE));
+}
+
+static int
+gen_returned_tlvs(struct ibuf *buf, uint16_t type, uint16_t length,
+ char *tlv_data)
+{
+ struct tlv rtlvs;
+ struct tlv tlv;
+ int err;
+
+ rtlvs.type = htons(TLV_TYPE_RETURNED_TLVS);
+ rtlvs.length = htons(length + TLV_HDR_SIZE);
+ tlv.type = htons(type);
+ tlv.length = htons(length);
+
+ err = ibuf_add(buf, &rtlvs, sizeof(rtlvs));
+ SET_FLAG(err, ibuf_add(buf, &tlv, sizeof(tlv)));
+ SET_FLAG(err, ibuf_add(buf, tlv_data, length));
+
+ return (err);
+}
+
+void
+log_msg_notification(int out, struct nbr *nbr, struct notify_msg *nm)
+{
+ if (nm->status_code & STATUS_FATAL) {
+ debug_msg(out, "notification: lsr-id %pI4, status %s (fatal error)", &nbr->id,
+ status_code_name(nm->status_code));
+ return;
+ }
+
+ debug_msg(out, "notification: lsr-id %pI4, status %s",
+ &nbr->id, status_code_name(nm->status_code));
+ if (CHECK_FLAG(nm->flags, F_NOTIF_FEC))
+ debug_msg(out, "notification: fec %s", log_map(&nm->fec));
+ if (CHECK_FLAG(nm->flags, F_NOTIF_PW_STATUS))
+ debug_msg(out, "notification: pw-status %s",
+ (nm->pw_status == PW_FORWARDING) ? "forwarding" : "not forwarding");
+}