root/usr.sbin/altq/libaltq/parser.c
/*      $NetBSD: parser.c,v 1.13 2024/01/15 19:44:07 andvar Exp $       */
/*      $KAME: parser.c,v 1.16 2002/02/20 10:40:39 kjc Exp $    */
/*
 * Copyright (C) 1999-2002
 *      Sony Computer Science Laboratories, Inc.  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 SONY CSL 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 SONY CSL 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.
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <syslog.h>
#include <netdb.h>
#include <err.h>

#include <altq/altq.h>
#include <altq/altq_cdnr.h>
#include <altq/altq_red.h>
#include <altq/altq_rio.h>
#include "altq_qop.h"
#include "qop_cdnr.h"

static int is_qdisc_name(const char *);
static int qdisc_interface_parser(const char *, const char *, int, char **);
static int qdisc_class_parser(const char *, const char *, const char *,
                              const char *, int, char **);
static int next_word(char **, char *);

static int get_ifname(char **, char **);
static int get_addr(char **, struct in_addr *, struct in_addr *);
static int get_port(const char *, u_int16_t *);
static int get_proto(const char *, int *);
static int get_fltr_opts(char **, char *, size_t, int *);
static int interface_parser(char *);
static int class_parser(char *) ;
static int filter_parser(char *);
#ifdef INET6
static int filter6_parser(char *);
static int get_ip6addr(char **, struct in6_addr *, struct in6_addr *);
#endif
static int ctl_parser(char *);
static int delete_parser(char *);
static int red_parser(char *);
static int rio_parser(char *);
static int conditioner_parser(char *);
static int tc_action_parser(char *, char **, struct tc_action *);

#define MAX_LINE        1024
#define MAX_WORD        128
#define MAX_ARGS        64
#define MAX_ACTIONS     16

#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif
#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif
#define EQUAL(s1, s2)   (strcmp((s1), (s2)) == 0)

int     line_no = 0;
int     filter_dontwarn;

static char     curifname[IFNAMSIZ];
static struct if_nameindex *if_namelist = NULL;

struct cmd_tab {
        const char      *cmd;
        int             (*parser)(char *);
        const char      *help;
} cmd_tab[] = {
        {"?",           NULL,   "?"},
        {"help",        NULL,   "help"},
        {"quit",        NULL,   "quit"},
        {"interface",   interface_parser,       "interface if_name [bandwidth bps] [cbq|hfsc]"},
        {"class",       class_parser,   "class discipline if_name class_name [parent]"},
        {"filter",      filter_parser,  "filter if_name class_name [name filt_name] dst [netmask #] dport src [netmask #] sport proto [tos # [tosmask #] [gpi #] [dontwarn]"},
        {"altq",        ctl_parser,     "altq if_name {enable|disable}"},
        {"delete",      delete_parser,  "delete if_name class_name [filter_name]"},
#ifdef INET6
        {"filter6",     filter6_parser, "filter6 if_name class_name [name filt_name] dst[/prefix] dport src[/prefix] sport proto [flowlabel #][tclass # [tclassmask #]][gpi #] [dontwarn]"},
#endif
        {"red",         red_parser,     "red th_min th_max inv_pmax"},
        {"rio",         rio_parser,     "rio low_th_min low_th_max low_inv_pmax med_th_min med_th_max med_inv_pmax high_th_min high_th_max high_inv_pmax"},
        {"conditioner", conditioner_parser,     "conditioner if_name cdnr_name <tc_action>"},
        {"debug",       NULL,           "debug"},
        {NULL,          NULL,           NULL}   /* termination */
};

/*
 * read one line from the specified stream. if it's a command,
 * execute the command.
 * returns 1 if OK, 0 if error or EOF.
 */
