/*
 * tunnel.c --
 * 
 *      An implementation of the TUNNEL-MIB for the UCD-SNMP 4.2
 *      agent running on Linux 2.2.x.
 *      
 * Copyright (c) 2000 Frank Strauss <strauss@ibr.cs.tu-bs.de>
 *
 *                          All Rights Reserved
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appears in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of the author and CMU and
 * The Regents of the University of California not be used in advertising
 * or publicity pertaining to distribution of the software without
 * specific written permission.
 * 
 * THE AUTHOR AND CMU AND THE REGENTS OF THE UNIVERSITY OF CALIFORNIA
 * DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL
 * THE AUTHOR OR CMU OR THE REGENTS OF THE UNIVERSITY OF CALIFORNIA BE
 * LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM THE LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 */

/*
 * NOTE: This TUNNEL-MIB implementation
 *
 *       (a) DOES NOT implement write access on the tunnelConfigTable,
 *           i.e. no new tunnels can be created and no existing tunnels
 *           can be removed through SET operations.
 *
 *       (b) DOES implement write access on some tunnelIfTable objects
 *           to allow reconfiguring established tunnels. This violates
 *           RFC 2667! However, the author thinks it makes sense. ;-)
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <linux/if.h>
#include <linux/ip.h>
#include <linux/sockios.h>
#include <linux/if_tunnel.h>
#include <linux/if_arp.h>

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#include "util_funcs.h"
#include "tunnel.h"

#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif



/*
 * This is used, because the TUNNEL-MIB augments ifTable. 
 */
extern unsigned char *var_ifEntry(struct variable *,
                                  oid *, size_t *,
                                  int, size_t *, WriteMethod **);


/*
 * tunnel_variables_oid:
 *   this is the top level oid that we want to register under.  This
 *   is essentially a prefix, with the suffix appearing in the
 *   variable below.
 */
oid             tunnel_variables_oid[] =
    { 1, 3, 6, 1, 2, 1, 10, 131, 1, 1 };
const int       tunnel_len = 10;

oid             tunnel_ifEntry_oid[] =
    { 1, 3, 6, 1, 2, 1, 10, 131, 1, 1, 1, 1 };
const int       tunnel_ifEntry_len = 12;

oid             tunnel_configEntry_oid[] =
    { 1, 3, 6, 1, 2, 1, 10, 131, 1, 1, 2, 1 };
const int       tunnel_configEntry_len = 12;



struct tunnel {
    oid             ifindex;
    int             id;
    char           *ifname;
    int             active;
    unsigned long   local;
    unsigned long   remote;
    int             encaps;
    int             hoplimit;
    int             security;
    int             tos;
    oid             config_name[MAX_OID_LEN];
    size_t          config_length;
    struct tunnel  *next;
};



/*
 * variable4 tunnel_variables:
 *   this variable defines function callbacks and type return information 
 *   for the tunnel mib section 
 */

struct variable4 tunnel_variables[] = {
    /*
     * magic number        , variable type , ro/rw , callback fn  , L, oidsuffix 
     */
#define   LOCALADDRESS          1
    {LOCALADDRESS, ASN_IPADDRESS, RWRITE, var_tunnelIfEntry, 3, {1, 1, 1}},
#define   REMOTEADDRESS         2
    {REMOTEADDRESS, ASN_IPADDRESS, RWRITE, var_tunnelIfEntry, 3,
     {1, 1, 2}},
#define   ENCAPSMETHOD          3
    {ENCAPSMETHOD, ASN_INTEGER, RONLY, var_tunnelIfEntry, 3, {1, 1, 3}},
#define   HOPLIMIT              4
    {HOPLIMIT, ASN_INTEGER, RWRITE, var_tunnelIfEntry, 3, {1, 1, 4}},
#define   SECURITY              5
    {SECURITY, ASN_INTEGER, RONLY, var_tunnelIfEntry, 3, {1, 1, 5}},
#define   TOS                   6
    {TOS, ASN_INTEGER, RWRITE, var_tunnelIfEntry, 3, {1, 1, 6}},

#define   IFINDEX               7
    {IFINDEX, ASN_INTEGER, RONLY, var_tunnelConfigEntry, 3, {2, 1, 5}},
#define   ROWSTATUS             8
    {ROWSTATUS, ASN_INTEGER, RWRITE, var_tunnelConfigEntry, 3, {2, 1, 6}},
};



