root/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_if.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2006 Shteryana Shopova <syrinx@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Bridge MIB implementation for SNMPd.
 * Bridge interface objects.
 */

#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>

#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_mib.h>
#include <net/if_types.h>

#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

#include <bsnmp/snmpmod.h>
#include <bsnmp/snmp_mibII.h>

#define SNMPTREE_TYPES
#include "bridge_tree.h"
#include "bridge_snmp.h"
#include "bridge_oid.h"

static const struct asn_oid oid_newRoot = OIDX_newRoot;
static const struct asn_oid oid_TopologyChange = OIDX_topologyChange;
static const struct asn_oid oid_begemotBrigeName = \
                        OIDX_begemotBridgeBaseName;
static const struct asn_oid oid_begemotNewRoot = OIDX_begemotBridgeNewRoot;
static const struct asn_oid oid_begemotTopologyChange = \
                        OIDX_begemotBridgeTopologyChange;

TAILQ_HEAD(bridge_ifs, bridge_if);

/*
 * Free the bridge interface list.
 */
static void
bridge_ifs_free(struct bridge_ifs *headp)
{
        struct bridge_if *b;

        while ((b = TAILQ_FIRST(headp)) != NULL) {
                TAILQ_REMOVE(headp, b, b_if);
                free(b);
        }
}

/*
 * Insert an entry in the bridge interface TAILQ. Keep the
 * TAILQ sorted by the bridge's interface name.
 */
static void
bridge_ifs_insert(struct bridge_ifs *headp,
        struct bridge_if *b)
{
        struct bridge_if *temp;

        if ((temp = TAILQ_FIRST(headp)) == NULL ||
            strcmp(b->bif_name, temp->bif_name) < 0) {
                TAILQ_INSERT_HEAD(headp, b, b_if);
                return;
        }

        TAILQ_FOREACH(temp, headp, b_if)
                if(strcmp(b->bif_name, temp->bif_name) < 0)
                        TAILQ_INSERT_BEFORE(temp, b, b_if);

        TAILQ_INSERT_TAIL(headp, b, b_if);
}

/* The global bridge interface list. */
static struct bridge_ifs bridge_ifs = TAILQ_HEAD_INITIALIZER(bridge_ifs);
static time_t bridge_list_age;

/*
 * Free the global list.
 */
void
bridge_ifs_fini(void)
{
        bridge_ifs_free(&bridge_ifs);
}

/*
 * Find a bridge interface entry by the bridge interface system index.
 */
struct bridge_if *
bridge_if_find_ifs(uint32_t sysindex)
{
        struct bridge_if *b;

        TAILQ_FOREACH(b, &bridge_ifs, b_if)
                if (b->sysindex == sysindex)
                        return (b);

        return (NULL);
}

/*
 * Find a bridge interface entry by the bridge interface name.
 */
struct bridge_if *
bridge_if_find_ifname(const char *b_name)
{
        struct bridge_if *b;

        TAILQ_FOREACH(b, &bridge_ifs, b_if)
                if (strcmp(b_name, b->bif_name) == 0)
                        return (b);

        return (NULL);
}

/*
 * Find a bridge name by the bridge interface system index.
 */
const char *
bridge_if_find_name(uint32_t sysindex)
{
        struct bridge_if *b;

        TAILQ_FOREACH(b, &bridge_ifs, b_if)
                if (b->sysindex == sysindex)
                        return (b->bif_name);

        return (NULL);
}

/*
 * Given two bridge interfaces' system indexes, find their
 * corresponding names and return the result of the name
 * comparison. Returns:
 * error : -2
 * i1 < i2 : -1
 * i1 > i2 : +1
 * i1 = i2 : 0
 */
int
bridge_compare_sysidx(uint32_t i1, uint32_t i2)
{
        int c;
        const char *b1, *b2;

        if (i1 == i2)
                return (0);

        if ((b1 = bridge_if_find_name(i1)) == NULL) {
                syslog(LOG_ERR, "Bridge interface %d does not exist", i1);
                return (-2);
        }

        if ((b2 = bridge_if_find_name(i2)) == NULL) {
                syslog(LOG_ERR, "Bridge interface %d does not exist", i2);
                return (-2);
        }

        if ((c = strcmp(b1, b2)) < 0)
                return (-1);
        else if (c > 0)
                return (1);

        return (0);
}

/*
 * Fetch the first bridge interface from the list.
 */
struct bridge_if *
bridge_first_bif(void)
{
        return (TAILQ_FIRST(&bridge_ifs));
}

/*
 * Fetch the next bridge interface from the list.
 */
struct bridge_if *
bridge_next_bif(struct bridge_if *b_pr)
{
        return (TAILQ_NEXT(b_pr, b_if));
}

/*
 * Create a new entry for a bridge interface and insert
 * it in the list.
 */