int
do_command(FILE *fp)
{
        char    cmd_line[MAX_LINE], cmd[MAX_WORD], *cp;
        struct cmd_tab *tp;
        int     len, rval;

        /*
         * read a line from the stream and make it a null-terminated string
         */
        cp = cmd_line;
read_line:
        if (fgets(cp, &cmd_line[MAX_LINE] - cp, fp) == NULL)
                /* EOF or error */
                return(0);
        line_no++;

        /* null-terminate the line */
        if ((len = strlen(cmd_line)) > 0) {
                cp = cmd_line + len - 1;
                if (*cp == '\n') {
                        /* if escaped newline, read next line */
                        if (len > 1 &&  *(cp - 1) == '\\')
                                goto read_line;
                        *cp = '\0';
                } else if (!feof(fp))
                        err(1, "LINE %d too long!", line_no);
        }
        /* trim comments */
        if ((cp = strchr(cmd_line, '#')) != NULL)
                *cp = '\0';

        cp = cmd_line;
        if ((len = next_word(&cp, cmd)) == 0)
                /* no command in this line */
                return (1);

        /* fnind the corresponding parser */
        rval = 0;
        for (tp = cmd_tab; tp->cmd != NULL; tp++)
                if (strncmp(cmd, tp->cmd, len) == 0)
                        break;

        if (tp->cmd == NULL) {
                if (fp == stdin) {
                        printf(" ?? %s\n", cmd);
                        rval = 1;
                } else
                        LOG(LOG_ERR, 0, "unknown command: %s", cmd);
                return (rval);
        }

        if (tp->parser != NULL)
                rval = (*tp->parser)(cp);
        else {
                /* handle other commands */
                if (strcmp(tp->cmd, "quit") == 0)
                        rval = 0;
                else if (strcmp(tp->cmd, "help") == 0 ||
                         strcmp(tp->cmd, "?") == 0) {
                        for (tp = cmd_tab; tp->cmd != NULL; tp++)
                                printf("%s\n", tp->help);
                        rval = 1;
                } else if (strcmp(tp->cmd, "debug") == 0) {
                        if (m_debug & DEBUG_ALTQ) {
                                /* turn off verbose */
                                l_debug = LOG_INFO;
                                m_debug &= ~DEBUG_ALTQ;
                        } else {
                                /* turn on verbose */
                                l_debug = LOG_DEBUG;
                                m_debug |= DEBUG_ALTQ;
                        }
                        rval = 1;
                }
        }
        return (rval);
}

static int
is_qdisc_name(const char *qname)
{
        struct qdisc_parser *qp;

        for (qp = qdisc_parser; qp->qname != NULL; qp++)
                if (strncmp(qp->qname, qname, strlen(qp->qname)) == 0)
                        return (1);
        return (0);
}

static int
qdisc_interface_parser(const char * qname, const char *ifname,
                       int argc, char **argv)
{
        struct qdisc_parser *qp;

        for (qp = qdisc_parser; qp->qname != NULL; qp++)
                if (strncmp(qp->qname, qname, strlen(qp->qname)) == 0)
                        return (*qp->interface_parser)(ifname, argc, argv);
        return (0);
}

static int
qdisc_class_parser(const char *qname, const char *ifname,
                   const char *class_name, const char *parent_name,
                   int argc, char **argv)
{
        struct qdisc_parser *qp;
        struct ifinfo   *ifinfo;

        for (qp = qdisc_parser; qp->qname != NULL; qp++)
                if (strncmp(qp->qname, qname, strlen(qp->qname)) == 0) {
                        if (qp->class_parser == NULL) {
                                LOG(LOG_ERR, 0,
                                    "class can't be specified for %s", qp->qname);
                                return (0);
                        }
                        if ((ifinfo = ifname2ifinfo(ifname)) == NULL) {
                                LOG(LOG_ERR, 0, "no such interface");
                                return (0);
                        }
                        if (strncmp(ifinfo->qdisc->qname, qname,
                                    strlen(ifinfo->qdisc->qname)) != 0) {
                                LOG(LOG_ERR, 0,
                                    "qname doesn't match the interface");
                                return (0);
                        }
                        return (*qp->class_parser)(ifname, class_name,
                                                   parent_name, argc, argv);
                }
        return (0);
}

/*
 * read the config file
 */
int
qcmd_config(void)
{
        FILE    *fp;
        int     rval;

        if (if_namelist != NULL)
                if_freenameindex(if_namelist);
        if_namelist = if_nameindex();
        curifname[0] = '\0';

        LOG(LOG_INFO, 0, "ALTQ config file is %s", altqconfigfile);

        fp = fopen(altqconfigfile, "r");
        if (fp == NULL) {
                LOG(LOG_ERR, errno, "can't open %s", altqconfigfile, 0);
                return (QOPERR_INVAL);
        }
        line_no = 0;
        rval = 1;
        while (rval)
                rval = do_command(fp);

        if (!feof(fp)) {
                LOG(LOG_ERR, 0, "Error in %s, line %d.  config failed.",
                    altqconfigfile, line_no);
                (void) qcmd_destroyall();
                rval = QOPERR_INVAL;
        } else
                rval = 0;

        (void)fclose(fp);
        line_no = 0;
        return (rval);
}

static int 
next_word(char **cpp, char *b)
{
        char    *cp;
        int     i;

        cp = *cpp;
        while (*cp == ' ' || *cp == '\t')
                cp++;
        for (i = 0; i < MAX_WORD - 1; i++) {
                if (*cp == ' ' || *cp == '\t' || *cp == '\n' || *cp == '\0')
                        break;
                *b++ = *cp++;
        }
        *b = '\0';
        *cpp = cp;
        return (i);
}