extern int      register_sysORTable(oid *, size_t, const char *);
extern int      unregister_sysORTable(oid *, size_t);

static oid      sysORTable_reg[] = { 1, 3, 6, 1, 2, 1, 10, 131 };
static size_t   sysORTable_reglen = 8;

static struct tunnel *tunnels;



void
deinit_tunnel(void)
{
    unregister_sysORTable(sysORTable_reg, sysORTable_reglen);
}



int
term_tunnel(int majorID, int minorID, void *serverarg, void *clientarg)
{
    deinit_tunnel();
    return 0;
}



void
init_tunnel(void)
{
    register_sysORTable(sysORTable_reg, sysORTable_reglen,
                        "RFC 2667 TUNNEL-MIB implementation for "
                        "Linux 2.2.x kernels.");

    /*
     * register ourselves with the agent to handle our mib tree 
     */
    REGISTER_MIB("tunnel", tunnel_variables, variable4,
                 tunnel_variables_oid);

    snmp_register_callback(SNMP_CALLBACK_LIBRARY,
                           SNMP_CALLBACK_SHUTDOWN, term_tunnel, NULL);

    tunnels = NULL;
}



static int
getType(int index)
{
    oid             name[MAX_OID_LEN] = { 1, 3, 6, 1, 2, 1, 2, 2, 1, 3 };
    size_t          length = 10;
    struct variable ifType_variable =
        { 3, ASN_INTEGER, RONLY, var_ifEntry, 10,
        {1, 3, 6, 1, 2, 1, 2, 2, 1, 3}
    };
    unsigned char  *p;
    size_t          var_len;
    WriteMethod    *write_method;

    name[length] = index;
    length++;

    p = var_ifEntry(&ifType_variable,
                    name, &length,
                    1 /* exact */ , &var_len, &write_method);
    if (!p)
        return 0;

    return *(int *) p;
}



static char    *
getName(int index)
{
    oid             name[MAX_OID_LEN] = { 1, 3, 6, 1, 2, 1, 2, 2, 1, 2 };
    size_t          length = 10;
    struct variable ifName_variable =
        { 2, ASN_INTEGER, RONLY, var_ifEntry, 10,
        {1, 3, 6, 1, 2, 1, 2, 2, 1, 2}
    };
    unsigned char  *p;
    size_t          var_len;
    WriteMethod    *write_method;

    name[length] = index;
    length++;

    p = var_ifEntry(&ifName_variable,
                    name, &length,
                    1 /* exact */ , &var_len, &write_method);
    if (!p)
        return NULL;

    return p;
}



static struct ip_tunnel_parm *
getTunnelParm(char *ifname)
{
    struct ifreq    ifrq;
    int             fd;
    static struct ip_tunnel_parm parm;

    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        return NULL;
    }

    memset(&parm, 0, sizeof(struct ip_tunnel_parm));
    strcpy(ifrq.ifr_name, ifname);
    ifrq.ifr_ifru.ifru_data = (void *) &parm;
    if (ioctl(fd, SIOCGETTUNNEL, &ifrq) < 0) {
        /*
         * try again with the last char of the device name cut off.
         * it might have been a zero digit appended by the agent.
         */
        ifrq.ifr_name[strlen(ifrq.ifr_name) - 1] = 0;
        if (ioctl(fd, SIOCGETTUNNEL, &ifrq) < 0) {
            close(fd);
            return NULL;
        }
        ifname[strlen(ifname) - 1] = 0;
    }

    close(fd);

    return &parm;
}