static struct bridge_if *
bridge_new_bif(const char *bif_n, uint32_t sysindex, const u_char *physaddr)
{
        struct bridge_if *bif;

        if ((bif = (struct bridge_if *) malloc(sizeof(*bif)))== NULL) {
                syslog(LOG_ERR, "bridge new interface failed: %s",
                    strerror(errno));
                return (NULL);
        }

        bzero(bif, sizeof(struct bridge_if));
        strlcpy(bif->bif_name, bif_n, IFNAMSIZ);
        bcopy(physaddr, bif->br_addr.octet, ETHER_ADDR_LEN);
        bif->sysindex = sysindex;
        bif->br_type = BaseType_transparent_only;
        /* 1 - all bridges default hold time * 100 - centi-seconds */
        bif->hold_time = 1 * 100;
        bif->prot_spec = dot1dStpProtocolSpecification_ieee8021d;
        bridge_ifs_insert(&bridge_ifs, bif);

        return (bif);
}

/*
 * Remove a bridge interface from the list, freeing all it's ports
 * and address entries.
 */
void
bridge_remove_bif(struct bridge_if *bif)
{
        bridge_members_free(bif);
        bridge_addrs_free(bif);
        TAILQ_REMOVE(&bridge_ifs, bif, b_if);
        free(bif);
}


/*
 * Prepare the variable (bridge interface name) for the private
 * begemot notifications.
 */
static struct snmp_value*
bridge_basename_var(struct bridge_if *bif, struct snmp_value* b_val)
{
        uint i;

        b_val->var = oid_begemotBrigeName;
        b_val->var.subs[b_val->var.len++] = strlen(bif->bif_name);

        if ((b_val->v.octetstring.octets = (u_char *)
            malloc(strlen(bif->bif_name))) == NULL)
                return (NULL);

        for (i = 0; i < strlen(bif->bif_name); i++)
                b_val->var.subs[b_val->var.len++] = bif->bif_name[i];

        b_val->v.octetstring.len = strlen(bif->bif_name);
        bcopy(bif->bif_name, b_val->v.octetstring.octets,
            strlen(bif->bif_name));
        b_val->syntax = SNMP_SYNTAX_OCTETSTRING;

        return (b_val);
}

/*
 * Compare the values of the old and the new root port and
 * send a new root notification, if they are not matching.
 */
static void
bridge_new_root(struct bridge_if *bif)
{
        struct snmp_value bif_idx;

        if (bridge_get_default() == bif)
                snmp_send_trap(&oid_newRoot, (struct snmp_value *) NULL);

        if (bridge_basename_var(bif, &bif_idx) == NULL)
                return;

        snmp_send_trap(&oid_begemotTopologyChange,
            &bif_idx, (struct snmp_value *) NULL);
}

/*
 * Compare the new and old topology change times and send a
 * topology change notification if necessary.
 */
static void
bridge_top_change(struct bridge_if *bif)
{
        struct snmp_value bif_idx;

        if (bridge_get_default() == bif)
                snmp_send_trap(&oid_TopologyChange,
                    (struct snmp_value *) NULL);

        if (bridge_basename_var(bif, &bif_idx) == NULL)
                return;

        snmp_send_trap(&oid_begemotNewRoot,
            &bif_idx, (struct snmp_value *) NULL);
}

static int
bridge_if_create(const char* b_name, int8_t up)
{
        if (bridge_create(b_name) < 0)
                return (-1);

        if (up == 1 && (bridge_set_if_up(b_name, 1) < 0))
                return (-1);

        /*
         * Do not create a new bridge entry here -
         * wait until the mibII module notifies us.
         */
        return (0);
}

static int
bridge_if_destroy(struct bridge_if *bif)
{
        if (bridge_destroy(bif->bif_name) < 0)
                return (-1);

        bridge_remove_bif(bif);

        return (0);
}

/*
 * Calculate the timeticks since the last topology change.
 */
static int
bridge_get_time_since_tc(struct bridge_if *bif, uint32_t *ticks)
{
        struct timeval ct;

        if (gettimeofday(&ct, NULL) < 0) {
                syslog(LOG_ERR, "bridge get time since last TC:"
                    "gettimeofday failed: %s", strerror(errno));
                return (-1);
        }

        if (ct.tv_usec - bif->last_tc_time.tv_usec < 0) {
                ct.tv_sec -= 1;
                ct.tv_usec += 1000000;
        }

        ct.tv_sec -= bif->last_tc_time.tv_sec;
        ct.tv_usec -= bif->last_tc_time.tv_usec;

        *ticks = ct.tv_sec * 100 + ct.tv_usec/10000;

        return (0);
}

/*
 * Update the info we have for a single bridge interface.
 * Return:
 * 1, if successful
 * 0, if the interface was deleted
 * -1, error occurred while fetching the info from the kernel.
 */