char *
cur_ifname(void)
{
        return (curifname);
}

u_int
get_ifindex(const char *ifname)
{
        struct if_nameindex *ifnp;

        for (ifnp = if_namelist; ifnp->if_name != NULL; ifnp++)
                if (strcmp(ifname, ifnp->if_name) == 0)
                        return (ifnp->if_index);
        return (0);
}

static int
get_ifname(char **cpp, char **ifnamep)
{
        char w[MAX_WORD], *ocp;
        struct if_nameindex *ifnp;

        ocp = *cpp;
        if (next_word(&ocp, w) && if_namelist != NULL)
                for (ifnp = if_namelist; ifnp->if_name != NULL; ifnp++)
                        if (strcmp(w, ifnp->if_name) == 0) {
                                /* if_name found. advance the word pointer */
                                *cpp = ocp;
                                strlcpy(curifname, w, sizeof(curifname));
                                *ifnamep = curifname;
                                return (1);
                        }

        /* this is not interface name. use one in the context. */
        if (curifname[0] == '\0')
                return (0);
        *ifnamep = curifname;
        return (1);
}

/* set address and netmask in network byte order */
static int
get_addr(char **cpp, struct in_addr *addr, struct in_addr *mask)
{
        char w[MAX_WORD], *ocp;
        struct in_addr tmp;

        addr->s_addr = 0;
        mask->s_addr = 0xffffffff;

        if (!next_word(cpp, w))
                return (0);

        if (inet_aton((char *)w, &tmp) != 1) {
                /* try gethostbyname */
                struct hostent *h;

                if ((h = gethostbyname(w)) == NULL ||
                    h->h_addrtype != AF_INET || h->h_length != 4)
                        return (0);
                bcopy(h->h_addr, &tmp, (size_t)h->h_length);
        }
        addr->s_addr = tmp.s_addr;

        /* check if netmask option is present */
        ocp = *cpp;
        if (next_word(&ocp, w) && EQUAL(w, "netmask")) {
                if (!next_word(&ocp, w))
                        return (0);
                if (inet_aton((char *)w, (struct in_addr *)&tmp) != 1)
                        return (0);

                mask->s_addr = tmp.s_addr;
                *cpp = ocp;
                return (1);
        }
        /* no netmask option */
        return (1);
}

/* returns service number in network byte order */
static int
get_port(const char *name, u_int16_t *port_no)
{
        struct servent *s;
        u_int16_t num;

        if (isdigit((unsigned char)name[0])) {
                num = (u_int16_t)strtol(name, NULL, 0);
                *port_no = htons(num);
                return (1);
        }

        if ((s = getservbyname(name, 0)) == NULL)
                return (0);

        *port_no = (u_int16_t)s->s_port;
        return (1);
}

static int
get_proto(const char *name, int *proto_no)
{
        struct protoent *p;

        if (isdigit((unsigned char)name[0])) {
                *proto_no = (int)strtol(name, NULL, 0);
                return (1);
        }

        if ((p = getprotobyname(name)) == NULL)
                return (0);

        *proto_no = p->p_proto;
        return (1);
}

static int
get_fltr_opts(char **cpp, char *fltr_name, size_t len, int *ruleno)
{
        char w[MAX_WORD], *ocp;

        ocp = *cpp;
        while (next_word(&ocp, w)) {
                if (EQUAL(w, "name")) {
                        if (!next_word(&ocp, w))
                                return (0);
                        strlcpy(fltr_name, w, len);
                        *cpp = ocp;
                } else if (EQUAL(w, "ruleno")) {
                        if (!next_word(&ocp, w))
                                return (0);
                        *ruleno = (int)strtol(w, NULL, 0);
                        *cpp = ocp;
                } else
                        break;
        }
        return (1);
}


#define DISCIPLINE_NONE         0

static int
interface_parser(char *cmdbuf)
{
        char    w[MAX_WORD], *ap, *cp = cmdbuf;
        char    *ifname, *argv[MAX_ARGS], qdisc_name[MAX_WORD];
        int     argc;
    
        if (!get_ifname(&cp, &ifname)) {
                LOG(LOG_ERR, 0, "missing interface name");
                return (0);
        }

        /* create argument list & look for scheduling discipline options. */
        snprintf(qdisc_name, sizeof qdisc_name, "null");
        argc = 0;
        ap = w;
        while (next_word(&cp, ap)) {
                if (is_qdisc_name(ap))
                        strlcpy(qdisc_name, ap, sizeof qdisc_name);

                argv[argc] = ap;
                ap += strlen(ap) + 1;
                argc++;
                if (argc >= MAX_ARGS) {
                        LOG(LOG_ERR, 0, "too many args");
                        return (0);
                }
        }

        return qdisc_interface_parser(qdisc_name, ifname, argc, argv);
}