int
setTunnelParm(char *ifname, struct ip_tunnel_parm *parm)
{
    struct ifreq    ifrq;
    int             fd;
    int             err;

    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        return -1;
    }

    strcpy(ifrq.ifr_name, ifname);
    ifrq.ifr_ifru.ifru_data = (void *) parm;
    err = ioctl(fd, SIOCCHGTUNNEL, &ifrq);
    close(fd);

    return err;
}



/*
 * update a struct tunnel. its index and ifname elements have to be set.
 */
static struct tunnel *
updateTunnel(struct tunnel *tunnel)
{
    struct ip_tunnel_parm *parm;
    int             fd;
    struct ifreq    ifrq;

    /*
     * NOTE: getTunnelParm() may adjust the passed ifname. 
     */
    parm = getTunnelParm(tunnel->ifname);
    if (!parm) {
	DEBUGMSGTL(("tunnel",
		    "updateTunnel(): getTunnelParm(\"%s\") returned NULL\n",
		    tunnel->ifname));
        tunnel->active = 0;
        return NULL;
    }

    tunnel->active = 1;

    tunnel->local = parm->iph.saddr;
    tunnel->remote = parm->iph.daddr;

    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        DEBUGMSGTL(("snmpd", "socket open failure in updateTunnels()\n"));
        return NULL;
    } else {
        /*
         * NOTE: this ioctl does not guarantee 6 bytes of a physaddr.
         * In particular, a 'sit0' interface only appears to get back
         * 4 bytes of sa_data. We don't use sa_data here, or we'd
         * need to memset it to 0 before the ioct.
         */
        strcpy(ifrq.ifr_name, tunnel->ifname);
        if (ioctl(fd, SIOCGIFHWADDR, &ifrq) == 0)
            switch (ifrq.ifr_hwaddr.sa_family) {
            case ARPHRD_TUNNEL:
                tunnel->encaps = 2;
                break;;         /* direct */
            case ARPHRD_TUNNEL6:
                tunnel->encaps = 2;
                break;;         /* direct */
            case ARPHRD_IPGRE:
                tunnel->encaps = 3;
                break;;         /* gre */
            case ARPHRD_SIT:
                tunnel->encaps = 2;
                break;;         /* direct */
            default:
                tunnel->encaps = 1;     /* other */
            }
        close(fd);
    }

    tunnel->hoplimit = parm->iph.ttl;
    tunnel->security = 1;
    tunnel->tos = (parm->iph.tos & 1) ? -1 : parm->iph.tos;
    /*
     * XXX: adjust tos mapping (kernel <-> TUNNEL-MIB::tunnelIfTOS) 
     */

    return tunnel;
}



static void
updateTunnels(void)
{
    static int      max_index = 1;
    static struct tunnel *last_tunnel = NULL;
    struct tunnel  *tunnel;
    char           *ifname;
    int             type;

    /*
     * uptime the tunnels we have so far 
     */
    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
        DEBUGMSG(("tunnel",
                  "updateTunnels(): updating %s (index=%d)\n",
                  tunnel->ifname, tunnel->ifindex));
        updateTunnel(tunnel);
    }

    /*
     * look for new tunnels 
     */
    for (; max_index < 256; max_index++) {
        DEBUGMSG(("tunnel",
                  "updateTunnels(): looking for new index=%d\n",
                  max_index));
        type = getType(max_index);
        if (type == 131) {
            tunnel = (struct tunnel *) malloc(sizeof(struct tunnel));
            if (!tunnel)
                continue;

            tunnel->ifindex = max_index;
            tunnel->id = 1;

            ifname = getName(max_index);
            if (!ifname) {
                free(tunnel);
                continue;
            }

            tunnel->ifname = strdup(ifname);
            if (!tunnel->ifname) {
                free(tunnel);
                continue;
            }

            if (!updateTunnel(tunnel)) {
                free(tunnel);
                continue;
            }

            if (last_tunnel)
                last_tunnel->next = tunnel;
            if (!tunnels)
                tunnels = last_tunnel = tunnel;
            tunnel->next = NULL;
            last_tunnel = tunnel;

            DEBUGMSG(("tunnel",
                      "updateTunnels(): added %s (index=%d state=%d)\n",
                      tunnel->ifname, tunnel->ifindex, tunnel->active));
        }
        if (type == 0)
            break;
    }
}