static int
bridge_update_bif(struct bridge_if *bif)
{
        struct mibif *ifp;

        /* Walk through the mibII interface list. */
        for (ifp = mib_first_if(); ifp != NULL; ifp = mib_next_if(ifp))
                if (strcmp(ifp->name, bif->bif_name) == 0)
                        break;

        if (ifp == NULL) {
                /* Ops, we do not exist anymore. */
                bridge_remove_bif(bif);
                return (0);
        }

        if (ifp->physaddr != NULL )
                bcopy(ifp->physaddr, bif->br_addr.octet, ETHER_ADDR_LEN);
        else
                bridge_get_basemac(bif->bif_name, bif->br_addr.octet,
                    ETHER_ADDR_LEN);

        if (ifp->mib.ifmd_flags & IFF_RUNNING)
                bif->if_status = RowStatus_active;
        else
                bif->if_status = RowStatus_notInService;

        switch (bridge_getinfo_bif(bif)) {
                case 2:
                        bridge_new_root(bif);
                        break;
                case 1:
                        bridge_top_change(bif);
                        break;
                case -1:
                        bridge_remove_bif(bif);
                        return (-1);
                default:
                        break;
        }

        /*
         * The number of ports is accessible via SNMP -
         * update the ports each time the bridge interface data
         * is refreshed too.
         */
        bif->num_ports = bridge_update_memif(bif);
        bif->entry_age = time(NULL);

        return (1);
}

/*
 * Update all bridge interfaces' ports only -
 * make sure each bridge interface exists first.
 */
void
bridge_update_all_ports(void)
{
        struct mibif *ifp;
        struct bridge_if *bif, *t_bif;

        for (bif = bridge_first_bif(); bif != NULL; bif = t_bif) {
                t_bif = bridge_next_bif(bif);

                for (ifp = mib_first_if(); ifp != NULL;
                    ifp = mib_next_if(ifp))
                        if (strcmp(ifp->name, bif->bif_name) == 0)
                                break;

                if (ifp != NULL)
                        bif->num_ports = bridge_update_memif(bif);
                else  /* Ops, we do not exist anymore. */
                        bridge_remove_bif(bif);
        }

        bridge_ports_update_listage();
}

/*
 * Update all addresses only.
 */
void
bridge_update_all_addrs(void)
{
        struct mibif *ifp;
        struct bridge_if *bif, *t_bif;

        for (bif = bridge_first_bif(); bif != NULL; bif = t_bif) {
                t_bif = bridge_next_bif(bif);

                for (ifp = mib_first_if(); ifp != NULL;
                    ifp = mib_next_if(ifp))
                        if (strcmp(ifp->name, bif->bif_name) == 0)
                                break;

                if (ifp != NULL)
                        bif->num_addrs = bridge_update_addrs(bif);
                else  /* Ops, we don't exist anymore. */
                        bridge_remove_bif(bif);
        }

        bridge_addrs_update_listage();
}

/*
 * Update only the bridge interfaces' data - skip addresses.
 */
void
bridge_update_all_ifs(void)
{
        struct bridge_if *bif, *t_bif;

        for (bif = bridge_first_bif(); bif != NULL; bif = t_bif) {
                t_bif = bridge_next_bif(bif);
                bridge_update_bif(bif);
        }

        bridge_ports_update_listage();
        bridge_list_age = time(NULL);
}

/*
 * Update all info we have for all bridges.
 */
void
bridge_update_all(void *arg __unused)
{
        struct bridge_if *bif, *t_bif;

        for (bif = bridge_first_bif(); bif != NULL; bif = t_bif) {
                t_bif = bridge_next_bif(bif);
                if (bridge_update_bif(bif) <= 0)
                        continue;

                /* Update our learnt addresses. */
                bif->num_addrs = bridge_update_addrs(bif);
        }

        bridge_list_age = time(NULL);
        bridge_ports_update_listage();
        bridge_addrs_update_listage();
}

/*
 * Callback for polling our last topology change time -
 * check whether we are root or whether a TC was detected once every
 * 30 seconds, so that we can send the newRoot and TopologyChange traps
 * on time. The rest of the data is polled only once every 5 min.
 */
void
bridge_update_tc_time(void *arg __unused)
{
        struct bridge_if *bif;
        struct mibif *ifp;

        TAILQ_FOREACH(bif, &bridge_ifs, b_if) {
                /* Walk through the mibII interface list. */
                for (ifp = mib_first_if(); ifp != NULL; ifp = mib_next_if(ifp))
                        if (strcmp(ifp->name, bif->bif_name) == 0)
                                break;

                if (ifp == NULL) {
                        bridge_remove_bif(bif);
                        continue;
                }

                switch (bridge_get_op_param(bif)) {
                        case 2:
                                bridge_new_root(bif);
                                break;
                        case 1:
                                bridge_top_change(bif);
                                break;
                }
        }
}

/*
 * Callback for handling new bridge interface creation.
 */
int
bridge_attach_newif(struct mibif *ifp)
{
        u_char mac[ETHER_ADDR_LEN];
        struct bridge_if *bif;

        if (ifp->mib.ifmd_data.ifi_type != IFT_BRIDGE)
                return (0);

        /* Make sure it does not exist in our list. */
        TAILQ_FOREACH(bif, &bridge_ifs, b_if)
                if(strcmp(bif->bif_name, ifp->name) == 0) {
                        syslog(LOG_ERR, "bridge interface %s already "
                            "in list", bif->bif_name);
                        return (-1);
                }

        if (ifp->physaddr == NULL) {
                if (bridge_get_basemac(ifp->name, mac, sizeof(mac)) == NULL) {
                        syslog(LOG_ERR, "bridge attach new %s failed - "
                            "no bridge mac address", ifp->name);
                        return (-1);
                }
        } else
                bcopy(ifp->physaddr, &mac, sizeof(mac));

        if ((bif = bridge_new_bif(ifp->name, ifp->sysindex, mac)) == NULL)
                return (-1);

        if (ifp->mib.ifmd_flags & IFF_RUNNING)
                bif->if_status = RowStatus_active;
        else
                bif->if_status = RowStatus_notInService;

        /* Skip sending notifications if the interface was just created. */
        if (bridge_getinfo_bif(bif) < 0 ||
            (bif->num_ports = bridge_getinfo_bif_ports(bif)) < 0 ||
            (bif->num_addrs = bridge_getinfo_bif_addrs(bif)) < 0) {
                bridge_remove_bif(bif);
                return (-1);
        }

        /* Check whether we are the default bridge interface. */
        if (strcmp(ifp->name, bridge_get_default_name()) == 0)
                bridge_set_default(bif);

        return (0);
}