static int
class_parser(char *cmdbuf)
{
        char    w[MAX_WORD], *cp = cmdbuf;
        char    *ifname, qdisc_name[MAX_WORD];
        char    class_name[MAX_WORD], parent_name[MAX_WORD];
        char    *clname = class_name;
        char    *parent = NULL;
        char    *argv[MAX_ARGS], *ap;
        int     argc;

        /* get scheduling class */
        if (!next_word(&cp, qdisc_name)) {
                LOG(LOG_ERR, 0, "missing discipline");
                return (0);
        }
        if (!is_qdisc_name(qdisc_name)) {
                LOG(LOG_ERR, 0, "unknown discipline '%s'", qdisc_name);
                return (0);
        }

        /* get interface name */
        if (!get_ifname(&cp, &ifname)) {
                LOG(LOG_ERR, 0, "missing interface name");
                return (0);
        }

        /* get class name */
        if (!next_word(&cp, class_name)) {
                LOG(LOG_ERR, 0, "missing class name");
                return (0);
        }

        /* get parent name */
        if (!next_word(&cp, parent_name)) {
                LOG(LOG_ERR, 0, "missing parent class");
                return (0);
        }
        if (!EQUAL(parent_name, "null") && !EQUAL(parent_name, "NULL"))
                parent = parent_name;
        else
                parent = NULL;

        ap = w;
        argc = 0;
        while (next_word(&cp, ap)) {
                argv[argc] = ap;
                ap += strlen(ap) + 1;
                argc++;
                if (argc >= MAX_ARGS) {
                        LOG(LOG_ERR, 0, "too many args");
                        return (0);
                }
        }

        return qdisc_class_parser(qdisc_name, ifname, clname, parent,
                                  argc, argv);
}

static int
filter_parser(char *cmdbuf)
{
        char    w[MAX_WORD], *cp = cmdbuf;
        char    *ifname, class_name[MAX_WORD], fltr_name[MAX_WORD];
        char    *flname = NULL;
        struct flow_filter      sfilt;
        int     protocol;
        u_char  tos, tosmask;
        int     ruleno;
        int     dontwarn = 0;
        int     error;

        memset(&sfilt, 0, sizeof(sfilt));
        sfilt.ff_flow.fi_family = AF_INET;

        if (!get_ifname(&cp, &ifname)) {
                LOG(LOG_ERR, 0, "missing interface name in filter command");
                return (0);
        }

        if (!next_word(&cp, class_name)) {
                LOG(LOG_ERR, 0, "missing class name in filter command");
                return (0);
        }

        fltr_name[0] = '\0';
        ruleno = 0;
        if (!get_fltr_opts(&cp, &fltr_name[0], sizeof(fltr_name), &ruleno)) {
                LOG(LOG_ERR, 0, "bad filter option");
                return (0);
        }
        if (fltr_name[0] != '\0')
                flname = fltr_name;
        sfilt.ff_ruleno = ruleno;

        /* get filter destination Address */
        if (!get_addr(&cp, &sfilt.ff_flow.fi_dst, &sfilt.ff_mask.mask_dst)) {
                LOG(LOG_ERR, 0, "bad filter destination address");
                return (0);
        }

        /* get filter destination port */
        if (!next_word(&cp, w)) {
                LOG(LOG_ERR, 0, "missing filter destination port");
                return (0);
        }
        if (!get_port(w, &sfilt.ff_flow.fi_dport)) {
                LOG(LOG_ERR, 0, "bad filter destination port");
                return (0);
        }

        /* get filter source address */
        if (!get_addr(&cp, &sfilt.ff_flow.fi_src, &sfilt.ff_mask.mask_src)) {
                LOG(LOG_ERR, 0, "bad filter source address");
                return (0);
        }

        /* get filter source port */
        if (!next_word(&cp, w)) {
                LOG(LOG_ERR, 0, "missing filter source port");
                return (0);
        }
        if (!get_port(w, &sfilt.ff_flow.fi_sport)) {
                LOG(LOG_ERR, 0, "bad filter source port");
                return (0);
        }

        /* get filter protocol id */
        if (!next_word(&cp, w)) {
                LOG(LOG_ERR, 0, "missing filter protocol");
                return (0);
        }
        if (!get_proto(w, &protocol)) {
                LOG(LOG_ERR, 0, "bad protocol");
                return (0);
        }
        sfilt.ff_flow.fi_proto = protocol; 

        while (next_word(&cp, w)) {
                if (EQUAL(w, "tos")) {
                        tos = 0;
                        tosmask = 0xff;

                        if (next_word(&cp, w)) {
                                tos = (u_char)strtol(w, NULL, 0);
                                if (next_word(&cp, w)) {
                                        if (EQUAL(w, "tosmask")) {
                                                next_word(&cp, w);
                                                tosmask = (u_char)strtol(w, NULL, 0);
                                        }
                                }
                        }
                        sfilt.ff_flow.fi_tos = tos;
                        sfilt.ff_mask.mask_tos = tosmask;
                } else if (EQUAL(w, "gpi")) {
                        if (next_word(&cp, w)) {
                                sfilt.ff_flow.fi_gpi =
                                        (u_int32_t)strtoul(w, NULL, 0);
                                sfilt.ff_flow.fi_gpi =
                                        htonl(sfilt.ff_flow.fi_gpi);
                        }
                } else if (EQUAL(w, "dontwarn"))
                        dontwarn = 1;
        }

        /*
         * Add the filter.
         */
        filter_dontwarn = dontwarn;     /* XXX */
        error = qcmd_add_filter(ifname, class_name, flname, &sfilt);
        filter_dontwarn = 0;            /* XXX */
        if (error) {
                LOG(LOG_ERR, 0,
                    "can't add filter to class '%s' on interface '%s'",
                    class_name, ifname);
                return (0);
        }
        return (1);
}