static struct tunnel *
getTunnelByIfIndex(int index)
{
    struct tunnel  *tunnel;

    DEBUGMSG(("tunnel", "getTunnelByIfIndex(%d): ", index));

    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
        if (tunnel->ifindex == index) {
            if (!tunnel->active)
                break;
            DEBUGMSG(("tunnel",
                      "%s (index=%d)\n", tunnel->ifname, tunnel->ifindex));
            return tunnel;
        }
    }
    DEBUGMSG(("tunnel", "NONE\n"));
    return NULL;
}



static struct tunnel *
getNextTunnelByIfIndex(int index)
{
    struct tunnel  *tunnel;

    DEBUGMSG(("tunnel", "getNextTunnelByIfIndex(%d): ", index));

    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
        if (tunnel->ifindex > index) {
            if (!tunnel->active)
                continue;
            DEBUGMSG(("tunnel",
                      "%s (index=%d)\n", tunnel->ifname, tunnel->ifindex));
            return tunnel;
        }
    }
    DEBUGMSG(("tunnel", "NONE\n"));
    return NULL;
}



static void
fillConfigOid(oid * name, struct tunnel *tunnel)
{
    name[0] = ((unsigned char *) &tunnel->local)[0];
    name[1] = ((unsigned char *) &tunnel->local)[1];
    name[2] = ((unsigned char *) &tunnel->local)[2];
    name[3] = ((unsigned char *) &tunnel->local)[3];
    name[4] = ((unsigned char *) &tunnel->remote)[0];
    name[5] = ((unsigned char *) &tunnel->remote)[1];
    name[6] = ((unsigned char *) &tunnel->remote)[2];
    name[7] = ((unsigned char *) &tunnel->remote)[3];
    name[8] = tunnel->encaps;
    name[9] = tunnel->id;
    DEBUGMSGOID(("tunnel", name, 10));
}



static struct tunnel *
getTunnelByConfigOid(oid * name, size_t * length)
{
    struct tunnel  *tunnel;
    oid             tname[4 + 4 + 1 + 1];

    DEBUGMSG(("tunnel", "getTunnelByConfigOid(): "));

    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
        fillConfigOid(tname, tunnel);
        if (!snmp_oid_compare(tname, 4 + 4 + 1 + 1,
                              &name[tunnel_len + 3],
                              (*length) - tunnel_len - 3)) {
            if (!tunnel->active)
                break;
            DEBUGMSG(("tunnel",
                      "%s (index=%d)\n", tunnel->ifname, tunnel->ifindex));
            return tunnel;
        }
    }
    DEBUGMSG(("tunnel", "NONE\n"));
    return NULL;
}



static struct tunnel *
getNextTunnelByConfigOid(oid * name, size_t * length)
{
    struct tunnel  *tunnel, *last_tunnel;
    oid             tname[10], last_tname[10];

    DEBUGMSG(("tunnel", "getNextTunnelByConfigOid("));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel", "): "));

    last_tunnel = NULL;
    for (tunnel = tunnels; tunnel; tunnel = tunnel->next) {
        if (!tunnel->active)
            continue;
        fillConfigOid(tname, tunnel);
        if (snmp_oid_compare(tname, 10,
                             &name[tunnel_len + 3],
                             (*length) - tunnel_len - 3) > 0) {
            if (!last_tunnel) {
                last_tunnel = tunnel;
                memcpy((char *) last_tname, (char *) tname,
                       10 * sizeof(oid));
            } else {
                if (snmp_oid_compare(tname, 10, last_tname, 10) < 0) {
                    last_tunnel = tunnel;
                    memcpy((char *) last_tname, (char *) tname,
                           10 * sizeof(oid));
                }
            }
        }
    }

    if (last_tunnel) {
        DEBUGMSG(("tunnel",
                  "%s (index=%d)\n",
                  last_tunnel->ifname, last_tunnel->ifindex));
    } else {
        DEBUGMSG(("tunnel", "NONE\n"));
    }

    return last_tunnel;
}