void
bridge_ifs_dump(void)
{
        struct bridge_if *bif;

        for (bif = bridge_first_bif(); bif != NULL;
                bif = bridge_next_bif(bif)) {
                syslog(LOG_ERR, "Bridge %s, index - %d", bif->bif_name,
                    bif->sysindex);
                bridge_ports_dump(bif);
                bridge_addrs_dump(bif);
        }
}

/*
 * RFC4188 specifics.
 */
int
op_dot1d_base(struct snmp_context *ctx __unused, struct snmp_value *value,
        uint sub, uint iidx __unused, enum snmp_op op)
{
        struct bridge_if *bif;

        if ((bif = bridge_get_default()) == NULL)
                return (SNMP_ERR_NOSUCHNAME);

        if (time(NULL) - bif->entry_age > bridge_get_data_maxage() &&
            bridge_update_bif(bif) <= 0) /* It was just deleted. */
                return (SNMP_ERR_NOSUCHNAME);

        switch (op) {
            case SNMP_OP_GET:
                switch (value->var.subs[sub - 1]) {
                    case LEAF_dot1dBaseBridgeAddress:
                        return (string_get(value, bif->br_addr.octet,
                            ETHER_ADDR_LEN));
                    case LEAF_dot1dBaseNumPorts:
                        value->v.integer = bif->num_ports;
                        return (SNMP_ERR_NOERROR);
                    case LEAF_dot1dBaseType:
                        value->v.integer = bif->br_type;
                        return (SNMP_ERR_NOERROR);
                }
                abort();

                case SNMP_OP_SET:
                    return (SNMP_ERR_NOT_WRITEABLE);

                case SNMP_OP_GETNEXT:
                case SNMP_OP_ROLLBACK:
                case SNMP_OP_COMMIT:
                   break;
        }

        abort();
}