#ifdef INET6
static int
filter6_parser(char *cmdbuf)
{
        char    w[MAX_WORD], *cp = cmdbuf;
        char    *ifname, class_name[MAX_WORD], fltr_name[MAX_WORD];
        char    *flname = NULL;
        struct flow_filter6     sfilt;
        int     protocol;
        u_char  tclass, tclassmask;
        int     ruleno;
        int     dontwarn = 0;
        int     ret;

        memset(&sfilt, 0, sizeof(sfilt));
        sfilt.ff_flow6.fi6_family = AF_INET6;

        if (!get_ifname(&cp, &ifname)) {
                LOG(LOG_ERR, 0, "missing interface name");
                return (0);
        }

        if (!next_word(&cp, class_name)) {
                LOG(LOG_ERR, 0, "missing class name");
                return (0);
        }

        fltr_name[0] = '\0';
        ruleno = 0;
        if (!get_fltr_opts(&cp, &fltr_name[0], sizeof(fltr_name), &ruleno)) {
                LOG(LOG_ERR, 0, "bad filter option");
                return (0);
        }
        if (fltr_name[0] != '\0')
                flname = fltr_name;
        sfilt.ff_ruleno = ruleno;

        /* get filter destination address */
        if (!get_ip6addr(&cp, &sfilt.ff_flow6.fi6_dst,
                         &sfilt.ff_mask6.mask6_dst)) {
                LOG(LOG_ERR, 0, "bad destination address");
                return (0);
        }

        /* get filter destination port */
        if (!next_word(&cp, w)) {
                LOG(LOG_ERR, 0, "missing filter destination port");
                return (0);
        }
        if (!get_port(w, &sfilt.ff_flow6.fi6_dport)) {
                LOG(LOG_ERR, 0, "bad filter destination port");
                return (0);
        }

        /* get filter source address */
        if (!get_ip6addr(&cp, &sfilt.ff_flow6.fi6_src,
                         &sfilt.ff_mask6.mask6_src)) {
                LOG(LOG_ERR, 0, "bad source address");
                return (0);
        }

        /* get filter source port */
        if (!next_word(&cp, w)) {
                LOG(LOG_ERR, 0, "missing filter source port");
                return (0);
        }
        if (!get_port(w, &sfilt.ff_flow6.fi6_sport)) {
                LOG(LOG_ERR, 0, "bad filter source port");
                return (0);
        }

        /* get filter protocol id */
        if (!next_word(&cp, w)) {
                LOG(LOG_ERR, 0, "missing filter protocol");
                return (0);
        }
        if (!get_proto(w, &protocol)) {
                LOG(LOG_ERR, 0, "bad protocol");
                return (0);
        }
        sfilt.ff_flow6.fi6_proto = protocol;

        while (next_word(&cp, w)) {
                if (EQUAL(w, "tclass")) {
                        tclass = 0;
                        tclassmask = 0xff;

                        if (next_word(&cp, w)) {
                                tclass = (u_char)strtol(w, NULL, 0);
                                if (next_word(&cp, w)) {
                                        if (EQUAL(w, "tclassmask")) {
                                                next_word(&cp, w);
                                                tclassmask =
                                                    (u_char)strtol(w, NULL, 0);
                                        }
                                }
                        }
                        sfilt.ff_flow6.fi6_tclass = tclass;
                        sfilt.ff_mask6.mask6_tclass = tclassmask;
                } else if (EQUAL(w, "gpi")) {
                        if (next_word(&cp, w)) {
                                sfilt.ff_flow6.fi6_gpi =
                                        (u_int32_t)strtoul(w, NULL, 0);
                                sfilt.ff_flow6.fi6_gpi =
                                        htonl(sfilt.ff_flow6.fi6_gpi);
                        }
                } else if (EQUAL(w, "flowlabel")) {
                        if (next_word(&cp, w)) {
                                sfilt.ff_flow6.fi6_flowlabel =
                                   (u_int32_t)strtoul(w, NULL, 0) & 0x000fffff;
                                sfilt.ff_flow6.fi6_flowlabel =
                                        htonl(sfilt.ff_flow6.fi6_flowlabel);
                        }
                } else if (EQUAL(w, "dontwarn"))
                        dontwarn = 1;
        }

        /*
         * Add the filter.
         */
        filter_dontwarn = dontwarn;     /* XXX */
        ret = qcmd_add_filter(ifname, class_name, flname,
                              (struct flow_filter *)&sfilt);
        filter_dontwarn = 0;            /* XXX */
        if (ret) {
                LOG(LOG_ERR, 0,
                    "can't add filter to class '%s' on interface '%s'",
                    class_name, ifname);
                return (0);
        }

        return (1);
}