static int
writeLocalAddress(int action, unsigned char *var_val,
                  unsigned char var_val_type, size_t var_val_len,
                  unsigned char *statP, oid * name, size_t name_len)
{
    static struct tunnel *tunnel;
    struct ip_tunnel_parm *parm;

    switch (action) {
    case RESERVE1:
        if (var_val_type != ASN_IPADDRESS) {
            return SNMP_ERR_WRONGTYPE;
        }
        if (var_val_len != 4) {
            return SNMP_ERR_WRONGLENGTH;
        }
    case RESERVE2:
        tunnel = getTunnelByIfIndex((int) name[name_len - 1]);
        if (!tunnel) {
            return SNMP_ERR_NOSUCHNAME;
        }
    case FREE:
        break;
    case ACTION:
        break;
    case UNDO:
        break;
    case COMMIT:
        if (!tunnel) {
            return SNMP_ERR_NOSUCHNAME;
        }
        parm = getTunnelParm(tunnel->ifname);
        if (!parm) {
            return SNMP_ERR_NOSUCHNAME;
        }
        parm->iph.saddr = *(unsigned long *) var_val;
        setTunnelParm(tunnel->ifname, parm);
        break;
    }

    return SNMP_ERR_NOERROR;
}



static int
writeRemoteAddress(int action, unsigned char *var_val,
                   unsigned char var_val_type, size_t var_val_len,
                   unsigned char *statP, oid * name, size_t name_len)
{
    static struct tunnel *tunnel;
    struct ip_tunnel_parm *parm;

    switch (action) {
    case RESERVE1:
        if (var_val_type != ASN_IPADDRESS) {
            return SNMP_ERR_WRONGTYPE;
        }
        if (var_val_len != 4) {
            return SNMP_ERR_WRONGLENGTH;
        }
    case RESERVE2:
        tunnel = getTunnelByIfIndex((int) name[name_len - 1]);
        if (!tunnel) {
            return SNMP_ERR_NOSUCHNAME;
        }
    case FREE:
        break;
    case ACTION:
        break;
    case UNDO:
        break;
    case COMMIT:
        if (!tunnel) {
            return SNMP_ERR_NOSUCHNAME;
        }
        parm = getTunnelParm(tunnel->ifname);
        if (!parm) {
            return SNMP_ERR_NOSUCHNAME;
        }
        parm->iph.daddr = *(unsigned long *) var_val;
        setTunnelParm(tunnel->ifname, parm);
        break;
    }

    return SNMP_ERR_NOERROR;
}



static int
writeHopLimit(int action, unsigned char *var_val,
              unsigned char var_val_type, size_t var_val_len,
              unsigned char *statP, oid * name, size_t name_len)
{
    static struct tunnel *tunnel;
    struct ip_tunnel_parm *parm;

    switch (action) {
    case RESERVE1:
        if (var_val_type != ASN_INTEGER) {
            return SNMP_ERR_WRONGTYPE;
        }
        if (var_val_len > sizeof(long)) {
            return SNMP_ERR_WRONGLENGTH;
        }
    case RESERVE2:
        tunnel = getTunnelByIfIndex((int) name[name_len - 1]);
        if (!tunnel) {
            return SNMP_ERR_NOSUCHNAME;
        }
    case FREE:
        break;
    case ACTION:
        break;
    case UNDO:
        break;
    case COMMIT:
        if (!tunnel) {
            return SNMP_ERR_NOSUCHNAME;
        }
        parm = getTunnelParm(tunnel->ifname);
        if (!parm) {
            return SNMP_ERR_NOSUCHNAME;
        }
        parm->iph.ttl = *(long *) var_val;
        setTunnelParm(tunnel->ifname, parm);
        break;
    }

    return SNMP_ERR_NOERROR;
}