int
op_dot1d_stp(struct snmp_context *ctx, struct snmp_value *val, uint sub,
    uint iidx __unused, enum snmp_op op)
{
        struct bridge_if *bif;

        if ((bif = bridge_get_default()) == NULL)
                return (SNMP_ERR_NOSUCHNAME);

        if (time(NULL) - bif->entry_age > bridge_get_data_maxage() &&
            bridge_update_bif(bif) <= 0) /* It was just deleted. */
                return (SNMP_ERR_NOSUCHNAME);

        switch (op) {
            case SNMP_OP_GET:
                switch (val->var.subs[sub - 1]) {
                    case LEAF_dot1dStpProtocolSpecification:
                        val->v.integer = bif->prot_spec;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpPriority:
                        val->v.integer = bif->priority;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpTimeSinceTopologyChange:
                        if (bridge_get_time_since_tc(bif,
                            &(val->v.uint32)) < 0)
                                return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpTopChanges:
                        val->v.uint32 = bif->top_changes;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpDesignatedRoot:
                        return (string_get(val, bif->design_root,
                            SNMP_BRIDGE_ID_LEN));

                    case LEAF_dot1dStpRootCost:
                        val->v.integer = bif->root_cost;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpRootPort:
                        val->v.integer = bif->root_port;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpMaxAge:
                        val->v.integer = bif->max_age;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpHelloTime:
                        val->v.integer = bif->hello_time;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpHoldTime:
                        val->v.integer = bif->hold_time;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpForwardDelay:
                        val->v.integer = bif->fwd_delay;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpBridgeMaxAge:
                        val->v.integer = bif->bridge_max_age;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpBridgeHelloTime:
                        val->v.integer = bif->bridge_hello_time;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpBridgeForwardDelay:
                        val->v.integer = bif->bridge_fwd_delay;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpVersion:
                        val->v.integer = bif->stp_version;
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpTxHoldCount:
                        val->v.integer = bif->tx_hold_count;
                        return (SNMP_ERR_NOERROR);
                }
                abort();

            case SNMP_OP_GETNEXT:
                abort();

            case SNMP_OP_SET:
                switch (val->var.subs[sub - 1]) {
                    case LEAF_dot1dStpPriority:
                        if (val->v.integer > SNMP_BRIDGE_MAX_PRIORITY ||
                            val->v.integer % 4096 != 0)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->priority;
                        if (bridge_set_priority(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpBridgeMaxAge:
                        if (val->v.integer < SNMP_BRIDGE_MIN_MAGE ||
                            val->v.integer > SNMP_BRIDGE_MAX_MAGE)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->bridge_max_age;
                        if (bridge_set_maxage(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpBridgeHelloTime:
                        if (val->v.integer < SNMP_BRIDGE_MIN_HTIME ||
                            val->v.integer > SNMP_BRIDGE_MAX_HTIME)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->bridge_hello_time;
                        if (bridge_set_hello_time(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpBridgeForwardDelay:
                        if (val->v.integer < SNMP_BRIDGE_MIN_FDELAY ||
                            val->v.integer > SNMP_BRIDGE_MAX_FDELAY)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->bridge_fwd_delay;
                        if (bridge_set_forward_delay(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpVersion:
                        if (val->v.integer != dot1dStpVersion_stpCompatible &&
                            val->v.integer != dot1dStpVersion_rstp)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->stp_version;
                        if (bridge_set_stp_version(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpTxHoldCount:
                        if (val->v.integer < SNMP_BRIDGE_MIN_TXHC ||
                            val->v.integer > SNMP_BRIDGE_MAX_TXHC)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->tx_hold_count;
                        if (bridge_set_tx_hold_count(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_dot1dStpProtocolSpecification:
                    case LEAF_dot1dStpTimeSinceTopologyChange:
                    case LEAF_dot1dStpTopChanges:
                    case LEAF_dot1dStpDesignatedRoot:
                    case LEAF_dot1dStpRootCost:
                    case LEAF_dot1dStpRootPort:
                    case LEAF_dot1dStpMaxAge:
                    case LEAF_dot1dStpHelloTime:
                    case LEAF_dot1dStpHoldTime:
                    case LEAF_dot1dStpForwardDelay:
                        return (SNMP_ERR_NOT_WRITEABLE);
                }
                abort();

            case SNMP_OP_ROLLBACK:
                switch (val->var.subs[sub - 1]) {
                    case LEAF_dot1dStpPriority:
                        bridge_set_priority(bif, ctx->scratch->int1);
                        break;
                    case LEAF_dot1dStpBridgeMaxAge:
                        bridge_set_maxage(bif, ctx->scratch->int1);
                        break;
                    case LEAF_dot1dStpBridgeHelloTime:
                        bridge_set_hello_time(bif, ctx->scratch->int1);
                        break;
                    case LEAF_dot1dStpBridgeForwardDelay:
                        bridge_set_forward_delay(bif, ctx->scratch->int1);
                        break;
                    case LEAF_dot1dStpVersion:
                        bridge_set_stp_version(bif, ctx->scratch->int1);
                        break;
                    case LEAF_dot1dStpTxHoldCount:
                        bridge_set_tx_hold_count(bif, ctx->scratch->int1);
                        break;
                }
                return (SNMP_ERR_NOERROR);

            case SNMP_OP_COMMIT:
                return (SNMP_ERR_NOERROR);
        }

        abort();
}

int
op_dot1d_tp(struct snmp_context *ctx, struct snmp_value *value,
        uint sub, uint iidx __unused, enum snmp_op op)
{
        struct bridge_if *bif;

        if ((bif = bridge_get_default()) == NULL)
                return (SNMP_ERR_NOSUCHNAME);

        if (time(NULL) - bif->entry_age > bridge_get_data_maxage() &&
            bridge_update_bif(bif) <= 0) /* It was just deleted. */
                return (SNMP_ERR_NOSUCHNAME);

        switch (op) {
            case SNMP_OP_GET:
                switch (value->var.subs[sub - 1]) {
                    case LEAF_dot1dTpLearnedEntryDiscards:
                        value->v.uint32 = bif->lrnt_drops;
                        return (SNMP_ERR_NOERROR);
                    case LEAF_dot1dTpAgingTime:
                        value->v.integer = bif->age_time;
                        return (SNMP_ERR_NOERROR);
                }
                abort();

            case SNMP_OP_GETNEXT:
                abort();

            case SNMP_OP_SET:
                switch (value->var.subs[sub - 1]) {
                    case LEAF_dot1dTpLearnedEntryDiscards:
                        return (SNMP_ERR_NOT_WRITEABLE);

                    case LEAF_dot1dTpAgingTime:
                        if (value->v.integer < SNMP_BRIDGE_MIN_AGE_TIME ||
                            value->v.integer > SNMP_BRIDGE_MAX_AGE_TIME)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->age_time;
                        if (bridge_set_aging_time(bif, value->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);
                }
                abort();

            case SNMP_OP_ROLLBACK:
                if (value->var.subs[sub - 1] == LEAF_dot1dTpAgingTime)
                    bridge_set_aging_time(bif, ctx->scratch->int1);
                return (SNMP_ERR_NOERROR);

            case SNMP_OP_COMMIT:
                return (SNMP_ERR_NOERROR);
        }

        abort();
}

/*
 * Private BEGEMOT-BRIDGE-MIB specifics.
 */

/*
 * Get the bridge name from an OID index.
 */
static char *
bridge_name_index_get(const struct asn_oid *oid, uint sub, char *b_name)
{
        uint i;

        if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
                return (NULL);

        for (i = 0; i < oid->subs[sub]; i++)
                b_name[i] = oid->subs[sub + i + 1];
        b_name[i] = '\0';

        return (b_name);
}

static void
bridge_if_index_append(struct asn_oid *oid, uint sub,
        const struct bridge_if *bif)
{
        uint i;

        oid->len = sub + strlen(bif->bif_name) + 1;
        oid->subs[sub] = strlen(bif->bif_name);

        for (i = 1; i <= strlen(bif->bif_name); i++)
                oid->subs[sub + i] = bif->bif_name[i - 1];
}

static struct bridge_if *
bridge_if_index_get(const struct asn_oid *oid, uint sub)
{
        uint i;
        char bif_name[IFNAMSIZ];

        if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
                return (NULL);

        for (i = 0; i < oid->subs[sub]; i++)
                bif_name[i] = oid->subs[sub + i + 1];
        bif_name[i] = '\0';

        return (bridge_if_find_ifname(bif_name));
}

static struct bridge_if *
bridge_if_index_getnext(const struct asn_oid *oid, uint sub)
{
        uint i;
        char bif_name[IFNAMSIZ];
        struct bridge_if *bif;

        if (oid->len - sub == 0)
                return (bridge_first_bif());

        if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
                return (NULL);

        for (i = 0; i < oid->subs[sub]; i++)
                bif_name[i] = oid->subs[sub + i + 1];
        bif_name[i] = '\0';

        if ((bif = bridge_if_find_ifname(bif_name)) == NULL)
                return (NULL);

        return (bridge_next_bif(bif));
}

static int
bridge_set_if_status(struct snmp_context *ctx,
        struct snmp_value *val, uint sub)
{
        struct bridge_if *bif;
        char bif_name[IFNAMSIZ];

        bif = bridge_if_index_get(&val->var, sub);

        switch (val->v.integer) {
            case RowStatus_active:
                if (bif == NULL)
                    return (SNMP_ERR_INCONS_VALUE);

                ctx->scratch->int1 = bif->if_status;

                switch (bif->if_status) {
                    case RowStatus_active:
                        return (SNMP_ERR_NOERROR);
                    case RowStatus_notInService:
                        if (bridge_set_if_up(bif->bif_name, 1) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);
                    default:
                        break;
                }
                return (SNMP_ERR_INCONS_VALUE);

            case RowStatus_notInService:
                if (bif == NULL)
                    return (SNMP_ERR_INCONS_VALUE);

                ctx->scratch->int1 = bif->if_status;

                switch (bif->if_status) {
                    case RowStatus_active:
                        if (bridge_set_if_up(bif->bif_name, 1) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);
                    case RowStatus_notInService:
                        return (SNMP_ERR_NOERROR);
                    default:
                        break;
                }
                return (SNMP_ERR_INCONS_VALUE);

            case RowStatus_notReady:
                return (SNMP_ERR_INCONS_VALUE);

            case RowStatus_createAndGo:
                if (bif != NULL)
                    return (SNMP_ERR_INCONS_VALUE);

                ctx->scratch->int1 = RowStatus_destroy;

                if (bridge_name_index_get(&val->var, sub, bif_name) == NULL)
                    return (SNMP_ERR_BADVALUE);
                if (bridge_if_create(bif_name, 1) < 0)
                    return (SNMP_ERR_GENERR);
                return (SNMP_ERR_NOERROR);

            case RowStatus_createAndWait:
                if (bif != NULL)
                    return (SNMP_ERR_INCONS_VALUE);

                if (bridge_name_index_get(&val->var, sub, bif_name) == NULL)
                    return (SNMP_ERR_BADVALUE);

                ctx->scratch->int1 = RowStatus_destroy;

                if (bridge_if_create(bif_name, 0) < 0)
                    return (SNMP_ERR_GENERR);
                return (SNMP_ERR_NOERROR);

            case RowStatus_destroy:
                if (bif == NULL)
                    return (SNMP_ERR_NOSUCHNAME);

                ctx->scratch->int1 = bif->if_status;
                bif->if_status = RowStatus_destroy;
        }

        return (SNMP_ERR_NOERROR);
}

static int
bridge_rollback_if_status(struct snmp_context *ctx,
        struct snmp_value *val, uint sub)
{
        struct bridge_if *bif;

        if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                return (SNMP_ERR_GENERR);

        switch (ctx->scratch->int1) {
                case RowStatus_destroy:
                        bridge_if_destroy(bif);
                        return (SNMP_ERR_NOERROR);

                case RowStatus_notInService:
                        if (bif->if_status != ctx->scratch->int1)
                                bridge_set_if_up(bif->bif_name, 0);
                        bif->if_status = RowStatus_notInService;
                        return (SNMP_ERR_NOERROR);

                case RowStatus_active:
                        if (bif->if_status != ctx->scratch->int1)
                                bridge_set_if_up(bif->bif_name, 1);
                        bif->if_status = RowStatus_active;
                        return (SNMP_ERR_NOERROR);
        }

        abort();
}

static int
bridge_commit_if_status(struct snmp_value *val, uint sub)
{
        struct bridge_if *bif;

        if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                return (SNMP_ERR_GENERR);

        if (bif->if_status == RowStatus_destroy &&
            bridge_if_destroy(bif) < 0)
                return (SNMP_ERR_COMMIT_FAILED);

        return (SNMP_ERR_NOERROR);
}

int
op_begemot_base_bridge(struct snmp_context *ctx, struct snmp_value *val,
        uint sub, uint iidx __unused, enum snmp_op op)
{
        struct bridge_if *bif;

        if (time(NULL) - bridge_list_age > bridge_get_data_maxage())
                bridge_update_all_ifs();

        switch (op) {
            case SNMP_OP_GET:
                if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);
                goto get;

            case SNMP_OP_GETNEXT:
                if ((bif = bridge_if_index_getnext(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);
                bridge_if_index_append(&val->var, sub, bif);
                goto get;

            case SNMP_OP_SET:
                switch (val->var.subs[sub - 1]) {
                    case LEAF_begemotBridgeBaseStatus:
                        return (bridge_set_if_status(ctx, val, sub));
                    case LEAF_begemotBridgeBaseName:
                    case LEAF_begemotBridgeBaseAddress:
                    case LEAF_begemotBridgeBaseNumPorts:
                    case LEAF_begemotBridgeBaseType:
                        return (SNMP_ERR_NOT_WRITEABLE);
                }
                abort();

            case SNMP_OP_ROLLBACK:
                return (bridge_rollback_if_status(ctx, val, sub));

            case SNMP_OP_COMMIT:
                return (bridge_commit_if_status(val, sub));
        }
        abort();

get:
        switch (val->var.subs[sub - 1]) {
            case LEAF_begemotBridgeBaseName:
                return (string_get(val, bif->bif_name, -1));

            case LEAF_begemotBridgeBaseAddress:
                return (string_get(val, bif->br_addr.octet, ETHER_ADDR_LEN));

            case LEAF_begemotBridgeBaseNumPorts:
                val->v.integer = bif->num_ports;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeBaseType:
                val->v.integer = bif->br_type;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeBaseStatus:
                val->v.integer = bif->if_status;
                return (SNMP_ERR_NOERROR);
        }

        abort();
}

int
op_begemot_stp(struct snmp_context *ctx, struct snmp_value *val,
        uint sub, uint iidx __unused, enum snmp_op op)
{
        struct bridge_if *bif;

        if (time(NULL) - bridge_list_age > bridge_get_data_maxage())
                bridge_update_all_ifs();

        switch (op) {
            case SNMP_OP_GET:
                if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);
                goto get;

            case SNMP_OP_GETNEXT:
                if ((bif = bridge_if_index_getnext(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);
                bridge_if_index_append(&val->var, sub, bif);
                goto get;

            case SNMP_OP_SET:
                if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);

                switch (val->var.subs[sub - 1]) {
                    case LEAF_begemotBridgeStpPriority:
                        if (val->v.integer > SNMP_BRIDGE_MAX_PRIORITY ||
                            val->v.integer % 4096 != 0)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->priority;
                        if (bridge_set_priority(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_begemotBridgeStpBridgeMaxAge:
                        if (val->v.integer < SNMP_BRIDGE_MIN_MAGE ||
                            val->v.integer > SNMP_BRIDGE_MAX_MAGE)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->bridge_max_age;
                        if (bridge_set_maxage(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_begemotBridgeStpBridgeHelloTime:
                        if (val->v.integer < SNMP_BRIDGE_MIN_HTIME ||
                            val->v.integer > SNMP_BRIDGE_MAX_HTIME)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->bridge_hello_time;
                        if (bridge_set_hello_time(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_begemotBridgeStpBridgeForwardDelay:
                        if (val->v.integer < SNMP_BRIDGE_MIN_FDELAY ||
                            val->v.integer > SNMP_BRIDGE_MAX_FDELAY)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->bridge_fwd_delay;
                        if (bridge_set_forward_delay(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_begemotBridgeStpVersion:
                        if (val->v.integer !=
                            begemotBridgeStpVersion_stpCompatible &&
                            val->v.integer != begemotBridgeStpVersion_rstp)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->stp_version;
                        if (bridge_set_stp_version(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_begemotBridgeStpTxHoldCount:
                        if (val->v.integer < SNMP_BRIDGE_MIN_TXHC ||
                            val->v.integer > SNMP_BRIDGE_MAX_TXHC)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->tx_hold_count;
                        if (bridge_set_tx_hold_count(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_begemotBridgeStpProtocolSpecification:
                    case LEAF_begemotBridgeStpTimeSinceTopologyChange:
                    case LEAF_begemotBridgeStpTopChanges:
                    case LEAF_begemotBridgeStpDesignatedRoot:
                    case LEAF_begemotBridgeStpRootCost:
                    case LEAF_begemotBridgeStpRootPort:
                    case LEAF_begemotBridgeStpMaxAge:
                    case LEAF_begemotBridgeStpHelloTime:
                    case LEAF_begemotBridgeStpHoldTime:
                    case LEAF_begemotBridgeStpForwardDelay:
                        return (SNMP_ERR_NOT_WRITEABLE);
                }
                abort();

            case SNMP_OP_ROLLBACK:
                if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);

                switch (val->var.subs[sub - 1]) {
                    case LEAF_begemotBridgeStpPriority:
                        bridge_set_priority(bif, ctx->scratch->int1);
                        break;

                    case LEAF_begemotBridgeStpBridgeMaxAge:
                        bridge_set_maxage(bif, ctx->scratch->int1);
                        break;

                    case LEAF_begemotBridgeStpBridgeHelloTime:
                        bridge_set_hello_time(bif, ctx->scratch->int1);
                        break;

                    case LEAF_begemotBridgeStpBridgeForwardDelay:
                        bridge_set_forward_delay(bif, ctx->scratch->int1);
                        break;

                    case LEAF_begemotBridgeStpVersion:
                        bridge_set_stp_version(bif, ctx->scratch->int1);
                        break;

                    case LEAF_begemotBridgeStpTxHoldCount:
                        bridge_set_tx_hold_count(bif, ctx->scratch->int1);
                        break;
                }
                return (SNMP_ERR_NOERROR);

            case SNMP_OP_COMMIT:
                return (SNMP_ERR_NOERROR);
        }
        abort();

get:
        switch (val->var.subs[sub - 1]) {
            case LEAF_begemotBridgeStpProtocolSpecification:
                val->v.integer = bif->prot_spec;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpPriority:
                val->v.integer = bif->priority;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpTimeSinceTopologyChange:
                if (bridge_get_time_since_tc(bif, &(val->v.uint32)) < 0)
                    return (SNMP_ERR_GENERR);
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpTopChanges:
                val->v.uint32 = bif->top_changes;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpDesignatedRoot:
                return (string_get(val, bif->design_root, SNMP_BRIDGE_ID_LEN));

            case LEAF_begemotBridgeStpRootCost:
                val->v.integer = bif->root_cost;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpRootPort:
                val->v.integer = bif->root_port;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpMaxAge:
                val->v.integer = bif->max_age;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpHelloTime:
                val->v.integer = bif->hello_time;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpHoldTime:
                val->v.integer = bif->hold_time;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpForwardDelay:
                val->v.integer = bif->fwd_delay;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpBridgeMaxAge:
                val->v.integer = bif->bridge_max_age;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpBridgeHelloTime:
                val->v.integer = bif->bridge_hello_time;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpBridgeForwardDelay:
                val->v.integer = bif->bridge_fwd_delay;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpVersion:
                val->v.integer = bif->stp_version;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeStpTxHoldCount:
                val->v.integer = bif->tx_hold_count;
                return (SNMP_ERR_NOERROR);
        }

        abort();
}

int
op_begemot_tp(struct snmp_context *ctx, struct snmp_value *val,
        uint sub, uint iidx __unused, enum snmp_op op)
{
        struct bridge_if *bif;

        if (time(NULL) - bridge_list_age > bridge_get_data_maxage())
                bridge_update_all_ifs();

        switch (op) {
            case SNMP_OP_GET:
                if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);
                goto get;

            case SNMP_OP_GETNEXT:
                if ((bif = bridge_if_index_getnext(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);
                bridge_if_index_append(&val->var, sub, bif);
                goto get;

            case SNMP_OP_SET:
                if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                    return (SNMP_ERR_NOSUCHNAME);

                switch (val->var.subs[sub - 1]) {
                    case LEAF_begemotBridgeTpAgingTime:
                        if (val->v.integer < SNMP_BRIDGE_MIN_AGE_TIME ||
                            val->v.integer > SNMP_BRIDGE_MAX_AGE_TIME)
                            return (SNMP_ERR_WRONG_VALUE);

                        ctx->scratch->int1 = bif->age_time;
                        if (bridge_set_aging_time(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_begemotBridgeTpMaxAddresses:
                        ctx->scratch->int1 = bif->max_addrs;
                        if (bridge_set_max_cache(bif, val->v.integer) < 0)
                            return (SNMP_ERR_GENERR);
                        return (SNMP_ERR_NOERROR);

                    case LEAF_begemotBridgeTpLearnedEntryDiscards:
                        return (SNMP_ERR_NOT_WRITEABLE);
                }
                abort();

            case SNMP_OP_ROLLBACK:
                if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
                    return (SNMP_ERR_GENERR);

                switch (val->var.subs[sub - 1]) {
                    case LEAF_begemotBridgeTpAgingTime:
                        bridge_set_aging_time(bif, ctx->scratch->int1);
                        break;

                    case LEAF_begemotBridgeTpMaxAddresses:
                        bridge_set_max_cache(bif, ctx->scratch->int1);
                        break;
                }
                return (SNMP_ERR_NOERROR);

            case SNMP_OP_COMMIT:
                return (SNMP_ERR_NOERROR);
        }
        abort();

get:
        switch (val->var.subs[sub - 1]) {
            case LEAF_begemotBridgeTpLearnedEntryDiscards:
                val->v.uint32 = bif->lrnt_drops;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeTpAgingTime:
                val->v.integer = bif->age_time;
                return (SNMP_ERR_NOERROR);

            case LEAF_begemotBridgeTpMaxAddresses:
                val->v.integer = bif->max_addrs;
                return (SNMP_ERR_NOERROR);
        }

        abort();
}