static int
get_ip6addr(char **cpp, struct in6_addr *addr, struct in6_addr *mask)
{
        char w[MAX_WORD], *prefix;
        u_char *cp;
        int len;

        *addr = in6addr_any;  /* set all 0 */
        *mask = in6addr_any;  /* set all 0 */

        if (!next_word(cpp, w))
                return (0);

        if (EQUAL(w, "0"))
                /* abbreviation of a wildcard (::0) */
                return (1);

        if ((prefix = strchr(w, '/')) != NULL) {
                /* address has prefix length */
                *prefix++ = '\0';
        }

        if (inet_pton(AF_INET6, w, addr) != 1)
                return (0);

        if (IN6_IS_ADDR_UNSPECIFIED(addr) && prefix == NULL)
                /* wildcard */
                return (1);

        /* convert address prefix length to address mask */
        if (prefix != NULL) {
                len = (int)strtol(prefix, NULL, 0);
                if ((len < 0) || (len > 128))
                        return (0);
                for (cp = (u_char *)mask; len > 7; len -= 8)
                        *cp++ = 0xff;
                if (len > 0)
                        *cp = (0xff << (8 - len)) & 0xff;

                IN6ADDR32_SET(addr, 0, IN6ADDR32_GET(mask, 0) &
                    IN6ADDR32_GET(addr, 0));
                IN6ADDR32_SET(addr, 1, IN6ADDR32_GET(mask, 1) &
                    IN6ADDR32_GET(addr, 1));
                IN6ADDR32_SET(addr, 2, IN6ADDR32_GET(mask, 2) &
                    IN6ADDR32_GET(addr, 2));
                IN6ADDR32_SET(addr, 3, IN6ADDR32_GET(mask, 3) &
                    IN6ADDR32_GET(addr, 3));
        } else
                /* full mask */
                memset(mask, 0xff, sizeof(struct in6_addr));

        return (1);
}

#endif /* INET6 */

static int
ctl_parser(char *cmdbuf)
{
        char    w[MAX_WORD], *cp = cmdbuf;
        char    *ifname;
        int     state;
        int     rval;

        if (!get_ifname(&cp, &ifname)) {
                printf("missing interface name in %s, line %d",
                       altqconfigfile, line_no);
                return (0);
        }

        if (!next_word(&cp, w)) {
                state = is_q_enabled(ifname);
                printf("altq %s on %s\n",
                       state ? "enabled" : "disabled", ifname);
                return (1);
        }

        if (EQUAL(w, "enable")) {
                rval = qcmd_enable(ifname);
                printf("altq %s on %s\n",
                       (rval == 0) ? "enabled" : "enable failed!", ifname);
        } else if (EQUAL(w, "disable")) {
                rval = qcmd_disable(ifname);
                printf("altq %s on %s\n",
                       (rval == 0) ? "disabled" : "disable failed!", ifname);
        } else if (EQUAL(w, "reload")) {
                printf("reinitializing altq...\n");
                qcmd_destroyall();
                qcmd_init();
        } else
                return (0);
        return (1);
}