static int
writeTOS(int action, unsigned char *var_val,
         unsigned char var_val_type, size_t var_val_len,
         unsigned char *statP, oid * name, size_t name_len)
{
    static struct tunnel *tunnel;
    struct ip_tunnel_parm *parm;

    switch (action) {
    case RESERVE1:
        if (var_val_type != ASN_INTEGER) {
            return SNMP_ERR_WRONGTYPE;
        }
        if (var_val_len > sizeof(long)) {
            return SNMP_ERR_WRONGLENGTH;
        }
    case RESERVE2:
        tunnel = getTunnelByIfIndex((int) name[name_len - 1]);
        if (!tunnel) {
            return SNMP_ERR_NOSUCHNAME;
        }
    case FREE:
        break;
    case ACTION:
        break;
    case UNDO:
        break;
    case COMMIT:
        if (!tunnel) {
            return SNMP_ERR_NOSUCHNAME;
        }
        parm = getTunnelParm(tunnel->ifname);
        if (!parm) {
            return SNMP_ERR_NOSUCHNAME;
        }
        /*
         * this does not cover all meaningful values: 
         */
        parm->iph.tos = (*(long *) var_val == -1) ? 1 : *(long *) var_val;
        setTunnelParm(tunnel->ifname, parm);
        break;
    }

    return SNMP_ERR_NOERROR;
}



unsigned char  *
var_tunnelIfEntry(struct variable *vp,
                  oid * name, size_t * length,
                  int exact, size_t * var_len, WriteMethod ** write_method)
{
    static unsigned long ret_addr;
    static long     ret_int;
    struct tunnel  *tunnel;

    DEBUGMSGTL(("tunnel", "var_tunnelIfEntry: "));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel", " %d\n", exact));

    updateTunnels();

    if (exact) {
        if (*length != tunnel_len + 3 + 1) {
            return NULL;
        }
        tunnel = getTunnelByIfIndex((int) name[*length - 1]);
    } else {
        if ((*length) < tunnel_len) {
            memcpy((char *) name, (char *) tunnel_variables_oid,
                   tunnel_len * sizeof(oid));
        }
        if ((*length) < tunnel_len + 1) {
            name[tunnel_len] = 1;
        }
        if ((*length) < tunnel_len + 2) {
            name[tunnel_len + 1] = 1;
        }
        if ((*length) < tunnel_len + 3) {
            name[tunnel_len + 2] = 1;
        }
        if ((*length) < tunnel_len + 4) {
            name[tunnel_len + 3] = 0;
        }
        *length = tunnel_len + 4;

        tunnel = getNextTunnelByIfIndex(name[*length - 1]);
        if (!tunnel) {
            /*
             * end of column, continue with first row of next column 
             */
            tunnel = tunnels;
            name[tunnel_len + 2]++;
            if (name[tunnel_len + 2] > 6) {
                /*
                 * there is no next column 
                 */
                return NULL;
            }
            if (!tunnel) {
                /*
                 * there is no (next) row 
                 */
                return NULL;
            }
        }
    }

    if (!tunnel) {
        return NULL;
    }

    name[*length - 1] = tunnel->ifindex;

    DEBUGMSGTL(("tunnel", "var_tunnelIfEntry: using"));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel", "\n"));

    switch (name[tunnel_len + 2]) {
    case 1:                    /* tunnelIfLocalAddress */
        ret_addr = tunnel->local;
        *var_len = 4;
        vp->type = ASN_IPADDRESS;
        *write_method = writeLocalAddress;
        return (u_char *) & ret_addr;
    case 2:                    /* tunnelIfRemoteAddress */
        ret_addr = tunnel->remote;
        *var_len = 4;
        vp->type = ASN_IPADDRESS;
        *write_method = writeRemoteAddress;
        return (u_char *) & ret_addr;
    case 3:                    /* tunnelIfEncapsMethod */
        ret_int = tunnel->encaps;
        *var_len = sizeof(ret_int);
        vp->type = ASN_INTEGER;
        return (u_char *) & ret_int;
    case 4:                    /* tunnelIfHopLimit */
        ret_int = tunnel->hoplimit;
        *var_len = sizeof(ret_int);
        vp->type = ASN_INTEGER;
        *write_method = writeHopLimit;
        return (u_char *) & ret_int;
    case 5:                    /* tunnelIfSecurity */
        ret_int = tunnel->security;
        *var_len = sizeof(ret_int);
        vp->type = ASN_INTEGER;
        return (u_char *) & ret_int;
    case 6:                    /* tunnelIfTOS */
        ret_int = tunnel->tos;
        *var_len = sizeof(ret_int);
        vp->type = ASN_INTEGER;
        *write_method = writeTOS;
        return (u_char *) & ret_int;
    default:
        return 0;
    }

    return NULL;
}