static int
delete_parser(char *cmdbuf)
{
        char    *cp = cmdbuf;
        char    *ifname, class_name[MAX_WORD], filter_name[MAX_WORD];
        int     ret;

        if (!get_ifname(&cp, &ifname)) {
                LOG(LOG_ERR, 0, "missing interface name");
                return (0);
        }

        if (!next_word(&cp, class_name)) {
                LOG(LOG_ERR, 0, "missing class name");
                return (0);
        }

        /* check if filter is specified */
        if (next_word(&cp, filter_name)) {
                ret = qcmd_delete_filter(ifname, class_name, filter_name);
                if (ret) {
                        LOG(LOG_ERR, 0,
                            "can't delete filter '%s' on interface '%s'",
                            filter_name, ifname);
                        return (0);
                }
                return (1);
        }

        ret = qcmd_delete_class(ifname, class_name);
        if (ret) {
                LOG(LOG_ERR, 0,
                    "can't delete class '%s' on interface '%s'",
                    class_name, ifname);
                return (0);
        }

        return (1);
}

static int
red_parser(char *cmdbuf)
{
        char    w[MAX_WORD], *cp = cmdbuf;
        int th_min, th_max, inv_pmax;

        if (!next_word(&cp, w))
                goto bad;
        th_min = (int)strtol(w, NULL, 0);

        if (!next_word(&cp, w))
                goto bad;
        th_max = (int)strtol(w, NULL, 0);

        if (!next_word(&cp, w))
                goto bad;
        inv_pmax = (int)strtol(w, NULL, 0);

        if (qop_red_set_defaults(th_min, th_max, inv_pmax) != 0) {
                LOG(LOG_ERR, 0, "can't set red default parameters");
                return (0);
        }

        return (1);

 bad:
        LOG(LOG_ERR, 0, "bad red parameter");
        return (0);
}

static int
rio_parser(char *cmdbuf)
{
        char    w[MAX_WORD], *cp = cmdbuf;
        int     i;
        struct redparams params[RIO_NDROPPREC];

        for (i = 0; i < RIO_NDROPPREC; i++) {
                if (!next_word(&cp, w))
                        goto bad;
                params[i].th_min = (int)strtol(w, NULL, 0);

                if (!next_word(&cp, w))
                        goto bad;
                params[i].th_max = (int)strtol(w, NULL, 0);

                if (!next_word(&cp, w))
                        goto bad;
                params[i].inv_pmax = (int)strtol(w, NULL, 0);
        }

        if (qop_rio_set_defaults(&params[0]) != 0) {
                LOG(LOG_ERR, 0, "can't set rio default parameters");
                return (0);
        }

        return (1);

 bad:
        LOG(LOG_ERR, 0, "bad rio parameter");
        return (0);
}

static int
conditioner_parser(char *cmdbuf)
{
        char    cdnr_name[MAX_WORD], *cp = cmdbuf;
        char    *ifname;
        struct tc_action action[MAX_ACTIONS];

        if (!get_ifname(&cp, &ifname)) {
                LOG(LOG_ERR, 0, "missing interface name");
                return (0);
        }

        /* get conditioner name */
        if (!next_word(&cp, cdnr_name)) {
                LOG(LOG_ERR, 0, "missing cdnr name");
                return (0);
        }

        if (tc_action_parser(ifname, &cp, &action[0]) == 0)
                return (0);

        if (qcmd_cdnr_add_element(NULL, ifname, cdnr_name, &action[0]) != 0)
                return (0);
        return (1);
}

/*
 * recursively parse '<'tc_action'>'
 * note that array "action" grows during recursive parse.
 */
static int
tc_action_parser(char *ifname, char **cpp, struct tc_action *action)
{
        char    *cp, *start, *end;
        char    type[MAX_WORD], w[MAX_WORD];
        int     depth, i;
        struct tb_profile profile[2];

        /*
         * find a possibly nested pair of '<' and '>',
         * make them pointed by 'start' and 'end'.
         */
        start = strchr(*cpp, '<');
        if (start == NULL) {
                LOG(LOG_ERR, 0, "conditioner action missing");
                return (0);
        }
        depth = 1;
        cp = start + 1;
        do {
                end = strpbrk(cp, "<>");
                if (end == NULL) {
                        LOG(LOG_ERR, 0,
                            "conditioner action delimiter mismatch");
                        return (0);
                }
                if (*end == '<')
                        depth++;
                else if (*end == '>')
                        depth--;
                cp = end + 1;
        } while (depth > 0);
        *end = '\0';
        *cpp = end + 1;
        cp = start + 1;

        if (IsDebug(DEBUG_ALTQ)) {
                printf("tc_action_parser: [%s]\n", cp);
        }

        if (!next_word(&cp, type)) {
                LOG(LOG_ERR, 0, "missing conditioner action type");
                return (0);
        }

        /*
         * action type specific process
         */
        if (EQUAL(type, "conditioner")) {
                if (!next_word(&cp, w)) {
                        LOG(LOG_ERR, 0,
                            "missing conditioner name");
                        return (0);
                }
                action->tca_code = TCACODE_HANDLE;
                action->tca_handle = cdnr_name2handle(ifname, w);
                if (action->tca_handle == CDNR_NULL_HANDLE) {
                        LOG(LOG_ERR, 0,
                            "wrong conditioner name %s", w);
                        return (0);
                }
        } else if (EQUAL(type, "pass")) {
                action->tca_code = TCACODE_PASS;
        } else if (EQUAL(type, "drop")) {
                action->tca_code = TCACODE_DROP;
        } else if (EQUAL(type, "mark")) {
                if (!next_word(&cp, w)) {
                        LOG(LOG_ERR, 0, "missing dscp");
                        return (0);
                }
                action->tca_code = TCACODE_MARK;
                action->tca_dscp = (u_int8_t)strtol(w, NULL, 0);
        } else if (EQUAL(type, "tbmeter")) {
                if (!next_word(&cp, w)) {
                        LOG(LOG_ERR, 0, "missing tb profile");
                        return (0);
                }
                profile[0].rate = atobps(w);
                if (!next_word(&cp, w)) {
                        LOG(LOG_ERR, 0, "missing tb profile");
                        return (0);
                }
                profile[0].depth = atobytes(w);
                if (tc_action_parser(ifname, &cp, &action[1]) == 0)
                        return (0);
                if (tc_action_parser(ifname, &cp, &action[2]) == 0)
                        return (0);

                if (qcmd_cdnr_add_tbmeter(action, ifname, NULL, &profile[0],
                                          &action[1], &action[2]) != 0)
                        return (0);
        } else if (EQUAL(type, "trtcm")) {
                int coloraware = 0;     /* default is color-blind */

                for (i=0; i<2; i++) {
                        if (!next_word(&cp, w)) {
                                LOG(LOG_ERR, 0, "missing tb profile");
                                return (0);
                        }
                        profile[i].rate = atobps(w);
                        if (!next_word(&cp, w)) {
                                LOG(LOG_ERR, 0, "missing tb profile");
                                return (0);
                        }
                        profile[i].depth = atobytes(w);
                }
                if (tc_action_parser(ifname, &cp, &action[1]) == 0)
                        return (0);
                if (tc_action_parser(ifname, &cp, &action[2]) == 0)
                        return (0);
                if (tc_action_parser(ifname, &cp, &action[3]) == 0)
                        return (0);
                if (next_word(&cp, w)) {
                        if (EQUAL(w, "coloraware"))
                                coloraware = 1;
                        else if (EQUAL(w, "colorblind"))
                                coloraware = 0;
                }

                if (qcmd_cdnr_add_trtcm(action, ifname, NULL,
                                        &profile[0], &profile[1],
                                        &action[1], &action[2], &action[3],
                                        coloraware) != 0)
                        return (0);
        } else if (EQUAL(type, "tswtcm")) {
                u_int32_t cmtd_rate, peak_rate, avg_interval;

                if (!next_word(&cp, w)) {
                        LOG(LOG_ERR, 0, "missing cmtd rate");
                        return (0);
                }
                cmtd_rate = atobps(w);

                if (!next_word(&cp, w)) {
                        LOG(LOG_ERR, 0, "missing peak rate");
                        return (0);
                }
                peak_rate = atobps(w);

                if (!next_word(&cp, w)) {
                        LOG(LOG_ERR, 0, "missing avg interval");
                        return (0);
                }
                avg_interval = (u_int32_t)strtoul(w, NULL, 0);

                if (tc_action_parser(ifname, &cp, &action[1]) == 0)
                        return (0);
                if (tc_action_parser(ifname, &cp, &action[2]) == 0)
                        return (0);
                if (tc_action_parser(ifname, &cp, &action[3]) == 0)
                        return (0);

                if (qcmd_cdnr_add_tswtcm(action, ifname, NULL,
                                         cmtd_rate, peak_rate, avg_interval,
                                         &action[1], &action[2], &action[3])
                    != 0)
                        return (0);
        } else {
                LOG(LOG_ERR, 0, "unknown action type %s");
                return (0);
        }

        *end = '>';     /* restore the end delimiter */

        return (1);
}