unsigned char  *
var_tunnelConfigEntry(struct variable *vp,
                      oid * name, size_t * length,
                      int exact, size_t * var_len,
                      WriteMethod ** write_method)
{
    static long     ret_int;
    struct tunnel  *tunnel;
    int             i;

    DEBUGMSGTL(("tunnel", "var_tunnelConfigEntry: "));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel", " %d\n", exact));

    updateTunnels();

    if (exact) {
        if (*length != tunnel_len + 3 + 4 + 4 + 1 + 1) {
            return NULL;
        }
        tunnel = getTunnelByConfigOid(name, length);
    } else {
        if (snmp_oid_compare(name, *length,
                             tunnel_configEntry_oid,
                             tunnel_configEntry_len) < 0) {
            *length = 0;
        }
        if ((*length) < tunnel_len) {
            memcpy((char *) name, (char *) tunnel_variables_oid,
                   tunnel_len * sizeof(oid));
        }
        if ((*length) < tunnel_len + 1) {
            name[tunnel_len] = 2;
        }
        if ((*length) < tunnel_len + 2) {
            name[tunnel_len + 1] = 1;
        }
        if ((*length) < tunnel_len + 3) {
            name[tunnel_len + 2] = 5;
        }
        for (i = MAX(*length, tunnel_len + 3);
             i < tunnel_len + 3 + 4 + 4 + 1 + 1; i++) {
            name[i] = 0;
        }
        *length = tunnel_len + 3 + 4 + 4 + 1 + 1;
        tunnel = getNextTunnelByConfigOid(name, length);
        if (!tunnel) {
            /*
             * end of column, continue with first row of next column 
             */
            tunnel = tunnels;
            name[tunnel_len + 2]++;
            if (name[tunnel_len + 2] > 6) {
                /*
                 * there is no next column 
                 */
                return NULL;
            }
            if (!tunnel) {
                /*
                 * there is no (next) row 
                 */
                return NULL;
            }
        }
    }

    if (!tunnel) {
        return NULL;
    }

    fillConfigOid(&name[tunnel_len + 3], tunnel);

    DEBUGMSGTL(("tunnel", "var_tunnelConfigEntry: using "));
    DEBUGMSGOID(("tunnel", name, *length));
    DEBUGMSG(("tunnel", "\n"));

    switch (name[tunnel_len + 2]) {
    case 5:                    /* tunnelConfigIfIndex */
        ret_int = tunnel->ifindex;
        *var_len = sizeof(ret_int);
        vp->type = ASN_INTEGER;
        return (u_char *) & ret_int;
    case 6:                    /* tunnelConfigStatus */
        ret_int = 1;            /* active */
        *var_len = sizeof(ret_int);
        vp->type = ASN_INTEGER;
        return (u_char *) & ret_int;
    default:
        return 0;
    }

    return NULL;
}
