/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* enable debug output and some debug asserts */
#undef _IPQOS_CONF_DEBUG
#include <stdlib.h>
#include <unistd.h>
#include <libintl.h>
#include <signal.h>
#include <strings.h>
#include <sys/nvpair.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/socket.h>
#include <limits.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <libipp.h>
#include <ipp/ipp_config.h>
#include <ipp/ipgpc/ipgpc.h>
#include <ipp/ipp.h>
#ifdef _IPQOS_CONF_DEBUG
#include <assert.h>
#endif
#include <sys/sockio.h>
#include <syslog.h>
#include <stdarg.h>
#include <libintl.h>
#include <locale.h>
#include <pwd.h>
#include "ipqosconf.h"
#if defined(_IPQOS_CONF_DEBUG)
/* debug level */
static int ipqosconf_dbg_flgs =
/*
*/
RBK |
MHME |
KRET |
DIFF |
APPLY |
L2 |
L1 |
L0 |
0;
#define IPQOSCDBG0(lvl, x)\
if (lvl & ipqosconf_dbg_flgs)\
(void) fprintf(stderr, x)
#define IPQOSCDBG1(lvl, x, y)\
if (lvl & ipqosconf_dbg_flgs)\
(void) fprintf(stderr, x, y)
#define IPQOSCDBG2(lvl, x, y, z)\
if (lvl & ipqosconf_dbg_flgs)\
(void) fprintf(stderr, x, y, z)
#define IPQOSCDBG3(lvl, x, y, z, a)\
if (lvl & ipqosconf_dbg_flgs)\
(void) fprintf(stderr, x, y, z, a)
#define IPQOSCDBG4(lvl, x, y, z, a, b)\
if (lvl & ipqosconf_dbg_flgs)\
(void) fprintf(stderr, x, y, z, a, b)
#define IPQOSCDBG5(lvl, x, y, z, a, b, c)\
if (lvl & ipqosconf_dbg_flgs)\
(void) fprintf(stderr, x, y, z, a, b, c)
#else /* defined(_IPQOS_CONF_DEBUG) && !defined(lint) */
#define IPQOSCDBG0(lvl, x)
#define IPQOSCDBG1(lvl, x, y)
#define IPQOSCDBG2(lvl, x, y, z)
#define IPQOSCDBG3(lvl, x, y, z, a)
#define IPQOSCDBG4(lvl, x, y, z, a, b)
#define IPQOSCDBG5(lvl, x, y, z, a, b, c)
#endif /* defined(_IPQOS_CONF_DEBUG) */
/* function prototypes */
static int modify_params(char *, nvlist_t **, int, boolean_t);
static int add_class(char *, char *, int, boolean_t, char *);
static int modify_class(char *, char *, int, boolean_t, char *,
enum ipp_flags);
static int remove_class(char *, char *, int, enum ipp_flags);
static int add_filter(char *, ipqos_conf_filter_t *, int);
static int modify_filter(char *, ipqos_conf_filter_t *, int);
static int remove_filter(char *, char *, int, int);
static boolean_t arrays_equal(int *, int *, uint32_t);
static int diffclass(ipqos_conf_class_t *, ipqos_conf_class_t *);
static int diffparams(ipqos_conf_params_t *, ipqos_conf_params_t *, char *);
static int difffilter(ipqos_conf_filter_t *, ipqos_conf_filter_t *, char *);
static int add_filters(ipqos_conf_filter_t *, char *, int, boolean_t);
static int add_classes(ipqos_conf_class_t *, char *, int, boolean_t);
static int modify_items(ipqos_conf_action_t *);
static int add_items(ipqos_conf_action_t *, boolean_t);
static int add_item(ipqos_conf_action_t *, boolean_t);
static int remove_items(ipqos_conf_action_t *, boolean_t);
static int remove_item(ipqos_conf_action_t *, boolean_t);
static int undo_modifys(ipqos_conf_action_t *, ipqos_conf_action_t *);
static int applydiff(ipqos_conf_action_t *, ipqos_conf_action_t *);
static int rollback(ipqos_conf_action_t *, ipqos_conf_action_t *);
static int rollback_recover(ipqos_conf_action_t *);
static ipqos_conf_class_t *classexist(char *, ipqos_conf_class_t *);
static ipqos_conf_filter_t *filterexist(char *, int, ipqos_conf_filter_t *);
static ipqos_conf_action_t *actionexist(char *, ipqos_conf_action_t *);
static int diffnvlists(nvlist_t *, nvlist_t *, char *, int *, place_t);
static int diffaction(ipqos_conf_action_t *, ipqos_conf_action_t *);
static int diffconf(ipqos_conf_action_t *, ipqos_conf_action_t *);
static int readllong(char *, long long *, char **);
static int readuint8(char *, uint8_t *, char **);
static int readuint16(char *, uint16_t *, char **);
static int readint16(char *, int16_t *, char **);
static int readint32(char *, int *, char **);
static int readuint32(char *, uint32_t *, char **);
static int readbool(char *, boolean_t *);
static void setmask(int, in6_addr_t *, int);
static int readtoken(FILE *, char **);
static nvpair_t *find_nvpair(nvlist_t *, char *);
static char *prepend_module_name(char *, char *);
static int readnvpair(FILE *, FILE *, nvlist_t **, nvpair_t **,
ipqos_nvtype_t *, place_t, char *);
static int add_aref(ipqos_conf_act_ref_t **, char *, char *);
static int readparams(FILE *, FILE *, char *, ipqos_conf_params_t *);
static int readclass(FILE *, char *, ipqos_conf_class_t **, char **, int);
static int readfilter(FILE *, FILE *, char *, ipqos_conf_filter_t **, char **,
int);
static FILE *validmod(char *, int *);
static int readaction(FILE *, ipqos_conf_action_t **);
static int actions_unique(ipqos_conf_action_t *, char **);
static int validconf(ipqos_conf_action_t *, int);
static int readconf(FILE *, ipqos_conf_action_t **);
static int flush(boolean_t *);
static int atomic_flush(boolean_t);
static int flushconf();
static int writeconf(ipqos_conf_action_t *, char *);
static int commitconf();
static int applyconf(char *ifile);
static int block_all_signals();
static int restore_all_signals();
static int unlock(int fd);
static int lock();
static int viewconf(int);
static void usage();
static int valid_name(char *);
static int in_cycle(ipqos_conf_action_t *);
static int readtype(FILE *, char *, char *, ipqos_nvtype_t *, str_val_nd_t **,
char *, boolean_t, place_t *);
static int read_int_array_info(char *, str_val_nd_t **, uint32_t *, int *,
int *, char *);
static str_val_nd_t *read_enum_nvs(char *, char *);
static int add_str_val_entry(str_val_nd_t **, char *, uint32_t);
static void free_str_val_entrys(str_val_nd_t *);
static void get_str_val_value_range(str_val_nd_t *, int *, int *);
static int read_enum_value(FILE *, char *, str_val_nd_t *, uint32_t *);
static int read_mapped_values(FILE *, nvlist_t **, char *, char *,
int);
static int read_int_array(FILE *, char *, int **, uint32_t, int, int,
str_val_nd_t *);
static int str_val_list_lookup(str_val_nd_t *, char *, uint32_t *);
static int parse_kparams(char *, ipqos_conf_params_t *, nvlist_t *);
static int parse_kclass(ipqos_conf_class_t *, nvlist_t *);
static int parse_kfilter(ipqos_conf_filter_t *, nvlist_t *);
static int parse_kaction(nvlist_t *, ipqos_actinfo_prm_t *);
static int readkconf(ipqos_conf_action_t **);
static void print_int_array(FILE *, int *, uint32_t, int, int, str_val_nd_t *,
int);
static void printrange(FILE *fp, uint32_t, uint32_t);
static void printenum(FILE *, uint32_t, str_val_nd_t *);
static void printproto(FILE *, uint8_t);
static void printport(FILE *, uint16_t);
static int printnvlist(FILE *, char *, nvlist_t *, int, ipqos_conf_filter_t *,
int, place_t);
static int virtual_action(char *);
static void free_arefs(ipqos_conf_act_ref_t *);
static void print_action_nm(FILE *, char *);
static int add_orig_ipqosconf(nvlist_t *);
static char *get_originator_nm(uint32_t);
static void mark_classes_filters_new(ipqos_conf_action_t *);
static void mark_classes_filters_del(ipqos_conf_action_t *);
static void mark_config_new(ipqos_conf_action_t *);
static int printifname(FILE *, int);
static int readifindex(char *, int *);
static void cleanup_string_table(char **, int);
static int domultihome(ipqos_conf_filter_t *, ipqos_conf_filter_t **,
boolean_t);
static int dup_filter(ipqos_conf_filter_t *, ipqos_conf_filter_t **, int, int,
void *, void *, int);
static void free_actions(ipqos_conf_action_t *);
static ipqos_conf_filter_t *alloc_filter();
static void free_filter(ipqos_conf_filter_t *);
static int read_curl_begin(FILE *);
static ipqos_conf_class_t *alloc_class(void);
static int diffclasses(ipqos_conf_action_t *old, ipqos_conf_action_t *new);
static int difffilters(ipqos_conf_action_t *old, ipqos_conf_action_t *new);
static int dup_class(ipqos_conf_class_t *src, ipqos_conf_class_t **dst);
static int add_action(ipqos_conf_action_t *act);
static int masktocidr(int af, in6_addr_t *mask);
static int read_perm_items(int, FILE *, char *, char ***, int *);
static int in_string_table(char *stable[], int size, char *string);
static void list_end(ipqos_list_el_t **listp, ipqos_list_el_t ***lendpp);
static void add_to_list(ipqos_list_el_t **listp, ipqos_list_el_t *el);
static int read_cfile_ver(FILE *, char *);
static char *quote_ws_string(const char *);
static int read_tfile_ver(FILE *, char *, char *);
static int ver_str_to_int(char *);
static void printuser(FILE *fp, uid_t uid);
static int readuser(char *str, uid_t *uid);
/*
* macros to call list functions with the more complex list element type
* cast to the skeletal type iqpos_list_el_t.
*/
#define LIST_END(list, end)\
list_end((ipqos_list_el_t **)list, (ipqos_list_el_t ***)end)
#define ADD_TO_LIST(list, el)\
add_to_list((ipqos_list_el_t **)list, (ipqos_list_el_t *)el)
/*
* Macros to produce a quoted string containing the value of a
* preprocessor macro. For example, if SIZE is defined to be 256,
* VAL2STR(SIZE) is "256". This is used to construct format
* strings for scanf-family functions below.
*/
#define QUOTE(x) #x
#define VAL2STR(x) QUOTE(x)
/* globals */
/* table of supported parameter types and enum value */
static str_val_t nv_types[] = {
{"uint8", IPQOS_DATA_TYPE_UINT8},
{"int16", IPQOS_DATA_TYPE_INT16},
{"uint16", IPQOS_DATA_TYPE_UINT16},
{"int32", IPQOS_DATA_TYPE_INT32},
{"uint32", IPQOS_DATA_TYPE_UINT32},
{"boolean", IPQOS_DATA_TYPE_BOOLEAN},
{"string", IPQOS_DATA_TYPE_STRING},
{"action", IPQOS_DATA_TYPE_ACTION},
{"address", IPQOS_DATA_TYPE_ADDRESS},
{"port", IPQOS_DATA_TYPE_PORT},
{"protocol", IPQOS_DATA_TYPE_PROTO},
{"enum", IPQOS_DATA_TYPE_ENUM},
{"ifname", IPQOS_DATA_TYPE_IFNAME},
{"mindex", IPQOS_DATA_TYPE_M_INDEX},
{"int_array", IPQOS_DATA_TYPE_INT_ARRAY},
{"user", IPQOS_DATA_TYPE_USER},
{"", 0}
};
/* table of name to id mappings for originator field */
static str_val_t originators[] = {
{IPP_CONFIG_NAME_PERMANENT, IPP_CONFIG_PERMANENT},
{IPP_CONFIG_NAME_IPQOSCONF, IPP_CONFIG_IPQOSCONF},
{IPP_CONFIG_NAME_FTPCL, IPP_CONFIG_FTPCL},
{"", -1}
};
/* current parse line */
static int lineno;
/* verbose output flag */
static int verbose;
/* use syslog for msg reporting flag */
static int use_syslog;
#ifdef _IPQOS_CONF_DEBUG
/*
* flag used to indicate that a rollback should be carried out regardless.
* Only settable during debug.
*/
static int force_rback = 0;
#endif /* _IPQOS_CONF_DEBUG */
/*
* delivers messages to either syslog or stderr, dependant upon the
* the state of the flags use_syslog and verbose. The type
* of the msg as given in msg_type is indicated in the output msg.
*
* valid message types are:
* o MT_ERROR (standard error message)
* o MT_ENOSTR (error message with system error string appended)
* o MT_WARNING (warning message)
* o MT_LOG (logging message)
*
* Log messages only go to syslog. Warning messages only go to stderr
* and only when the verbose flag is set. All other messages go by default
* to the console; to syslog if syslog flag set, and to both if both
* syslog and verbose are set.
*
*/
/*PRINTFLIKE2*/
static void
ipqos_msg(enum msg_type msgt, char *format, ...)
{
va_list ap;
char str_buf[IPQOS_MSG_BUF_SZ];
char fmt_buf[IPQOS_MSG_BUF_SZ];
char *cp;
IPQOSCDBG0(L1, "In ipqos_msg:\n");
va_start(ap, format);
/*
* send msgs to syslog if use_syslog set (except warning msgs),
* or a log msg.
*/
if ((use_syslog && (msgt != MT_WARNING)) || msgt == MT_LOG) {
/* fill in format string */
(void) vsnprintf(str_buf, IPQOS_MSG_BUF_SZ, format, ap);
/*
* print message to syslog with appropriate severity
*/
if (msgt == MT_ERROR) {
syslog(LOG_ERR, str_buf);
} else if (msgt == MT_LOG) {
syslog(LOG_INFO, str_buf);
/*
* for errno message type suffix with %m for syslog to
* interpret.
*/
} else if (msgt == MT_ENOSTR) {
/*
* remove any newline in message parameter.
* syslog will reapply a newline for us later.
*/
if ((cp = strchr(str_buf, '\n')) != NULL)
*cp = '\0';
(void) strlcat(str_buf, ": %m", IPQOS_MSG_BUF_SZ);
syslog(LOG_ERR, str_buf);
}
}
/*
* send msgs to stderr if use_syslog not set (except log msgs), or
* if verbose set.
*/
if ((!use_syslog && (msgt != MT_LOG)) || (verbose)) {
/*
* prefix message with appropriate severity string
*/
if (msgt == MT_ERROR) {
(void) strlcpy(fmt_buf, gettext("Error: "),
IPQOS_MSG_BUF_SZ);
} else if (msgt == MT_WARNING) {
if (!verbose) { /* don't show warn msg if !verbose */
va_end(ap);
return;
}
(void) strlcpy(fmt_buf, gettext("Warning: "),
IPQOS_MSG_BUF_SZ);
} else if (msgt == MT_ENOSTR) {
(void) strlcpy(fmt_buf, gettext("Error: "),
IPQOS_MSG_BUF_SZ);
} else if (msgt == MT_LOG) {
(void) strlcpy(fmt_buf, gettext("Notice: "),
IPQOS_MSG_BUF_SZ);
}
(void) strlcat(fmt_buf, format, IPQOS_MSG_BUF_SZ);
/*
* for errno message type suffix message with errno string
*/
if (msgt == MT_ENOSTR) {
/*
* get rid of any newline in passed message.
* we'll apply another later.
*/
if ((cp = strchr(fmt_buf, '\n')) != NULL)
*cp = '\0';
(void) strlcat(fmt_buf, ": ", IPQOS_MSG_BUF_SZ);
(void) strlcat(fmt_buf, strerror(errno),
IPQOS_MSG_BUF_SZ);
}
/*
* append a newline to message if not one already.
*/
if ((cp = strchr(fmt_buf, '\n')) == NULL)
(void) strlcat(fmt_buf, "\n", IPQOS_MSG_BUF_SZ);
(void) vfprintf(stderr, fmt_buf, ap);
}
va_end(ap);
}
/* **************** kernel filter/class/params manipulation fns *********** */
/*
* modify the kernel parameters of the action action_nm using the nvlist
* parameter nvl and setting the stats according to stats_enable.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
modify_params(
char *action_name,
nvlist_t **nvl,
int module_version,
boolean_t stats_enable)
{
int res;
int created = 0;
IPQOSCDBG1(APPLY, "In modify_params: action: %s\n", action_name);
/* create nvlist if NULL */
if (*nvl == NULL) {
created++;
res = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_alloc");
return (IPQOS_CONF_ERR);
}
}
/* add params modify config type */
res = nvlist_add_byte(*nvl, IPP_CONFIG_TYPE, IPP_SET);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
goto fail;
}
/*
* add module version
*/
if (nvlist_add_uint32(*nvl, IPP_MODULE_VERSION,
(uint32_t)module_version) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
goto fail;
}
/* add stats_enable */
res = nvlist_add_uint32(*nvl, IPP_ACTION_STATS_ENABLE,
(uint32_t)stats_enable);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
goto fail;
}
/* add ipqosconf as originator */
res = add_orig_ipqosconf(*nvl);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* call lib to do modify */
res = ipp_action_modify(action_name, nvl, 0);
if (res != 0) {
/* invalid parameters */
if (errno == EINVAL) {
ipqos_msg(MT_ERROR,
gettext("Invalid parameters for action %s.\n"),
action_name);
} else if (errno == ENOENT) {
ipqos_msg(MT_ERROR,
gettext("Mandatory parameter missing for "
"action %s.\n"), action_name);
} else { /* unexpected error */
ipqos_msg(MT_ERROR, gettext("Failed to modify action "
"%s parameters: %s.\n"), action_name,
strerror(errno));
}
goto fail;
}
return (IPQOS_CONF_SUCCESS);
fail:
if (created && *nvl != NULL) {
nvlist_free(*nvl);
*nvl = NULL;
}
return (IPQOS_CONF_ERR);
}
/*
* add a class to the kernel action action_name called class_name with
* stats set according to stats_enable and the first action set to
* first_action.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
add_class(
char *action_name,
char *class_name,
int module_version,
boolean_t stats_enable,
char *first_action)
{
nvlist_t *nvl;
IPQOSCDBG4(APPLY, "add_class: action: %s, class: %s, "
"first_action: %s, stats: %s\n", action_name, class_name,
first_action, (stats_enable == B_TRUE ? "true" : "false"));
/* create nvlist */
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_alloc");
return (IPQOS_CONF_ERR);
}
/* add 'add class' config type */
if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_ADD_CLASS) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
goto fail;
}
/*
* add module version
*/
if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION,
(uint32_t)module_version) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
goto fail;
}
/* add class name */
if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, class_name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
goto fail;
}
/* add next action */
if (nvlist_add_string(nvl, CLASSIFIER_NEXT_ACTION, first_action) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
goto fail;
}
/* add stats_enable */
if (nvlist_add_uint32(nvl, CLASSIFIER_CLASS_STATS_ENABLE,
(uint32_t)stats_enable) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
goto fail;
}
/* add ipqosconf as originator */
if (add_orig_ipqosconf(nvl) != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* call lib to do modify */
if (ipp_action_modify(action_name, &nvl, 0) != 0) {
/* ipgpc max classes */
if (errno == ENOSPC &&
strcmp(action_name, IPGPC_CLASSIFY) == 0) {
ipqos_msg(MT_ERROR,
gettext("Max number of classes reached in %s.\n"),
IPGPC_NAME);
/* other errors */
} else {
ipqos_msg(MT_ERROR,
gettext("Failed to create class %s in action "
"%s: %s.\n"), class_name, action_name,
strerror(errno));
}
goto fail;
}
return (IPQOS_CONF_SUCCESS);
fail:
nvlist_free(nvl);
return (IPQOS_CONF_ERR);
}
/*
* modify the class in the kernel action action_name called class_name with
* stats set according to stats_enable and the first action set to
* first_action.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
modify_class(
char *action_name,
char *class_name,
int module_version,
boolean_t stats_enable,
char *first_action,
enum ipp_flags flags)
{
nvlist_t *nvl;
IPQOSCDBG5(APPLY, "modify_class: action: %s, class: %s, first: %s, "
"stats: %s, flags: %x\n", action_name, class_name, first_action,
stats_enable == B_TRUE ? "true" : "false", flags);
/* create nvlist */
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_alloc");
return (IPQOS_CONF_ERR);
}
/* add 'modify class' config type */
if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_MODIFY_CLASS) !=
0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
goto fail;
}
/*
* add module version
*/
if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION,
(uint32_t)module_version) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
goto fail;
}
/* add class name */
if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, class_name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
goto fail;
}
/* add next action */
if (nvlist_add_string(nvl, CLASSIFIER_NEXT_ACTION, first_action) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
goto fail;
}
/* add stats enable */
if (nvlist_add_uint32(nvl, CLASSIFIER_CLASS_STATS_ENABLE,
(uint32_t)stats_enable) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
goto fail;
}
/* add originator ipqosconf */
if (add_orig_ipqosconf(nvl) != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* call lib to do modify */
if (ipp_action_modify(action_name, &nvl, flags) != 0) {
/* generic error message */
ipqos_msg(MT_ERROR,
gettext("Modifying class %s in action %s failed: %s.\n"),
class_name, action_name, strerror(errno));
goto fail;
}
return (IPQOS_CONF_SUCCESS);
fail:
nvlist_free(nvl);
return (IPQOS_CONF_ERR);
}
/*
* removes the class class_name from the kernel action action_name. The
* flags argument can currently be set to IPP_ACTION_DESTROY which will
* result in the action this class references being destroyed.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
remove_class(
char *action_name,
char *class_name,
int module_version,
enum ipp_flags flags)
{
nvlist_t *nvl;
IPQOSCDBG3(APPLY, "remove_class: action: %s, class: %s, "
"flags: %x\n", action_name, class_name, flags);
/* allocate nvlist */
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_alloc");
return (IPQOS_CONF_ERR);
}
/* add 'remove class' config type */
if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_REMOVE_CLASS) !=
0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
goto fail;
}
/*
* add module version
*/
if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION,
(uint32_t)module_version) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
goto fail;
}
/* add class name */
if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, class_name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
goto fail;
}
if (ipp_action_modify(action_name, &nvl, flags) != 0) {
/* generic error message */
ipqos_msg(MT_ERROR,
gettext("Removing class %s in action %s failed: %s.\n"),
class_name, action_name, strerror(errno));
goto fail;
}
return (IPQOS_CONF_SUCCESS);
fail:
nvlist_free(nvl);
return (IPQOS_CONF_ERR);
}
/*
* add the filter flt to the kernel action named action_name.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
add_filter(
char *action_name,
ipqos_conf_filter_t *flt,
int module_version)
{
nvlist_t *nvl = flt->nvlist;
char ipvsbuf[IPQOS_INT_STR_LEN];
IPQOSCDBG4(APPLY, "add_filter: action: %s, filter: %s, "
"instance: %d, class: %s\n", action_name, flt->name,
flt->instance, flt->class_name);
/* add 'add filter' config type to filter nvlist */
if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_ADD_FILTER) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
return (IPQOS_CONF_ERR);
}
/*
* add module version
*/
if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION,
(uint32_t)module_version) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
return (IPQOS_CONF_ERR);
}
/* add filter name to nvlist */
if (nvlist_add_string(nvl, CLASSIFIER_FILTER_NAME, flt->name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
/* add class name to nvlist */
if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, flt->class_name) !=
0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
/* add ipqosconf as originator to nvlist */
if (add_orig_ipqosconf(nvl) != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* add ipgpc specific nv entrys */
if (strcmp(action_name, IPGPC_CLASSIFY) == 0) {
/* add src and dst nodes to nvlist if present */
if (flt->src_nd_name != NULL &&
nvlist_add_string(nvl, IPGPC_SADDR_HOSTNAME,
flt->src_nd_name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
if (flt->dst_nd_name != NULL &&
nvlist_add_string(nvl, IPGPC_DADDR_HOSTNAME,
flt->dst_nd_name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
/*
* add ip_version to private list element if present.
* NOTE: this value is of only real use to ipqosconf so
* it is placed in this opaque private field.
*/
if (flt->ip_versions != 0) {
(void) sprintf(ipvsbuf, "%d", flt->ip_versions);
if (nvlist_add_string(nvl, IPGPC_FILTER_PRIVATE,
ipvsbuf) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
}
/* add filter instance if present */
if (nvlist_add_int32(nvl, IPGPC_FILTER_INSTANCE,
flt->instance) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_int32");
return (IPQOS_CONF_ERR);
}
}
if (ipp_action_modify(action_name, &flt->nvlist, 0) != 0) {
/* invalid parameters */
if (errno == EINVAL) {
ipqos_msg(MT_ERROR,
gettext("Invalid/missing parameters for filter "
"%s in action %s.\n"), flt->name, action_name);
/* max ipgpc filters/classes */
} else if (errno == ENOSPC &&
strcmp(action_name, IPGPC_CLASSIFY) == 0) {
ipqos_msg(MT_ERROR, gettext("Max number of filters "
"reached in action %s.\n"), IPGPC_NAME);
/* anything other errnos */
} else {
ipqos_msg(MT_ERROR,
gettext("Failed to create filter %s in action "
"%s: %s.\n"), flt->name, action_name,
strerror(errno));
}
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* modify the filter flt in the kernel action named action_name.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
modify_filter(
char *action_name,
ipqos_conf_filter_t *flt,
int module_version)
{
nvlist_t *nvl = flt->nvlist;
char ipvsbuf[IPQOS_INT_STR_LEN];
IPQOSCDBG4(APPLY, "modify_filter: action: %s, filter: %s, "
"instance: %d, class: %s\n", action_name, flt->name,
flt->instance, flt->class_name);
/* show src address and dst address if present */
#ifdef _IPQOS_CONF_DEBUG
if (ipqosconf_dbg_flgs & APPLY) {
uint_t tmp;
in6_addr_t *add;
char st[100];
if (nvlist_lookup_uint32_array(nvl, IPGPC_SADDR,
(uint32_t **)&add, &tmp) == 0) {
(void) fprintf(stderr, "saddr: %s\n",
inet_ntop(AF_INET6, add, st, 100));
}
if (nvlist_lookup_uint32_array(nvl, IPGPC_DADDR,
(uint32_t **)&add, &tmp) == 0) {
(void) fprintf(stderr, "daddr: %s\n",
inet_ntop(AF_INET6, add, st, 100));
}
}
#endif /* _IPQOS_CONF_DEBUG */
/* add 'modify filter' config type to filters nvlist */
if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE,
CLASSIFIER_MODIFY_FILTER) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
return (IPQOS_CONF_ERR);
}
/*
* add module version
*/
if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION,
(uint32_t)module_version) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
return (IPQOS_CONF_ERR);
}
/* add filter name to nvlist */
if (nvlist_add_string(nvl, CLASSIFIER_FILTER_NAME, flt->name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
/* add class name to nvlist */
if (nvlist_add_string(nvl, CLASSIFIER_CLASS_NAME, flt->class_name) !=
0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
/* add originator ipqosconf to nvlist */
if (add_orig_ipqosconf(nvl) != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* add ipgpc specific nvpairs */
if (strcmp(action_name, IPGPC_CLASSIFY) == 0) {
/* add src and dst nodes to nvlist if present */
if (flt->src_nd_name &&
nvlist_add_string(nvl, IPGPC_SADDR_HOSTNAME,
flt->src_nd_name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
if (flt->dst_nd_name &&
nvlist_add_string(nvl, IPGPC_DADDR_HOSTNAME,
flt->dst_nd_name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
/*
* add ip_version to private list element if present.
* NOTE: this value is of only real use to ipqosconf so
* it is placed in this opaque private field.
*/
if (flt->ip_versions != 0) {
(void) sprintf(ipvsbuf, "%d", flt->ip_versions);
if (nvlist_add_string(nvl, IPGPC_FILTER_PRIVATE,
ipvsbuf) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
}
/* add filter instance if present */
if (nvlist_add_int32(nvl, IPGPC_FILTER_INSTANCE,
flt->instance) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_int32");
return (IPQOS_CONF_ERR);
}
}
if (ipp_action_modify(action_name, &flt->nvlist, 0) != 0) {
/* invalid parameters */
if (errno == EINVAL) {
ipqos_msg(MT_ERROR, gettext("Missing/Invalid "
"parameter for filter %s in action %s.\n"),
flt->name, action_name);
/* any other errnos */
} else {
ipqos_msg(MT_ERROR,
gettext("Failed to modify filter %s in action %s: "
"%s.\n"), flt->name, action_name, strerror(errno));
}
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* remove the filter named filter_name instance number instance from the
* kernel action action_name.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
remove_filter(
char *action_name,
char *filter_name,
int instance,
int module_version)
{
nvlist_t *nvl;
IPQOSCDBG2(APPLY, "remove_filter: action: %s, filter: %s\n",
action_name, filter_name);
/* create nvlist */
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_alloc");
return (IPQOS_CONF_ERR);
}
/* add 'remove filter' config type to list */
if (nvlist_add_byte(nvl, IPP_CONFIG_TYPE, CLASSIFIER_REMOVE_FILTER)
!= 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
return (IPQOS_CONF_ERR);
}
/*
* add module version
*/
if (nvlist_add_uint32(nvl, IPP_MODULE_VERSION,
(uint32_t)module_version) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
return (IPQOS_CONF_ERR);
}
/* add filter name to list */
if (nvlist_add_string(nvl, CLASSIFIER_FILTER_NAME, filter_name) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
return (IPQOS_CONF_ERR);
}
/* add instance number if part of multi-instance filter */
if (instance != -1 && nvlist_add_int32(nvl, IPGPC_FILTER_INSTANCE,
instance) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_int32");
return (IPQOS_CONF_ERR);
}
/* call into lib to remove */
if (ipp_action_modify(action_name, &nvl, 0) != 0) {
/* generic error message */
ipqos_msg(MT_ERROR,
gettext("Removing filter %s in action %s failed: %s.\n"),
filter_name, action_name, strerror(errno));
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/* ******************************************************************* */
/*
* add originator nvpair set to ipqosconf to nvl.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
add_orig_ipqosconf(nvlist_t *nvl)
{
if (nvlist_add_uint32(nvl, IPP_CONFIG_ORIGINATOR,
IPP_CONFIG_IPQOSCONF) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32: originator:");
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/* ************************* differencing functions ************************ */
/*
* compares the contents of arrays array1 and array2, both of size size, and
* returns B_TRUE or B_FALSE if they're equal or not respectively.
* RETURNS: B_TRUE if equal, else B_FALSE.
*/
static boolean_t
arrays_equal(
int array1[],
int array2[],
uint32_t size)
{
int x;
for (x = 0; x < size; x++) {
if (array1[x] != array2[x])
return (B_FALSE);
}
return (B_TRUE);
}
/*
* difference class old against class new. It marks the new class as
* modified if it is different.
* RETURNS: IPQOS_CONF_SUCCESS.
*/
static int
diffclass(
ipqos_conf_class_t *old,
ipqos_conf_class_t *new)
{
IPQOSCDBG0(L0, "In diffclass:\n");
/* two different spec'd actions */
if (strcmp(old->alist->name, new->alist->name) != 0) {
IPQOSCDBG1(DIFF, "marking class %s as modified\n", new->name);
new->modified = B_TRUE;
return (IPQOS_CONF_SUCCESS);
}
/* different stats values */
if (old->stats_enable != new->stats_enable) {
IPQOSCDBG1(DIFF, "marking class %s as modified\n", new->name);
new->modified = B_TRUE;
return (IPQOS_CONF_SUCCESS);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* difference params set old against params set new of module module_name. It
* marks the new params as modified if different.
* RETURNS: if error IPQOS_CONF_ERR, else IPQOS_CONF_SUCCESS.
*/
static int
diffparams(
ipqos_conf_params_t *old,
ipqos_conf_params_t *new,
char *module_name)
{
int diff;
int res;
IPQOSCDBG0(L0, "In diffparams\n");
/* diff stats */
if (old->stats_enable != new->stats_enable) {
new->modified = B_TRUE;
return (IPQOS_CONF_SUCCESS);
}
/* diff module specific params */
res = diffnvlists(old->nvlist, new->nvlist, module_name, &diff,
PL_PARAMS);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
if (diff) {
new->modified = B_TRUE;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* differences filter old against filter new of module module_name. It marks
* filter new as different if so.
* RETURNS: if error IPQOS_CONF_ERR, else IPQOS_CONF_SUCCESS.
*/
static int
difffilter(
ipqos_conf_filter_t *old,
ipqos_conf_filter_t *new,
char *module_name)
{
int res;
int diff;
IPQOSCDBG0(L0, "In difffilter\n");
/* compare class name */
if (strcmp(old->class_name, new->class_name) != 0) {
IPQOSCDBG1(DIFF, "Marking filter %s as modified\n", new->name);
new->modified = B_TRUE;
return (IPQOS_CONF_SUCCESS);
}
/* compare module specific params */
res = diffnvlists(old->nvlist, new->nvlist, module_name, &diff,
PL_FILTER);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
if (diff) {
IPQOSCDBG1(DIFF, "Marking filter %s as modified\n", new->name);
new->modified = B_TRUE;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* mark all the filters and classes in parameter action either
* for deletion (if they are ipqosconf originated) or for modification.
*/
static void
mark_classes_filters_del(ipqos_conf_action_t *action)
{
ipqos_conf_filter_t *flt;
ipqos_conf_class_t *cls;
IPQOSCDBG1(L1, "In mark_classes_filters_del: action: %s\n",
action->name);
/* mark all non-permanent filters for del and permanent to modify */
for (flt = action->filters; flt; flt = flt->next) {
if (flt->originator == IPP_CONFIG_PERMANENT) {
IPQOSCDBG1(DIFF, "Marking prm filter %s as modified.\n",
flt->name);
flt->modified = B_TRUE;
} else {
IPQOSCDBG1(DIFF, "Marking filter %s as del.\n",
flt->name);
flt->todel = B_TRUE;
}
}
/* mark all non-permanent classes for del and permanent to modify */
for (cls = action->classes; cls; cls = cls->next) {
if (cls->originator == IPP_CONFIG_PERMANENT) {
IPQOSCDBG1(DIFF, "Marking prm class %s as modified.\n",
cls->name);
cls->modified = B_TRUE;
} else {
IPQOSCDBG1(DIFF, "Marking class %s as del.\n",
cls->name);
cls->todel = B_TRUE;
}
}
}
/*
* mark all classes and filters either new (non-permanent) or modified.
*/
static void
mark_classes_filters_new(ipqos_conf_action_t *action)
{
ipqos_conf_filter_t *flt;
ipqos_conf_class_t *cls;
IPQOSCDBG1(L1, "In mark_classes_filters_new: action: %s\n",
action->name);
/* mark all permanent filters as modified and all others new */
for (flt = action->filters; flt; flt = flt->next) {
if (flt->originator == IPP_CONFIG_PERMANENT) {
IPQOSCDBG1(DIFF, "Marking prm filter %s as modified.\n",
flt->name);
flt->modified = B_TRUE;
action->modified = B_TRUE;
} else {
IPQOSCDBG1(DIFF, "Marking filter %s as new.\n",
flt->name);
flt->new = B_TRUE;
}
}
/* mark all permanent classes as modified and all others new */
for (cls = action->classes; cls; cls = cls->next) {
if (cls->originator == IPP_CONFIG_PERMANENT) {
IPQOSCDBG1(DIFF, "Marking prm class %s as modified.\n",
cls->name);
cls->modified = B_TRUE;
action->modified = B_TRUE;
} else {
IPQOSCDBG1(DIFF, "Marking class %s as new.\n",
cls->name);
cls->new = B_TRUE;
}
}
}
/*
* Marks all the actions and their constituent elements in conf
* as new.
*/
static void
mark_config_new(
ipqos_conf_action_t *conf)
{
while (conf != NULL) {
IPQOSCDBG1(DIFF, "Marking action %s as new\n", conf->name);
mark_classes_filters_new(conf);
conf->new = B_TRUE;
conf->visited = 0;
conf = conf->next;
}
}
/*
* differences the configuration in new against old marking the actions
* and their contents appropriately.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
diffconf(
ipqos_conf_action_t *old,
ipqos_conf_action_t *new)
{
int res;
ipqos_conf_action_t *act;
ipqos_conf_action_t *tmp;
IPQOSCDBG0((L0 | DIFF), "In diffconf\n");
/* check the new actions against the old */
for (act = new; act; act = act->next) {
/* if action not in old mark it and it's contents as new */
if ((tmp = actionexist(act->name, old)) == NULL) {
IPQOSCDBG1(DIFF, "marking act %s as new\n", act->name);
act->new = B_TRUE;
mark_classes_filters_new(act);
continue;
}
/* if action in old diff old against new */
res = diffaction(tmp, act);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
/*
* mark actions, and their contents, in old but not new that were
* created by us for del.
*/
for (act = old; act; act = act->next) {
if (act->params->originator == IPP_CONFIG_IPQOSCONF &&
actionexist(act->name, new) == NULL) {
IPQOSCDBG1(DIFF, "marking act %s for del\n", act->name);
act->todel = B_TRUE;
mark_classes_filters_del(act);
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* differences action old against action new, comparing its classes, filters
* and parameters. If it is different the new action is marked as modified
* and it's different sub-objects are also marked approriately.
* RETURNS: IPQOS_CONF_ERR if error, else IPQOS_CONF_SUCCESS.
*/
static int
diffaction(
ipqos_conf_action_t *old,
ipqos_conf_action_t *new)
{
int res;
IPQOSCDBG0(L0, "In diffaction\n");
/* compare and mark classes */
res = diffclasses(old, new);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/* compare and mark filters */
res = difffilters(old, new);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/* compare and mark parameters */
res = diffparams(old->params, new->params, old->module);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/* mark action as modified if params are */
if (new->params->modified == B_TRUE) {
IPQOSCDBG1(DIFF, "Marking params for action %s modified\n",
new->name);
new->modified = B_TRUE;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* differences the set of classes in new against those in old, marking any
* that are new/modified, approriately in the new class, and any removed
* in the old class appropriately. Also marks the action which has had an
* object within marked, as modified.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
diffclasses(
ipqos_conf_action_t *old,
ipqos_conf_action_t *new)
{
ipqos_conf_class_t *cls;
ipqos_conf_class_t *tmpc;
ipqos_conf_class_t *ncls;
int res;
/* loop through old classes checking for classes not present in new */
for (cls = old->classes; cls; cls = cls->next) {
if (classexist(cls->name, new->classes) == NULL) {
/* if we created original class mark for deletion */
if (cls->originator == IPP_CONFIG_IPQOSCONF) {
IPQOSCDBG1(DIFF, "marking class %s for del\n",
cls->name);
cls->todel = B_TRUE;
/* mark old action */
old->modified = B_TRUE;
/*
* if permanent class and next action created by us
* copy it, set it's next action to continue and
* add it to new action. This will cause the class
* to be marked as and modified. This returns the class
* to an assumed default state and prevents the
* case where the class is pointing at an action
* we want to remove and therefore couldn't without
* this forced modify.
*/
} else if (cls->originator == IPP_CONFIG_PERMANENT &&
cls->alist->action && /* not virtual action */
cls->alist->action->params->originator ==
IPP_CONFIG_IPQOSCONF) {
/* copy class */
res = dup_class(cls, &ncls);
if (res != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* set next action to continue */
(void) strcpy(ncls->alist->name,
IPP_ANAME_CONT);
/* add to news classes to be diffed below */
ADD_TO_LIST(&new->classes, ncls);
}
}
}
/* loop through new classes checking for new / modified classes */
for (cls = new->classes; cls; cls = cls->next) {
/* new ipqosconf class */
if ((tmpc = classexist(cls->name, old->classes)) == NULL ||
(tmpc->originator != IPP_CONFIG_IPQOSCONF &&
tmpc->originator != IPP_CONFIG_PERMANENT)) {
IPQOSCDBG1(DIFF, "marking class %s new\n",
cls->name);
cls->new = B_TRUE;
new->modified = B_TRUE; /* mark new action */
continue;
/* existing ipqosconf/perm class */
} else {
res = diffclass(tmpc, cls);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
if (cls->modified == B_TRUE) {
new->modified = B_TRUE;
}
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* differences the set of filters in new against those in old, marking any
* that are new/modified, approriately in the new filter/s, and any removed
* in the old filter appropriately. Also marks the action which has had an
* object within marked, as modified.
* RETURNS: IPQOS_CONF_SUCCESS (we return an int for symmetry with diffclasses
* and difffparams).
*/
static int
difffilters(
ipqos_conf_action_t *old,
ipqos_conf_action_t *new)
{
ipqos_conf_filter_t *flt;
ipqos_conf_filter_t *tmpf;
int maxi;
int newi;
int res;
/* check for new/modified filters */
for (flt = new->filters; flt; flt = flt->next) {
/* new ipqosconf filter */
if ((tmpf = filterexist(flt->name, -1, old->filters)) == NULL) {
/* mark all instances of this filter as new */
for (;;) {
IPQOSCDBG1(DIFF, "Marking filter %s as "
"new\n", flt->name);
flt->new = B_TRUE;
if (flt->next == NULL ||
strcmp(flt->next->name, flt->name) != 0) {
break;
}
flt = flt->next;
}
new->modified = B_TRUE; /* mark new action */
/* ipqosconf/permanent filter existed */
} else {
/*
* if ip node name force filter refresh - ie. mark
* all old filter instances as todel and all new new.
*/
if (tmpf->src_nd_name || tmpf->dst_nd_name ||
flt->src_nd_name || flt->dst_nd_name) {
/* init max previous filter instance */
maxi = tmpf->instance;
/* mark old instances for deletion */
do {
IPQOSCDBG2(DIFF, "Marking filter "
"%s, instance %d for del\n",
tmpf->name, tmpf->instance);
tmpf->todel = B_TRUE;
/*
* check and update previous instance
* max.
*/
if (tmpf->instance > maxi) {
maxi = tmpf->instance;
}
tmpf = tmpf->next;
} while (tmpf != NULL &&
strcmp(tmpf->name, flt->name) == 0);
/*
* use the max previous instance + 1 for
* the start of the new instance numbers.
*/
newi = (uint32_t)++maxi % INT_MAX;
/*
* mark new instances for addition and
* give new instance number.
*/
for (;;) {
IPQOSCDBG2(DIFF, "Marking filter "
"%s, instance %d as new\n",
flt->name, newi);
flt->new = B_TRUE;
flt->instance = newi++;
if (flt->next == NULL ||
strcmp(flt->next->name,
flt->name) != 0) {
break;
}
flt = flt->next;
}
new->modified = B_TRUE; /* mark new action */
/* mark old action */
old->modified = B_TRUE;
/* non-node name filter */
} else {
/* compare and mark as modified if diff */
res = difffilter(tmpf, flt, new->module);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
if (flt->modified == B_TRUE) {
/* mark action if diff */
new->modified = B_TRUE;
}
}
}
}
/*
* Check for deleted ipqosconf created filters and mark
* any found for deletion.
* For non-ipqosconf generated filters, including permanent
* ones (none of these exist at the moment) we just leave
* the filter unmarked.
*/
for (flt = old->filters; flt; flt = flt->next) {
if (flt->originator == IPP_CONFIG_IPQOSCONF &&
filterexist(flt->name, -1, new->filters) == NULL) {
/* mark all old instances for deletions */
for (;;) {
IPQOSCDBG2(DIFF, "marking flt %s, inst %d "
"for del\n", flt->name, flt->instance);
flt->todel = B_TRUE;
old->modified = B_TRUE; /* mark old action */
if (flt->next == NULL ||
strcmp(flt->next->name, flt->name) != 0) {
break;
}
flt = flt->next;
}
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* differences the elements of nvlists old and new using the types file
* for module name to interpret the element types. It sets pdiff to either
* 0 or 1 if they are the same or different respectively.
* RETURNS: IPQOS_CONF_ERR if any errors, else IPQOS_CONF_SUCCESS.
*/
static int
diffnvlists(
nvlist_t *old,
nvlist_t *new,
char *module_name,
int *pdiff,
place_t place)
{
int first_pass = 1;
nvlist_t *tmp;
int res;
nvpair_t *nvp;
FILE *tfp;
str_val_nd_t *enum_nvs;
char dfltst[IPQOS_VALST_MAXLEN+1] = "";
char *lo;
ipqos_nvtype_t type;
char *nme;
int diff;
int openerr;
IPQOSCDBG0(L0, "In diffnvlists\n");
/* open stream to types file */
tfp = validmod(module_name, &openerr);
if (tfp == NULL) {
if (openerr) {
ipqos_msg(MT_ENOSTR, "fopen");
}
return (IPQOS_CONF_ERR);
}
start:
/*
* loop through each of the elements of the new list comparing
* it with the old one if present. If the old one isn't present
* then it is compared with the default value for that type (if
* set). Any time the values are determined to be different
* or the default value is to be used but isn't present the diff
* param is set to 1 and we return.
*
* If the loop runs its course then the new and old nvlists are
* reversed and the loop is entered for a second time.
*/
nvp = nvlist_next_nvpair(new, NULL);
while (nvp != NULL) {
/* get name */
nme = nvpair_name(nvp);
/*
* get type.
*/
place = PL_ANY;
res = readtype(tfp, module_name, SHORT_NAME(nme), &type,
&enum_nvs, dfltst, B_TRUE, &place);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/* init diff to 1 */
diff = 1;
switch (type) {
/* interface name */
case IPQOS_DATA_TYPE_IFINDEX: {
uint32_t ifidx;
uint32_t oifidx;
/* get new value */
(void) nvpair_value_uint32(nvp, &ifidx);
/* compare against old if present */
res = nvlist_lookup_uint32(old, nme, &oifidx);
if (res == 0) {
/* diff values */
diff = (ifidx != oifidx);
/* not in old so see if new value is default */
} else {
diff = (ifidx != 0);
}
break;
}
/* protocol */
case IPQOS_DATA_TYPE_PROTO: {
uchar_t proto;
uchar_t oproto;
(void) nvpair_value_byte(nvp, &proto);
res = nvlist_lookup_byte(old, nme, &oproto);
if (res == 0) {
diff = (proto != oproto);
} else {
diff = (proto != 0);
}
break;
}
/* port */
case IPQOS_DATA_TYPE_PORT: {
uint16_t port;
uint16_t oport;
(void) nvpair_value_uint16(nvp, &port);
res = nvlist_lookup_uint16(old, nme, &oport);
if (res == 0) {
diff = (port != oport);
} else {
diff = (port != 0);
}
break;
}
/* action name / string */
case IPQOS_DATA_TYPE_ACTION:
case IPQOS_DATA_TYPE_STRING: {
char *str;
char *ostr;
(void) nvpair_value_string(nvp, &str);
res = nvlist_lookup_string(old, nme, &ostr);
if (res == 0) {
diff = strcmp(str, ostr);
} else if (*dfltst) {
diff = strcmp(str, dfltst);
}
break;
}
/* address mask / address */
case IPQOS_DATA_TYPE_ADDRESS_MASK:
case IPQOS_DATA_TYPE_ADDRESS: {
in6_addr_t *in6;
in6_addr_t *oin6;
uint_t x;
/*
* all addresses are stored as v6 addresses, so
* a uint32_t[4] array is used.
*/
/* lookup new value */
(void) nvpair_value_uint32_array(nvp,
(uint32_t **)&in6, &x);
/* see if there's an old value and diff it */
res = nvlist_lookup_uint32_array(old, nme,
(uint32_t **)&oin6, &x);
if (res == 0) {
/* diff each of the 16 v6 address bytes */
for (x = 0; x < 16; x++) {
if (in6->s6_addr[x] !=
oin6->s6_addr[x]) {
diff++;
break;
}
}
}
break;
}
/* boolean */
case IPQOS_DATA_TYPE_BOOLEAN: {
boolean_t bl;
boolean_t obl;
(void) nvpair_value_uint32(nvp, (uint32_t *)&bl);
/* see if there's an old value and diff it */
res = nvlist_lookup_uint32(old, nme, (uint32_t *)&obl);
if (res == 0) {
diff = (bl != obl);
/* compare against default if present */
} else if (*dfltst) {
res = readbool(dfltst, &obl);
if (res == IPQOS_CONF_SUCCESS) {
diff = (bl != obl);
}
}
break;
}
/* uint 8 */
case IPQOS_DATA_TYPE_UINT8: {
uint8_t u8;
uint8_t ou8;
(void) nvpair_value_byte(nvp, (uchar_t *)&u8);
res = nvlist_lookup_byte(old, nme, (uchar_t *)&ou8);
if (res == 0) {
diff = (u8 != ou8);
} else if (*dfltst) {
res = readuint8(dfltst, &ou8, &lo);
if (res == IPQOS_CONF_SUCCESS) {
diff = (u8 != ou8);
}
}
break;
}
/* int 16 */
case IPQOS_DATA_TYPE_INT16: {
int16_t i16;
int16_t oi16;
(void) nvpair_value_int16(nvp, &i16);
res = nvlist_lookup_int16(old, nme, &oi16);
if (res == 0) {
diff = (i16 != oi16);
} else if (*dfltst) {
res = readint16(dfltst, &oi16, &lo);
if (res == IPQOS_CONF_SUCCESS) {
diff = (i16 != oi16);
}
}
break;
}
/* uint16 */
case IPQOS_DATA_TYPE_UINT16: {
uint16_t ui16;
uint16_t oui16;
(void) nvpair_value_uint16(nvp, &ui16);
res = nvlist_lookup_uint16(old, nme, &oui16);
if (res == 0) {
diff = (ui16 != oui16);
} else if (*dfltst) {
res = readuint16(dfltst, &oui16, &lo);
if (res == IPQOS_CONF_SUCCESS) {
diff = (ui16 != oui16);
}
}
break;
}
/*
* int32 and user.
* Since user uids are stored in an int32 nvpair we can use
* the same comparison code.
*/
case IPQOS_DATA_TYPE_USER:
case IPQOS_DATA_TYPE_INT32: {
int32_t i32;
int32_t oi32;
(void) nvpair_value_int32(nvp, &i32);
res = nvlist_lookup_int32(old, nme, &oi32);
if (res == 0) {
diff = (i32 != oi32);
} else if (*dfltst) {
res = readint32(dfltst, &oi32, &lo);
if (res == IPQOS_CONF_SUCCESS) {
diff = (i32 != oi32);
}
}
break;
}
/* uint32 */
case IPQOS_DATA_TYPE_UINT32: {
uint32_t ui32;
uint32_t oui32;
(void) nvpair_value_uint32(nvp, &ui32);
res = nvlist_lookup_uint32(old, nme, &oui32);
if (res == 0) {
diff = (ui32 != oui32);
} else if (*dfltst) {
res = readuint32(dfltst, &oui32, &lo);
if (res == IPQOS_CONF_SUCCESS) {
diff = (ui32 != oui32);
}
}
break;
}
/* enumeration */
case IPQOS_DATA_TYPE_ENUM: {
uint32_t eval;
uint32_t oeval;
(void) nvpair_value_uint32(nvp, &eval);
res = nvlist_lookup_uint32(old, nme, &oeval);
if (res == 0) {
diff = (eval != oeval);
} else if (*dfltst) {
res = readuint32(dfltst, &oeval, &lo);
if (res == IPQOS_CONF_SUCCESS) {
diff = (eval != oeval);
}
}
break;
}
case IPQOS_DATA_TYPE_M_INDEX: {
uint8_t idx, oidx;
(void) nvpair_value_byte(nvp, &idx);
res = nvlist_lookup_byte(old, nme, &oidx);
if (res == 0)
diff = (idx != oidx);
break;
}
case IPQOS_DATA_TYPE_INT_ARRAY: {
int *oarr, *arr;
uint32_t osize, size;
(void) nvpair_value_int32_array(nvp, &arr, &size);
res = nvlist_lookup_int32_array(old, nme, &oarr,
&osize);
if (res == 0)
diff = (arrays_equal(arr, oarr, size) ==
B_FALSE);
break;
}
#ifdef _IPQOS_CONF_DEBUG
default: {
/* shouldn't get here as all types should be covered */
assert(1);
}
#endif
} /* switch */
if (diff != 0) {
IPQOSCDBG1(DIFF, "parameter %s different\n", nme);
*pdiff = 1;
(void) fclose(tfp);
return (IPQOS_CONF_SUCCESS);
}
nvp = nvlist_next_nvpair(new, nvp);
}
/* now compare all the stuff in the second list with the first */
if (first_pass) {
tmp = old;
old = new;
new = tmp;
first_pass = 0;
goto start;
}
(void) fclose(tfp);
*pdiff = 0;
return (IPQOS_CONF_SUCCESS);
}
/* ************************** difference application *********************** */
/*
* causes all items marked as requiring change in actions and old_actions
* to have the change applied.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
applydiff(
ipqos_conf_action_t *actions,
ipqos_conf_action_t *old_actions)
{
int res;
IPQOSCDBG0(L1, "In applydiff:\n");
/* add each item marked as new */
res = add_items(actions, B_FALSE);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/* modify items marked for modification */
res = modify_items(actions);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/* delete items marked for deletion */
res = remove_items(old_actions, B_FALSE);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
return (IPQOS_CONF_SUCCESS);
}
static int
add_items(
ipqos_conf_action_t *actions,
boolean_t rem_undo)
{
int res;
ipqos_conf_action_t *act;
IPQOSCDBG1(L1, "In add_items, rem_undo: %u\n", rem_undo);
/*
* we need to create ipgpc action before any others as some actions
* such as ftpcl which make calls to it depend on it being there on
* their creation.
*/
act = actionexist(IPGPC_CLASSIFY, actions);
if (act &&
(rem_undo == B_FALSE && act->new == B_TRUE ||
rem_undo == B_TRUE && act->deleted == B_TRUE)) {
res = add_action(act);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
/*
* loop though action list and add any actions marked as
* new/modified action and apply any additions there, then return.
*/
for (act = actions; act; act = act->next) {
res = add_item(act, rem_undo);
if (res != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
*
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
add_item(
ipqos_conf_action_t *actions,
boolean_t rem_undo)
{
ipqos_conf_action_t *act = actions;
int res;
ipqos_conf_class_t *cls;
ipqos_conf_act_ref_t *pact;
IPQOSCDBG2(L1, "In add_item: action: %s, rem_undo: %u\n",
actions->name, rem_undo);
/* if already visited return immediately */
if (act->visited == ADD_VISITED) {
IPQOSCDBG0(L1, "Early exit due to visited\n");
return (IPQOS_CONF_SUCCESS);
}
act->visited = ADD_VISITED;
/* recurse to last action in tree */
for (cls = act->classes; cls; cls = cls->next) {
/* if not virtual action */
if (cls->alist->action) {
res = add_item(cls->alist->action, rem_undo);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
}
for (pact = act->params->actions; pact; pact = pact->next) {
/* if not virtual */
if (pact->action) {
res = add_item(pact->action, rem_undo);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
}
/* if action marked as new and not ipgpc, create */
if (((rem_undo == B_FALSE && act->new == B_TRUE) ||
(rem_undo == B_TRUE && act->deleted == B_TRUE)) &&
strcmp(act->name, IPGPC_CLASSIFY) != 0) {
res = add_action(act);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
/* add any classes and filters marked as new */
if (add_classes(act->classes, act->name, act->module_version,
rem_undo) != IPQOS_CONF_SUCCESS ||
add_filters(act->filters, act->name, act->module_version,
rem_undo) != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Uses the contents of acts params nvlist and adds an originator
* element set to ipqosconf and the stats parameter. This list
* is then used as the parameter to a call to ipp_action_create to create
* this action in the kernel.
* RETURNS: IPQOS_CONF_ERR on err, else IPQOS_CONF_SUCCESS.
*/
static int
add_action(ipqos_conf_action_t *act)
{
int res;
nvlist_t **nvl;
IPQOSCDBG2(APPLY, "add_action: action: %s, module: %s\n", act->name,
act->module);
nvl = &act->params->nvlist;
/* alloc params nvlist if not already one */
if (*nvl == NULL) {
res = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_alloc");
return (IPQOS_CONF_ERR);
}
}
/*
* add module version
*/
if (nvlist_add_uint32(*nvl, IPP_MODULE_VERSION,
(uint32_t)act->module_version) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32");
return (IPQOS_CONF_ERR);
}
/* add action stats */
if (nvlist_add_uint32(*nvl, IPP_ACTION_STATS_ENABLE,
(uint32_t)act->params->stats_enable) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32: action stats");
return (IPQOS_CONF_ERR);
}
/* add ipqosconf originator id */
if (add_orig_ipqosconf(*nvl) != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* call into lib to create action */
res = ipp_action_create(act->module, act->name, nvl, 0);
if (res != 0) {
IPQOSCDBG2(APPLY, "Create action %s, module %s failed\n",
act->name, act->module);
/* invalid params */
if (errno == EINVAL) {
ipqos_msg(MT_ERROR,
gettext("Invalid Parameters for action %s.\n"),
act->name);
} else if (errno == ENOENT) {
ipqos_msg(MT_ERROR,
gettext("Missing required parameter for action "
"%s.\n"), act->name);
} else { /* unexpected error */
ipqos_msg(MT_ERROR, gettext("Failed to create action "
"%s: %s.\n"), act->name, strerror(errno));
}
return (IPQOS_CONF_ERR);
}
/* mark action as created */
act->cr_mod = B_TRUE;
return (IPQOS_CONF_SUCCESS);
}
/*
* for each of the filters in parameter filters if rem_undo is false and
* the filter is marked as new or if rem_undo is true and the filter is
* marked as deleted then add the filter to the kernel action named by action
* and if successful mark as created.
* RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS.
*/
static int
add_filters(
ipqos_conf_filter_t *filters,
char *action,
int module_version,
boolean_t rem_undo)
{
ipqos_conf_filter_t *flt;
IPQOSCDBG0(L1, "In add_filters\n");
/* loop through filters in filters param */
for (flt = filters; flt; flt = flt->next) {
/*
* skip filter if in normal mode and not new filter or
* if doing rollback and filter wasn't previously deleted.
*/
if ((rem_undo == B_FALSE && flt->new == B_FALSE) ||
(rem_undo == B_TRUE && flt->deleted == B_FALSE)) {
continue;
}
/* add filter to action */
if (add_filter(action, flt, module_version) !=
IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* mark as created */
flt->cr_mod = B_TRUE;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* for each of the classes in parameter classes if rem_undo is false and
* the class is marked as new or if rem_undo is true and the class is
* marked as deleted then add the class to the kernel action named by action
* and if successful mark as created.
* RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS.
*/
int
add_classes(
ipqos_conf_class_t *classes,
char *action,
int module_version,
boolean_t rem_undo) {
int res;
ipqos_conf_class_t *cls;
IPQOSCDBG0(L1, "In add_classes\n");
/* for each class */
for (cls = classes; cls; cls = cls->next) {
/*
* skip class if in normal mode and not new class or
* if doing rollback and class wasn't deleted.
*/
if ((rem_undo == B_FALSE && cls->new == B_FALSE) ||
(rem_undo == B_TRUE && cls->deleted == B_FALSE)) {
continue;
}
/* add class to action */
res = add_class(action, cls->name, module_version,
cls->stats_enable, cls->alist->name);
if (res != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* mark class as created */
cls->cr_mod = B_TRUE;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* For each of the actions in actions remove the action if marked as
* such or remove any objects within marked as such.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
remove_items(
ipqos_conf_action_t *actions,
boolean_t add_undo)
{
int res;
ipqos_conf_action_t *act;
IPQOSCDBG1(L0, "In remove_items, add_undo: %u\n", add_undo);
/*
* loop through actions removing any actions, or action contents
* that are marked as such.
*/
for (act = actions; act; act = act->next) {
res = remove_item(act, add_undo);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Deletes this action if marked for deletion or any of it's contents marked
* for deletion. If the action is marked for deletion any actions referencing
* this action are destroyed first if marked or have their contents destroyed
* if marked. This is recursive.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
remove_item(
ipqos_conf_action_t *act,
boolean_t add_undo)
{
ipqos_conf_class_t *cls;
ipqos_conf_filter_t *flt;
ipqos_conf_act_ref_t *dep;
int res;
IPQOSCDBG3(L1, "In remove_item: action: %s, add_undo: %u, mod: %u\n",
act->name, add_undo, act->modified);
/* return immmediately if previously visited in remove phase */
if (act->visited == REM_VISITED) {
IPQOSCDBG0(L1, "Exit due to REM_VISITED set\n");
return (IPQOS_CONF_SUCCESS);
}
act->visited = REM_VISITED;
/* if this action is to be deleted */
if (add_undo == B_FALSE && act->todel == B_TRUE ||
add_undo == B_TRUE && act->new == B_TRUE &&
act->cr_mod == B_TRUE) {
/* modify parent actions first */
for (dep = act->dependencies; dep; dep = dep->next) {
res = remove_item(dep->action, add_undo);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
/* delete this action */
IPQOSCDBG1(APPLY, "deleting action %s\n", act->name);
res = ipp_action_destroy(act->name, 0);
if (res != 0) {
IPQOSCDBG1(APPLY, "failed to destroy action %s\n",
act->name);
return (IPQOS_CONF_ERR);
}
/* flag as deleted */
act->deleted = B_TRUE;
/* if modified action */
} else if (act->modified == B_TRUE) {
/* loop through removing any filters marked for del */
for (flt = act->filters; flt; flt = flt->next) {
if ((add_undo == B_FALSE && flt->todel == B_TRUE) ||
(add_undo == B_TRUE && flt->new == B_TRUE &&
flt->cr_mod == B_TRUE)) {
/* do deletion */
res = remove_filter(act->name, flt->name,
flt->instance, act->module_version);
if (res != IPQOS_CONF_SUCCESS) {
IPQOSCDBG2(APPLY, "failed to destroy "
"filter %s, inst: %d\n", flt->name,
flt->instance);
return (IPQOS_CONF_ERR);
}
/* flag deleted */
flt->deleted = B_TRUE;
}
}
/* remove any classes marked for del */
for (cls = act->classes; cls; cls = cls->next) {
if ((add_undo == B_FALSE && cls->todel == B_TRUE) ||
(add_undo == B_TRUE && cls->new == B_TRUE &&
cls->cr_mod == B_TRUE)) {
/* do deletion */
res = remove_class(act->name, cls->name,
act->module_version, 0);
if (res != IPQOS_CONF_SUCCESS) {
IPQOSCDBG1(APPLY, "failed to destroy "
"class %s\n", cls->name);
return (IPQOS_CONF_ERR);
}
/* flag deleted */
cls->deleted = B_TRUE;
}
}
/* mark action as having been modified */
act->cr_mod = B_TRUE;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* for each of the actions in parameter actions apply any objects marked as
* modified as a modification to the kernel action represented.
* RETURNS: IPQOS_CONF_ERR on err, else IPQOS_CONF_SUCCESS.
*/
static int
modify_items(ipqos_conf_action_t *actions)
{
ipqos_conf_action_t *act;
int res;
ipqos_conf_filter_t *flt;
ipqos_conf_class_t *cls;
IPQOSCDBG0(L1, "In modify_items\n");
/* loop through actions in parameter actions */
for (act = actions; act; act = act->next) {
/* skip unchanged actions */
if (act->modified == B_FALSE) {
continue;
}
/* apply any parameter mods */
if (act->params->modified) {
res = modify_params(act->name,
&act->params->nvlist,
act->module_version, act->params->stats_enable);
if (res != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
act->params->cr_mod = B_TRUE;
}
/* apply any class mods */
for (cls = act->classes; cls; cls = cls->next) {
if (cls->modified) {
res = modify_class(act->name, cls->name,
act->module_version, cls->stats_enable,
cls->alist->name, 0);
if (res != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* mark modification done */
cls->cr_mod = B_TRUE;
}
}
/* apply any filter mods */
for (flt = act->filters; flt; flt = flt->next) {
if (flt->modified) {
res = modify_filter(act->name, flt,
act->module_version);
if (res != 0) {
return (IPQOS_CONF_ERR);
}
/* mark modification done */
flt->cr_mod = B_TRUE;
}
}
/* mark action modified */
act->cr_mod = B_TRUE;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* For each of the objects of each of the actions in nactions that are
* marked as having been modified the object modification is done in
* reverse using the same named object from oactions.
* RETURNS: IPQOS_CONF_ERR on error, IPQOS_CONF_SUCCESS otherwise.
*/
static int
undo_modifys(
ipqos_conf_action_t *oactions,
ipqos_conf_action_t *nactions)
{
ipqos_conf_filter_t *flt;
ipqos_conf_class_t *cls;
ipqos_conf_action_t *act;
ipqos_conf_action_t *oldact;
ipqos_conf_filter_t *oldflt;
ipqos_conf_class_t *oldcls;
int res;
IPQOSCDBG0(L1, "In undo_modifys:\n");
/* loop throught new actions */
for (act = nactions; act; act = act->next) {
oldact = actionexist(act->name, oactions);
/*
* if the action was new then it will be removed and
* any permamanent items that were marked for modify
* will dissappear, so ignore action.
*/
if (oldact == NULL) {
continue;
}
/* if parameters were modified switch them back */
if (act->params->modified == B_TRUE &&
act->params->cr_mod == B_TRUE) {
res = modify_params(act->name,
&oldact->params->nvlist,
act->module_version, act->params->stats_enable);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
/* for each filter in action if filter modified switch back */
for (flt = act->filters; flt; flt = flt->next) {
if (flt->modified == B_TRUE &&
flt->cr_mod == B_TRUE) {
oldflt = filterexist(flt->name, -1,
oldact->filters);
res = modify_filter(act->name, oldflt,
act->module_version);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
}
/* for each class in action if class modified switch back */
for (cls = act->classes; cls; cls = cls->next) {
if (cls->modified == B_TRUE &&
cls->cr_mod == B_TRUE) {
oldcls = classexist(cls->name, oldact->classes);
if (oldcls->alist) {
res = modify_class(act->name,
cls->name, act->module_version,
oldcls->stats_enable,
oldcls->alist->name, 0);
}
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
}
}
/*
* Go through the old actions modifying perm filters and classes
* whose action was deleted.
*
*/
for (act = oactions; act != NULL; act = act->next) {
if (act->deleted == B_FALSE) {
continue;
}
for (flt = act->filters; flt != NULL; flt = flt->next) {
if (flt->originator == IPP_CONFIG_PERMANENT) {
res = modify_filter(act->name, flt,
act->module_version);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
}
for (cls = act->classes; cls != NULL; cls = cls->next) {
if (cls->originator == IPP_CONFIG_PERMANENT) {
res = modify_class(act->name, cls->name,
act->module_version, cls->stats_enable,
cls->alist->name, 0);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* causes all changes marked as being done in actions and old_actions
* to be undone.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
rollback(
ipqos_conf_action_t *actions,
ipqos_conf_action_t *old_actions)
{
int res;
IPQOSCDBG0(RBK, "In rollback:\n");
/* re-add items that were deleted */
res = add_items(old_actions, B_TRUE);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/* change modified items back how they were */
res = undo_modifys(old_actions, actions);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/* remove new items that were added */
res = remove_items(actions, B_TRUE);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
return (IPQOS_CONF_SUCCESS);
}
/* ******************************* print config **************************** */
/*
* Prints the username of the user with uid 'uid' to 'fp' if the uid belongs
* to a known user on the system, otherwise just print 'uid'.
*/
static void
printuser(
FILE *fp,
uid_t uid)
{
struct passwd *pwd;
IPQOSCDBG0(L0, "In printuser\n");
pwd = getpwuid(uid);
if (pwd != NULL) {
(void) fprintf(fp, "%s\n", pwd->pw_name);
} else {
(void) fprintf(fp, "%u\n", (int)uid);
}
}
/*
* print either a single value of start to fp (if start equals end), else
* print start'-'end if start is the smaller of the two values, otherwise
* print end'-'start.
*/
static void
printrange(
FILE *fp,
uint32_t start,
uint32_t end)
{
uint32_t tmp;
if (start > end) {
tmp = start;
start = end;
end = tmp;
}
(void) fprintf(fp, "%u", start);
if (end != start)
(void) fprintf(fp, "-%u", end);
}
/*
* print the contents of the array arr to fp in the form:
* {0-6:1;7-12:2;13:3.....} or {0-6:GREEN;7-12:YELLOW:...}
* dependant upon whether this is an integer or enumerated array resectively
* (if enum_nvs isn't set to NULL this is assumed to be an enumerated array);
* where 0-6 is the range of indexes with value 1 (or GREEN), 7-12 the range
* with value 2 (or YELLOW), and so forth. size is the array size and llimit
* and ulimit are the lower and upper limits of the array values printed
* respectively. For enumerated arrays enum_nvs carries the list of name
* and value pairs and ulimit and llimit parameters are ignored and instead
* determined from the enum_nvs list.
*/
static void
print_int_array(
FILE *fp,
int arr[],
uint32_t size,
int llimit,
int ulimit,
str_val_nd_t *enum_nvs,
int tab_inserts)
{
int x, y;
uint32_t first, last;
boolean_t first_entry; /* first 'ranges:value' to be printed ? */
boolean_t first_range; /* first range for a value to be printed ? */
boolean_t found_range; /* did we find a range for this value ? */
IPQOSCDBG4(L0, "In print_int_array: size: %u, llimit: %u, ulimit: %u, "
"enum_nvs: %x \n", size, llimit, ulimit, enum_nvs);
/*
* if an enumeration retrieve value range.
*/
if (enum_nvs != NULL)
get_str_val_value_range(enum_nvs, &llimit, &ulimit);
/*
* print opening curl.
*/
(void) fprintf(fp, "%c\n", CURL_BEGIN);
PRINT_TABS(fp, tab_inserts + 1);
first_entry = B_TRUE;
/*
* for each value in range.
*/
for (x = llimit; x <= ulimit; x++) {
found_range = B_FALSE;
first_range = B_TRUE;
y = 0;
/*
* scan array and print ranges of indexes with value x.
*/
while (y < size) {
/*
* get first occurence of value for this range.
*/
while ((arr[y] != x) && (y < size))
y++;
if (y == size) {
break;
} else {
found_range = B_TRUE;
}
first = y;
/*
* get last occurence of value for this range.
*/
while ((arr[y] == x) && (y < size))
y++;
last = y - 1;
/*
* print entry delimiter (semi-colon)? It must be
* the first range for this value and this mustn't
* be the first 'ranges:value' entry.
*/
if (!first_entry && first_range) {
(void) fprintf(fp, ";\n");
PRINT_TABS(fp, tab_inserts + 1);
} else {
first_entry = B_FALSE;
}
/*
* print comma (range delimeter) only if there was
* a previous range for this value.
*/
if (!first_range) {
(void) fprintf(fp, ",");
} else {
first_range = B_FALSE;
}
/*
* print range.
*/
printrange(fp, first, last);
}
/*
* only print a colon and value if we found a range with
* this value.
*/
if (found_range) {
(void) fprintf(fp, ":");
/*
* print numeric/symbolic value.
*/
if (enum_nvs) {
printenum(fp, x, enum_nvs);
} else {
(void) fprintf(fp, "%d", x);
}
}
}
/*
* print closing curl.
*/
(void) fprintf(fp, "\n");
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, "%c\n", CURL_END);
}
/* print the protocol name for proto, or if unknown protocol number proto. */
static void
printproto(
FILE *fp,
uint8_t proto)
{
struct protoent *pent;
pent = getprotobynumber(proto);
if (pent != NULL) {
(void) fprintf(fp, "%s\n", pent->p_name);
} else {
(void) fprintf(fp, "%u\n", proto);
}
}
/*
* prints the name associated with interface with index ifindex to fp.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
printifname(
FILE *fp,
int ifindex)
{
int s;
struct lifconf lc;
struct lifnum ln;
struct lifreq *lr;
char *buf;
int len;
char *cp;
int ret;
int x;
int idx;
/* open socket */
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
ipqos_msg(MT_ENOSTR, gettext("opening AF_INET socket"));
return (IPQOS_CONF_ERR);
}
/* get number of lifreq structs that need to be alloc'd for */
ln.lifn_family = AF_UNSPEC;
ln.lifn_flags = 0;
ret = ioctl(s, SIOCGLIFNUM, &ln);
if (ret < 0) {
ipqos_msg(MT_ENOSTR, "SIOCLIFNUM ioctl");
(void) close(s);
return (IPQOS_CONF_ERR);
}
/* allocate buffer for SIOGLIFCONF ioctl */
len = ln.lifn_count * sizeof (struct lifreq);
buf = malloc(len);
if (buf == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
(void) close(s);
return (IPQOS_CONF_ERR);
}
/* setup lifconf params for ioctl */
lc.lifc_family = AF_UNSPEC;
lc.lifc_flags = 0;
lc.lifc_len = len;
lc.lifc_buf = buf;
/* do SIOCGLIFCONF ioctl */
ret = ioctl(s, SIOCGLIFCONF, &lc);
if (ret < 0) {
ipqos_msg(MT_ENOSTR, "SIGLIFCONF");
(void) close(s);
free(buf);
return (IPQOS_CONF_ERR);
}
(void) close(s);
/*
* for each interface name given in the returned lifreq list get
* it's index and compare with ifindex param. Break if equal.
*/
for (x = ln.lifn_count, lr = lc.lifc_req; x > 0; x--, lr++) {
ret = readifindex(lr->lifr_name, &idx);
if (ret != IPQOS_CONF_SUCCESS) {
free(buf);
return (IPQOS_CONF_ERR);
}
if (idx == ifindex) {
break;
}
}
free(buf);
if (x == 0) {
IPQOSCDBG1(L1, "Failed to find if index %u in returned "
"if list.\n", ifindex);
return (IPQOS_CONF_ERR);
}
/* truncate any logical suffix */
if ((cp = strchr(lr->lifr_name, '@')) != NULL) {
*cp = NULL;
}
/* print interface name */
(void) fprintf(fp, "%s\n", lr->lifr_name);
return (IPQOS_CONF_SUCCESS);
}
/*
* print to fp the enumeration clause evaluating to the value val using the
* names/values given in enum_nvs.
*/
static void
printenum(
FILE *fp,
uint32_t val,
str_val_nd_t *enum_nvs)
{
boolean_t isfirstval = B_TRUE;
str_val_nd_t *name_val = enum_nvs;
/* for each value in enum_nvs if same bit set in val print name */
while (name_val) {
if ((name_val->sv.value & val) == name_val->sv.value) {
if (isfirstval == B_TRUE) {
(void) fprintf(fp, "%s", name_val->sv.string);
isfirstval = B_FALSE;
} else {
(void) fprintf(fp, ", %s", name_val->sv.string);
}
}
name_val = name_val->next;
}
}
/* prints the service name of port, or if unknown the number to fp. */
static void
printport(
FILE *fp,
uint16_t port)
{
struct servent *sent;
sent = getservbyport(port, NULL);
if (sent != NULL) {
(void) fprintf(fp, "%s\n", sent->s_name);
} else {
(void) fprintf(fp, "%u\n", ntohs(port));
}
}
/*
* prints tp fp the name and value of all user specifiable parameters in the
* nvlist.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
printnvlist(
FILE *fp,
char *module,
nvlist_t *nvl,
int printall, /* are we want ip addresses printing if node name */
ipqos_conf_filter_t *flt, /* used to determine if node name set */
int tab_inserts,
place_t place)
{
FILE *tfp;
nvpair_t *nvp;
char *name;
ipqos_nvtype_t type;
str_val_nd_t *enum_nvs;
int ret;
char dfltst[IPQOS_VALST_MAXLEN+1];
char *param;
int openerr;
int res;
IPQOSCDBG0(L1, "In printnvlist\n");
/* open stream to types file */
tfp = validmod(module, &openerr);
if (tfp == NULL) {
if (openerr) {
ipqos_msg(MT_ENOSTR, "fopen");
}
return (IPQOS_CONF_ERR);
}
/* go through list getting param name and type and printing it */
nvp = nvlist_next_nvpair(nvl, NULL);
while (nvp) {
/* get nvpair name */
name = nvpair_name(nvp);
IPQOSCDBG1(L0, "processing element %s.\n", name);
/* skip ipgpc params that are not explicitly user settable */
if (strcmp(name, IPGPC_FILTER_TYPE) == 0 ||
strcmp(name, IPGPC_SADDR_MASK) == 0 ||
strcmp(name, IPGPC_DADDR_MASK) == 0 ||
strcmp(name, IPGPC_SPORT_MASK) == 0 ||
strcmp(name, IPGPC_DPORT_MASK) == 0) {
nvp = nvlist_next_nvpair(nvl, nvp);
continue;
}
param = SHORT_NAME(name);
/*
* get parameter type from types file.
*/
place = PL_ANY;
ret = readtype(tfp, module, param, &type, &enum_nvs, dfltst,
B_TRUE, &place);
if (ret != IPQOS_CONF_SUCCESS) {
return (ret);
}
/*
* for map entries we don't print the map value, only
* the index value it was derived from.
*/
if (place == PL_MAP) {
nvp = nvlist_next_nvpair(nvl, nvp);
continue;
}
/*
* the ifindex is converted to the name and printed out
* so print the parameter name as ifname.
*/
if (strcmp(name, IPGPC_IF_INDEX) == 0) {
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, "%s ", IPQOS_IFNAME_STR);
/*
* we may not print the address due to us instead printing
* the node name in printfilter, therefore we leave the
* printing of the parameter in the addresses switch case code.
*/
} else if ((strcmp(name, IPGPC_SADDR) != 0 &&
strcmp(name, IPGPC_DADDR) != 0)) {
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, "%s ", param);
}
switch (type) {
case IPQOS_DATA_TYPE_IFINDEX: {
uint32_t ifidx;
(void) nvpair_value_uint32(nvp, &ifidx);
(void) printifname(fp, ifidx);
break;
}
case IPQOS_DATA_TYPE_BOOLEAN: {
boolean_t bl;
(void) nvpair_value_uint32(nvp,
(uint32_t *)&bl);
(void) fprintf(fp, "%s\n",
bl == B_TRUE ? "true" : "false");
break;
}
case IPQOS_DATA_TYPE_ACTION: {
char *strval;
(void) nvpair_value_string(nvp, &strval);
print_action_nm(fp, strval);
break;
}
case IPQOS_DATA_TYPE_STRING: {
char *strval;
(void) nvpair_value_string(nvp, &strval);
(void) fprintf(fp, "%s\n",
quote_ws_string(strval));
break;
}
case IPQOS_DATA_TYPE_ADDRESS: {
uint_t tmp;
in6_addr_t *addr;
char addrstr[INET6_ADDRSTRLEN];
uchar_t ftype;
int af;
in6_addr_t *mask;
/*
* skip addresses that have node names for
* non printall listings.
*/
if (printall == 0 &&
(strcmp(nvpair_name(nvp), IPGPC_SADDR) ==
0 && flt->src_nd_name ||
strcmp(nvpair_name(nvp), IPGPC_DADDR) ==
0 && flt->dst_nd_name)) {
break;
}
/* we skipped this above */
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, "%s ", param);
(void) nvpair_value_uint32_array(nvp,
(uint32_t **)&addr, &tmp);
/* get filter type */
(void) nvlist_lookup_byte(nvl,
IPGPC_FILTER_TYPE, &ftype);
if (ftype == IPGPC_V4_FLTR) {
af = AF_INET;
addr = (in6_addr_t *)
&V4_PART_OF_V6((*addr));
} else {
af = AF_INET6;
}
/* get mask */
if (strcmp(nvpair_name(nvp), IPGPC_SADDR) ==
0) {
ret = nvlist_lookup_uint32_array(nvl,
IPGPC_SADDR_MASK,
(uint32_t **)&mask, &tmp);
} else {
ret = nvlist_lookup_uint32_array(nvl,
IPGPC_DADDR_MASK,
(uint32_t **)&mask, &tmp);
}
/* print address/mask to fp */
(void) fprintf(fp, "%s/%u\n",
inet_ntop(af, addr, addrstr,
INET6_ADDRSTRLEN), masktocidr(af, mask));
break;
}
case IPQOS_DATA_TYPE_ENUM: {
uint32_t val;
(void) nvpair_value_uint32(nvp, &val);
/*
* print list of tokens resulting in val
*/
(void) fprintf(fp, "{ ");
printenum(fp, val, enum_nvs);
(void) fprintf(fp, " }\n");
break;
}
case IPQOS_DATA_TYPE_PORT: {
uint16_t port;
(void) nvpair_value_uint16(nvp, &port);
printport(fp, port);
break;
}
case IPQOS_DATA_TYPE_PROTO: {
uint8_t proto;
(void) nvpair_value_byte(nvp, &proto);
printproto(fp, proto);
break;
}
case IPQOS_DATA_TYPE_M_INDEX:
case IPQOS_DATA_TYPE_UINT8: {
uchar_t u8;
(void) nvpair_value_byte(nvp, &u8);
(void) fprintf(fp, "%u\n", u8);
break;
}
case IPQOS_DATA_TYPE_UINT16: {
uint16_t u16;
(void) nvpair_value_uint16(nvp, &u16);
(void) fprintf(fp, "%u\n", u16);
break;
}
case IPQOS_DATA_TYPE_INT16: {
int16_t i16;
(void) nvpair_value_int16(nvp, &i16);
(void) fprintf(fp, "%d\n", i16);
break;
}
case IPQOS_DATA_TYPE_UINT32: {
uint32_t u32;
(void) nvpair_value_uint32(nvp, &u32);
(void) fprintf(fp, "%u\n", u32);
break;
}
case IPQOS_DATA_TYPE_INT32: {
int i32;
(void) nvpair_value_int32(nvp, &i32);
(void) fprintf(fp, "%d\n", i32);
break;
}
case IPQOS_DATA_TYPE_INT_ARRAY: {
str_val_nd_t *arr_enum_nvs = NULL;
uint32_t size;
int llimit, ulimit;
int *arr;
(void) nvpair_value_int32_array(nvp, &arr,
&size);
/*
* read array info from types file.
*/
res = read_int_array_info(dfltst,
&arr_enum_nvs, &size, &llimit, &ulimit,
module);
/*
* print array with numbers, or symbols
* if enumerated.
*/
if (res == IPQOS_CONF_SUCCESS) {
print_int_array(fp, arr, size,
llimit, ulimit, arr_enum_nvs,
tab_inserts);
if (arr_enum_nvs != NULL) {
free_str_val_entrys(
arr_enum_nvs);
}
}
break;
}
case IPQOS_DATA_TYPE_USER: {
uid_t uid;
(void) nvpair_value_int32(nvp, (int *)&uid);
printuser(fp, uid);
break;
}
#ifdef _IPQOS_CONF_DEBUG
default: {
/*
* we should have catered for all used data
* types that readtype returns.
*/
assert(1);
}
#endif
}
nvp = nvlist_next_nvpair(nvl, nvp);
}
(void) fclose(tfp);
return (IPQOS_CONF_SUCCESS);
}
/*
* print a parameter clause for the parmeters given in params to fp.
* If printall is set, then the originator of the parameter object is printed.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
printparams(
FILE *fp,
char *module,
ipqos_conf_params_t *params,
int printall,
int tab_inserts)
{
int res;
/* print opening clause */
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, IPQOS_CONF_PARAMS_STR " {\n");
/* print originator name if printall flag set */
if (printall) {
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(stdout, "Originator %s\n",
quote_ws_string(get_originator_nm(params->originator)));
}
/* print global stats */
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, IPQOS_CONF_GLOBAL_STATS_STR " %s\n",
params->stats_enable == B_TRUE ? "true" : "false");
/* print module specific parameters */
res = printnvlist(fp, module, params->nvlist, printall, NULL,
tab_inserts + 1, PL_PARAMS);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, "}\n");
return (IPQOS_CONF_SUCCESS);
}
/*
* print the interpreted name of the action_nm parameter if it is a special
* action, else action_nm verbatim to fp parameter.
*/
static void
print_action_nm(FILE *fp, char *action_nm)
{
if (strcmp(action_nm, IPP_ANAME_CONT) == 0) {
(void) fprintf(fp, IPQOS_CONF_CONT_STR "\n");
} else if (strcmp(action_nm, IPP_ANAME_DEFER) == 0) {
(void) fprintf(fp, IPQOS_CONF_DEFER_STR "\n");
} else if (strcmp(action_nm, IPP_ANAME_DROP) == 0) {
(void) fprintf(fp, IPQOS_CONF_DROP_STR "\n");
} else {
(void) fprintf(fp, "%s\n", quote_ws_string(action_nm));
}
}
/*
* print a class clause for class to fp. If printall is set the originator
* is printed.
*/
static void
printclass(
FILE *fp,
ipqos_conf_class_t *class,
int printall,
int tab_inserts)
{
/* print opening clause */
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, IPQOS_CONF_CLASS_STR " {\n");
/* if printall flag print originator name */
if (printall) {
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(stdout, "Originator %s\n",
get_originator_nm(class->originator));
}
/* print name, next action and stats enable */
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, IPQOS_CONF_NAME_STR " %s\n",
quote_ws_string(class->name));
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, IPQOS_CONF_NEXT_ACTION_STR " ");
print_action_nm(fp, class->alist->name);
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, IPQOS_CONF_STATS_ENABLE_STR " %s\n",
class->stats_enable == B_TRUE ? "true" : "false");
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, "}\n");
}
/*
* Returns a ptr to the originator name associated with origid. If unknown
* id returns ptr to "unknown".
* RETURNS: ptr to originator name, or if id not known "unknown".
*/
static char *
get_originator_nm(uint32_t origid)
{
int x;
/* scan originators table for origid */
for (x = 0; originators[x].value != -1 &&
originators[x].value != origid; x++) {}
/* if we've reached end of array due to unknown type return "unknown" */
if (originators[x].value == -1) {
return ("unknown");
}
return (originators[x].string);
}
/*
* print a filter clause for filter pointed to by filter out to fp. If printall
* is set then the originator is printed, for filters with node names instance
* numbers are printed, and the filter pointer isn't advanced to point at the
* last instance of the printed filter.
* RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS.
*/
static int
printfilter(
FILE *fp,
char *module,
ipqos_conf_filter_t **filter,
int printall,
int tab_inserts)
{
int res;
/* print opening clause */
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, IPQOS_CONF_FILTER_STR " {\n");
/* print originator if printall flag set */
if (printall) {
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(stdout, "Originator %s\n",
quote_ws_string(get_originator_nm((*filter)->originator)));
}
/* print name and class */
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, IPQOS_CONF_NAME_STR " %s\n",
quote_ws_string((*filter)->name));
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, IPQOS_CONF_CLASS_STR " %s\n",
quote_ws_string((*filter)->class_name));
/* print the instance if printall and potential mhomed addresses */
if (printall && ((*filter)->src_nd_name || (*filter)->dst_nd_name)) {
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, "Instance %u\n", (*filter)->instance);
}
/* print node names if any */
if ((*filter)->src_nd_name) {
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, "%s %s\n", strchr(IPGPC_SADDR, '.') + 1,
(*filter)->src_nd_name);
}
if ((*filter)->dst_nd_name) {
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, "%s %s\n", strchr(IPGPC_DADDR, '.') + 1,
(*filter)->dst_nd_name);
}
/* print ip_version enumeration if set */
if ((*filter)->ip_versions != 0) {
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, IPQOS_CONF_IP_VERSION_STR " {");
if (VERSION_IS_V4(*filter)) {
(void) fprintf(fp, " V4");
}
if (VERSION_IS_V6(*filter)) {
(void) fprintf(fp, " V6");
}
(void) fprintf(fp, " }\n");
}
/* print other module specific parameters parameters */
res = printnvlist(fp, module, (*filter)->nvlist, printall, *filter,
tab_inserts + 1, PL_FILTER);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, "}\n");
/*
* if not printall advance filter parameter to last instance of this
* filter.
*/
if (!printall) {
for (;;) {
if ((*filter)->next == NULL ||
strcmp((*filter)->name, (*filter)->next->name) !=
0) {
break;
}
*filter = (*filter)->next;
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Returns a pointer to str if no whitespace is present, else it returns
* a pointer to a string with the contents of str enclose in double quotes.
* This returned strings contents may change in subsequent calls so a copy
* should be made of it if the caller wishes to retain it.
*/
static char *
quote_ws_string(const char *str)
{
static char *buf = NULL;
const char *cp; /* we don't modify the contents of str so const */
IPQOSCDBG0(L0, "In quote_ws_string\n");
/*
* Just return str if no whitespace.
*/
for (cp = str; (*cp != '\0') && !isspace(*cp); cp++)
;
if (*cp == '\0')
return ((char *)str);
if (buf == NULL) {
/*
* if first run just allocate buffer of
* strlen(str) + 2 quote characters + NULL terminator.
*/
buf = malloc(strlen(str) + 3);
} else if ((strlen(str) + 2) > strlen(buf)) {
/*
* Not first run, so check if we have a big enough buffer
* and if not reallocate the buffer to a sufficient size.
*/
buf = realloc(buf, strlen(str) + 3);
}
if (buf == NULL)
return ("");
/*
* copy string into buffer with quotes.
*/
(void) strcpy(buf, "\"");
(void) strcat(buf, str);
(void) strcat(buf, "\"");
return (buf);
}
/*
* print an action clause for action to fp. If the printall flag is set
* then all filters and classes (regardless of their originator) and
* their originators are displayed.
* RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS.
*/
static int
printaction(
FILE *fp,
ipqos_conf_action_t *action,
int printall,
int tab_inserts)
{
ipqos_conf_filter_t *flt;
ipqos_conf_class_t *cls;
int res;
/* print opening clause, module and name */
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, IPQOS_CONF_ACTION_STR " {\n");
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, IPQOS_CONF_MODULE_STR " %s\n",
quote_ws_string(action->module));
PRINT_TABS(fp, tab_inserts + 1);
(void) fprintf(fp, "name %s\n", quote_ws_string(action->name));
/* print params clause */
(void) fprintf(fp, "\n");
res = printparams(fp, action->module, action->params, printall,
tab_inserts + 1);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/*
* print classes clause for each class if printall is set, else
* just ipqosconf created or permanent classes.
*/
for (cls = action->classes; cls != NULL; cls = cls->next) {
if (printall ||
cls->originator == IPP_CONFIG_IPQOSCONF ||
cls->originator == IPP_CONFIG_PERMANENT) {
(void) fprintf(fp, "\n");
printclass(fp, cls, printall, tab_inserts + 1);
}
}
/*
* print filter clause for each filter if printall is set, else
* just ipqosconf created or permanent filters.
*/
for (flt = action->filters; flt != NULL; flt = flt->next) {
if (printall ||
flt->originator == IPP_CONFIG_IPQOSCONF ||
flt->originator == IPP_CONFIG_PERMANENT) {
(void) fprintf(fp, "\n");
res = printfilter(fp, action->module, &flt, printall,
tab_inserts + 1);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
}
}
PRINT_TABS(fp, tab_inserts);
(void) fprintf(fp, "}\n");
return (IPQOS_CONF_SUCCESS);
}
/* *************************************************************** */
static void
list_end(
ipqos_list_el_t **listp,
ipqos_list_el_t ***lendpp)
{
*lendpp = listp;
while (**lendpp != NULL) {
*lendpp = &(**lendpp)->next;
}
}
static void
add_to_list(
ipqos_list_el_t **listp,
ipqos_list_el_t *el)
{
el->next = *listp;
*listp = el;
}
/*
* given mask calculates the number of bits it spans. The mask must be
* continuous.
* RETURNS: number of bits spanned.
*/
static int
masktocidr(
int af,
in6_addr_t *mask)
{
int zeros = 0;
int byte;
int cidr;
/*
* loop through from lowest byte to highest byte counting the
* number of zero bits till hitting a one bit.
*/
for (byte = 15; byte >= 0; byte--) {
/*
* zero byte, so add 8 to zeros.
*/
if (mask->s6_addr[byte] == 0) {
zeros += 8;
/*
* non-zero byte, add zero count to zeros.
*/
} else {
zeros += (ffs((int)mask->s6_addr[byte]) - 1);
break;
}
}
/*
* translate zero bits to 32 or 128 bit mask based on af.
*/
if (af == AF_INET) {
cidr = 32 - zeros;
} else {
cidr = 128 - zeros;
}
return (cidr);
}
/*
* Sets the first prefix_len bits in the v4 or v6 address (based upon af)
* contained in the v6 address referenced by addr to 1.
*/
static void
setmask(int prefix_len, in6_addr_t *addr, int af)
{
int i;
int shift;
int maskstartbit = 128 - prefix_len;
int end_u32;
IPQOSCDBG2(L1, "In setmask, prefix_len: %u, af: %s\n", prefix_len,
af == AF_INET ? "AF_INET" : "AF_INET6");
/* zero addr */
bzero(addr, sizeof (in6_addr_t));
/* set which 32bits in *addr are relevant to this af */
if (af == AF_INET) {
end_u32 = 3;
maskstartbit = 32 - prefix_len;
/* AF_INET6 */
} else {
end_u32 = 0;
}
/*
* go through each of the 32bit quantities in 128 bit in6_addr_t
* and set appropriate bits according to prefix_len.
*/
for (i = 3; i >= end_u32; i--) {
/* does the prefix apply to this 32bits? */
if (maskstartbit < ((4 - i) * 32)) {
/* is this 32bits fully masked? */
if (maskstartbit <= ((3 - i) * 32)) {
shift = 0;
} else {
shift = maskstartbit % 32;
}
addr->_S6_un._S6_u32[i] = (uint32_t)~0;
addr->_S6_un._S6_u32[i] =
addr->_S6_un._S6_u32[i] >> shift;
addr->_S6_un._S6_u32[i] =
addr->_S6_un._S6_u32[i] << shift;
}
/* translate to NBO */
addr->_S6_un._S6_u32[i] = htonl(addr->_S6_un._S6_u32[i]);
}
}
/*
* search nvlist for an element with the name specified and return a ptr
* to it if found.
* RETURNS: pointer to nvpair named name if found, else NULL.
*/
static nvpair_t *
find_nvpair(nvlist_t *nvl, char *name)
{
nvpair_t *nvp;
nvpair_t *match = NULL;
char *nvp_name;
IPQOSCDBG0(L1, "In find_nvpair\n");
nvp = nvlist_next_nvpair(nvl, NULL);
while (nvp) {
nvp_name = nvpair_name(nvp);
if (strcmp(name, nvp_name) == 0) {
match = nvp;
}
nvp = nvlist_next_nvpair(nvl, nvp);
}
return (match);
}
/*
* returns a string containing module_name '.' name.
* RETURNS: IPQOS_CONF_ERR if error, else IPQOS_CONF_SUCCESS.
*/
static char *
prepend_module_name(
char *name,
char *module)
{
char *ret;
IPQOSCDBG0(L2, "In prepend_module_name\n");
ret = malloc(strlen(module) + strlen(".") + strlen(name) + 1);
if (ret == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (NULL);
}
(void) strcpy(ret, module);
(void) strcat(ret, ".");
(void) strcat(ret, name);
return (ret);
}
#if 0
/*
* check if element with matching s1 and s2 string is in table table.
* RETURNS: 1 if found else 0.
*/
static int
in_str_str_table(
str_str_t *table,
char *s1,
char *s2)
{
str_str_t *ss = table;
/* loop through table till matched or end */
while (ss->s1[0] != '\0' &&
(strcmp(ss->s1, s1) != 0 || strcmp(ss->s2, s2) != 0)) {
ss++;
}
if (ss->s1[0] != '\0') {
return (1);
}
return (0);
}
#endif /* 0 */
/*
* check whether name is a valid action/class/filter name.
* RETURNS: IPQOS_CONF_ERR if invalid name else IPQOS_CONF_SUCCESS.
*/
static int
valid_name(char *name)
{
IPQOSCDBG1(L1, "In valid_name: name: %s\n", name);
/* first char can't be '!' */
if (name[0] == '!') {
ipqos_msg(MT_ERROR, gettext("Name not allowed to start with "
"'!', line %u.\n"), lineno);
return (IPQOS_CONF_ERR);
}
/* can't exceed IPQOS_CONF_NAME_LEN size */
if (strlen(name) >= IPQOS_CONF_NAME_LEN) {
ipqos_msg(MT_ERROR, gettext("Name exceeds maximum name length "
"line %u.\n"), lineno);
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/* ********************* string value manip fns ************************** */
/*
* searches through the str_val_nd_t list of string value pairs finding
* the minimum and maximum values for value and places them in the
* integers pointed at by min and max.
*/
static void
get_str_val_value_range(
str_val_nd_t *svnp,
int *min,
int *max)
{
if (svnp != NULL) {
*min = *max = svnp->sv.value;
svnp = svnp->next;
}
while (svnp != NULL) {
if (svnp->sv.value > *max)
*max = svnp->sv.value;
if (svnp->sv.value < *min)
*min = svnp->sv.value;
svnp = svnp->next;
}
}
/*
* add an entry with string string and value val to sv_entrys.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
add_str_val_entry(
str_val_nd_t **sv_entrys,
char *string,
uint32_t val)
{
str_val_nd_t *sv_entry;
IPQOSCDBG2(L1, "In add_str_val_entry: string: %s, val: %u\n", string,
val);
/* alloc new node */
sv_entry = malloc(sizeof (str_val_nd_t));
if (sv_entry == NULL) {
return (IPQOS_CONF_ERR);
}
/* populate node */
sv_entry->sv.string = malloc(strlen(string) + 1);
if (sv_entry->sv.string == NULL) {
free(sv_entry);
ipqos_msg(MT_ENOSTR, "malloc");
return (IPQOS_CONF_ERR);
} else {
(void) strcpy(sv_entry->sv.string, string);
}
sv_entry->sv.value = val;
/* place at start of sv_entrys list */
sv_entry->next = *sv_entrys;
*sv_entrys = sv_entry;
return (IPQOS_CONF_SUCCESS);
}
/* frees all the elements of sv_entrys. */
static void
free_str_val_entrys(
str_val_nd_t *sv_entrys)
{
str_val_nd_t *sve = sv_entrys;
str_val_nd_t *tmp;
IPQOSCDBG0(L1, "In free_str_val_entrys\n");
while (sve) {
free(sve->sv.string);
tmp = sve->next;
free(sve);
sve = tmp;
}
}
/*
* finds the value associated with string and assigns it to value ref'd by
* val.
* RETURNS: IPQOS_CONF_ERR if string not found, else IPQOS_CONF_SUCCESS.
*/
static int
str_val_list_lookup(
str_val_nd_t *svs,
char *string,
uint32_t *val)
{
str_val_nd_t *sv = svs;
IPQOSCDBG1(L1, "In str_val_list_lookup: %s\n", string);
/* loop through list and exit when found or list end */
while (sv != NULL) {
if (strcmp(sv->sv.string, string) == 0) {
break;
}
sv = sv->next;
}
/* ret error if not found */
if (sv == NULL) {
return (IPQOS_CONF_ERR);
}
*val = sv->sv.value;
IPQOSCDBG1(L1, "svll: Value returned is %u\n", *val);
return (IPQOS_CONF_SUCCESS);
}
/* ************************ conf file read fns ***************************** */
/*
* Reads a uid or username from string 'str' and assigns either the uid
* or associated uid respectively to storage pointed at by 'uid'. The
* function determines whether to read a uid by checking whether the first
* character of 'str' is numeric, in which case it reads a uid; otherwise it
* assumes a username.
* RETURNS: IPQOS_CONF_ERR if a NULL string pointer is passed, the read uid
* doesn't have an entry on the system, or the read username doesn't have an
* entry on the system.
*/
static int
readuser(
char *str,
uid_t *uid)
{
struct passwd *pwd;
char *lo;
IPQOSCDBG1(L0, "In readuser, str: %s\n", str);
if (str == NULL)
return (IPQOS_CONF_ERR);
/*
* Check if this appears to be a uid, and if so check that a
* corresponding user exists.
*/
if (isdigit((int)str[0])) {
/*
* Read a 32bit integer and check in doing so that
* we have consumed the whole string.
*/
if (readint32(str, (int *)uid, &lo) != IPQOS_CONF_SUCCESS ||
*lo != '\0')
return (IPQOS_CONF_ERR);
if (getpwuid(*uid) == NULL)
return (IPQOS_CONF_ERR);
} else { /* This must be a username, so lookup the uid. */
pwd = getpwnam(str);
if (pwd == NULL) {
return (IPQOS_CONF_ERR);
} else {
*uid = pwd->pw_uid;
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Reads a range from range_st, either of form 'a-b' or simply 'a'.
* In the former case lower and upper have their values set to a
* and b respectively; in the later lower and upper have both
* their values set to a.
* RETURNS: IPQOS_CONF_ERR if there's a parse error, else IPQOS_CONF_SUCCESS.
*/
static int
readrange(
char *range_st,
int *lower,
int *upper)
{
char *cp;
char *end, *end2;
IPQOSCDBG1(L0, "In readrange: string: %s\n", range_st);
/*
* get range boundarys.
*/
cp = strchr(range_st, '-');
if (cp != NULL) { /* we have a range */
*cp++ = '\0';
*lower = (int)strtol(range_st, &end, 10);
*upper = (int)strtol(cp, &end2, 10);
SKIPWS(end);
SKIPWS(end2);
if ((range_st == end) || (*end != NULL) ||
(cp == end) || (*end2 != NULL)) {
IPQOSCDBG0(L0, "Failed reading a-b\n");
return (IPQOS_CONF_ERR);
}
} else { /* single value */
*lower = *upper = (int)strtol(range_st, &end, 10);
SKIPWS(end);
if ((range_st == end) || (*end != NULL)) {
IPQOSCDBG0(L0, "Failed reading a\n");
return (IPQOS_CONF_ERR);
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Reads the values of an integer array from fp whose format is:
* '{'RANGE[,RANGE[..]]:VALUE[;RANGE:VALUE[..]]'}', creates an array of size
* arr_size, applies the values to it and points arrp at this array.
* RANGE is one set of array indexes over which this value is to
* be applied, and VALUE either an integer within the range
* llimit - ulimit, or if enum_nvs isn't NULL, an enumeration value
* found in the list enum_nvs. Those values which aren't explicity set
* will be set to -1.
*
* RETURNS: IPQOS_CONF_ERR on resource or parse error, else IPQOS_CONF_SUCCESS.
*/
static int
read_int_array(
FILE *fp,
char *first_token,
int **arrp,
uint32_t arr_size,
int llimit,
int ulimit,
str_val_nd_t *enum_nvs)
{
char buf[5 * IPQOS_CONF_LINEBUF_SZ];
char *token;
char *range;
char *ranges;
char *svalue;
int value;
int res;
char *entry;
char *tmp;
char *end;
int lower, upper;
int x;
uint32_t startln;
IPQOSCDBG4(L0, "In read_int_array: size: %u, lower: %u, upper: %u, "
"first_token: %s\n", arr_size, llimit, ulimit, first_token);
/*
* read beginning curl.
*/
if (first_token[0] != CURL_BEGIN) {
ipqos_msg(MT_ERROR, gettext("\'{\' missing at line "
"%u.\n"), lineno);
return (IPQOS_CONF_ERR);
}
/*
* allocate and initialise array for holding read values.
*/
*arrp = malloc(arr_size * sizeof (int));
if (*arrp == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (IPQOS_CONF_ERR);
}
(void) memset(*arrp, -1, arr_size * sizeof (int));
/*
* read whole array declaration string into buffer.
* this is because readtoken doesn't interpret our
* delimeter values specially and may return them
* within another string.
*/
startln = lineno; /* store starting lineno for error reports */
buf[0] = '\0';
res = readtoken(fp, &token);
while ((res != IPQOS_CONF_CURL_END) && (res != IPQOS_CONF_ERR) &&
(res != IPQOS_CONF_EOF)) {
(void) strlcat(buf, token, sizeof (buf));
free(token);
res = readtoken(fp, &token);
}
if (res != IPQOS_CONF_CURL_END) {
goto array_err;
}
IPQOSCDBG1(L0, "array declaration buffer contains: %s\n", buf);
/*
* loop reading "ranges ':' value;" till end of buffer.
*/
entry = strtok(buf, ";");
while (entry != NULL) {
svalue = strchr(entry, ':');
if (svalue == NULL) { /* missing value string */
IPQOSCDBG0(L0, "Missing value string\n");
goto array_err;
}
*svalue++ = '\0';
ranges = entry;
/*
* get value of number or enumerated symbol.
*/
if (enum_nvs) {
/*
* get rid of surrounding whitespace so as not to
* confuse read_enum_value.
*/
SKIPWS(svalue);
tmp = svalue;
while (*tmp != '\0') {
if (isspace(*tmp)) {
*tmp = '\0';
break;
} else {
tmp++;
}
}
/*
* read enumeration value.
*/
res = read_enum_value(NULL, svalue, enum_nvs,
(uint32_t *)&value);
if (res != IPQOS_CONF_SUCCESS)
goto array_err;
} else {
value = (int)strtol(svalue, &end, 10);
SKIPWS(end);
if ((svalue == end) || (*end != NULL)) {
IPQOSCDBG0(L0, "Invalid value\n");
goto array_err;
}
IPQOSCDBG1(L0, "value: %u\n", value);
/*
* check value within valid range.
*/
if ((value < llimit) || (value > ulimit)) {
IPQOSCDBG0(L0, "value out of range\n");
goto array_err;
}
}
/*
* loop reading ranges for this value.
*/
range = strtok_r(ranges, ",", &tmp);
while (range != NULL) {
res = readrange(range, &lower, &upper);
if (res != IPQOS_CONF_SUCCESS)
goto array_err;
IPQOSCDBG2(L0, "range: %u - %u\n", lower, upper);
if (upper < lower) {
uint32_t u = lower;
lower = upper;
upper = u;
}
/*
* check range valid for array size.
*/
if ((lower < 0) || (upper > arr_size)) {
IPQOSCDBG0(L0, "Range out of array "
"dimensions\n");
goto array_err;
}
/*
* add this value to array indexes within range.
*/
for (x = lower; x <= upper; x++)
(*arrp)[x] = value;
/*
* get next range.
*/
range = strtok_r(NULL, ",", &tmp);
}
entry = strtok(NULL, ";");
}
return (IPQOS_CONF_SUCCESS);
array_err:
ipqos_msg(MT_ERROR,
gettext("Array declaration line %u is invalid.\n"), startln);
free(*arrp);
return (IPQOS_CONF_ERR);
}
static int
readllong(char *str, long long *llp, char **lo)
{
*llp = strtoll(str, lo, 0);
if (*lo == str) {
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
static int
readuint8(char *str, uint8_t *ui8, char **lo)
{
long long tmp;
if (readllong(str, &tmp, lo) != 0) {
return (IPQOS_CONF_ERR);
}
if (tmp > UCHAR_MAX || tmp < 0) {
return (IPQOS_CONF_ERR);
}
*ui8 = (uint8_t)tmp;
return (IPQOS_CONF_SUCCESS);
}
static int
readuint16(char *str, uint16_t *ui16, char **lo)
{
long long tmp;
if (readllong(str, &tmp, lo) != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
if (tmp > USHRT_MAX || tmp < 0) {
return (IPQOS_CONF_ERR);
}
*ui16 = (uint16_t)tmp;
return (IPQOS_CONF_SUCCESS);
}
static int
readint16(char *str, int16_t *i16, char **lo)
{
long long tmp;
if (readllong(str, &tmp, lo) != 0) {
return (IPQOS_CONF_ERR);
}
if (tmp > SHRT_MAX || tmp < SHRT_MIN) {
return (IPQOS_CONF_ERR);
}
*i16 = (int16_t)tmp;
return (IPQOS_CONF_SUCCESS);
}
static int
readint32(char *str, int *i32, char **lo)
{
long long tmp;
if (readllong(str, &tmp, lo) != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
if (tmp > INT_MAX || tmp < INT_MIN) {
return (IPQOS_CONF_ERR);
}
*i32 = tmp;
return (IPQOS_CONF_SUCCESS);
}
static int
readuint32(char *str, uint32_t *ui32, char **lo)
{
long long tmp;
if (readllong(str, &tmp, lo) != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
if (tmp > UINT_MAX || tmp < 0) {
return (IPQOS_CONF_ERR);
}
*ui32 = (uint32_t)tmp;
return (IPQOS_CONF_SUCCESS);
}
/*
* retrieves the index associated with the interface named ifname and assigns
* it to the int pointed to by ifindex.
* RETURNS: IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS.
*/
static int
readifindex(
char *ifname,
int *ifindex)
{
int s;
struct lifreq lifrq;
/* open socket */
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
ipqos_msg(MT_ENOSTR, gettext("opening AF_INET socket"));
return (IPQOS_CONF_ERR);
}
/* copy ifname into lifreq */
(void) strlcpy(lifrq.lifr_name, ifname, LIFNAMSIZ);
/* do SIOGLIFINDEX ioctl */
if (ioctl(s, SIOCGLIFINDEX, (caddr_t)&lifrq) == -1) {
(void) close(s);
return (IPQOS_CONF_ERR);
}
/* Warn if a virtual interface is specified */
if ((ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrq) != -1) &&
(lifrq.lifr_flags & IFF_VIRTUAL)) {
ipqos_msg(MT_WARNING, gettext("Invalid interface"));
}
(void) close(s);
*ifindex = lifrq.lifr_index;
return (IPQOS_CONF_SUCCESS);
}
/*
* Case insensitively compares the string in str with IPQOS_CONF_TRUE_STR
* and IPQOS_CONF_FALSE_STR and sets boolean pointed to by bool accordingly.
* RETURNS: if failure to match either IPQOS_CONF_ERR, else IPQOS_CONF_SUCCESS.
*/
static int
readbool(char *str, boolean_t *bool)
{
if (strcasecmp(str, IPQOS_CONF_TRUE_STR) == 0) {
*bool = B_TRUE;
} else if (strcasecmp(str, IPQOS_CONF_FALSE_STR) == 0) {
*bool = B_FALSE;
} else {
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* reads a protocol name/number from proto_str and assigns the number
* to the uint8 ref'd by proto.
* RETURNS: If not a valid name or protocol number IPQOS_CONF_ERR, else
* IPQOS_CONF_SUCCESS.
*/
static int
readproto(char *proto_str, uint8_t *proto)
{
struct protoent *pent;
char *lo;
int res;
IPQOSCDBG1(L1, "In readproto: string: %s\n", proto_str);
/* try name lookup */
pent = getprotobyname(proto_str);
if (pent) {
*proto = pent->p_proto;
/* check valid protocol number */
} else {
res = readuint8(proto_str, proto, &lo);
if (res != IPQOS_CONF_SUCCESS || proto == 0) {
return (IPQOS_CONF_ERR);
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* reads either a port service, or a port number from port_str and assigns
* the associated port number to short ref'd by port.
* RETURNS: If invalid name and number IPQOS_CONF_ERR, else IPQOS_CONF_SUCCESS.
*/
static int
readport(char *port_str, uint16_t *port)
{
struct servent *sent;
char *tmp;
IPQOSCDBG1(L1, "In readport: string: %s\n", port_str);
/* try service name lookup */
sent = getservbyname(port_str, NULL);
/* failed name lookup so read port number */
if (sent == NULL) {
if (readuint16(port_str, port, &tmp) != IPQOS_CONF_SUCCESS ||
*port == 0) {
return (IPQOS_CONF_ERR);
}
*port = htons(*port);
} else {
*port = sent->s_port;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Reads a curly brace, a string enclosed in double quotes, or a whitespace/
* curly brace delimited string. If a double quote enclosed string the
* closing quotes need to be on the same line.
* RETURNS:
* on reading a CURL_BEGIN token it returns IPQOS_CONF_CURL_BEGIN,
* on reading a CURL_END token it returns IPQOS_CONF_CURL_END,
* on reading another valid token it returns IPQOS_CONF_SUCCESS.
* for each of these token is set to point at the read string.
* at EOF it returns IPQOS_CONF_EOF and if errors it returns IPQOS_CONF_ERR.
*/
static int
readtoken(
FILE *fp,
char **token)
{
char *st, *tmp;
int len;
int quoted = 0;
char *cmnt;
char *bpos;
int rembuf;
static char *lo;
static char *buf = NULL;
static int bufsize;
/* if first call initialize line buf to default size */
if (buf == NULL) {
bufsize = IPQOS_CONF_LINEBUF_SZ;
buf = malloc(bufsize);
if (buf == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (IPQOS_CONF_ERR);
}
}
/* set buffer postition and size to use whole buffer */
bpos = buf;
rembuf = bufsize;
/*
* loop reading lines until we've read a line with a non-whitespace
* char.
*/
do {
/* if no leftover from previous invocation */
if (lo == NULL) {
/*
* loop reading into buffer doubling if necessary until
* we have either read a complete line or reached the
* end of file.
*/
for (;;) {
st = fgets(bpos, rembuf, fp);
if (st == NULL) {
/* if read error */
if (ferror(fp)) {
free(buf);
buf = NULL;
ipqos_msg(MT_ENOSTR,
"fgets");
return (IPQOS_CONF_ERR);
/* end of file */
} else {
free(buf);
buf = NULL;
*token = NULL;
return (IPQOS_CONF_EOF);
}
} else {
/* if read a newline */
if (buf[strlen(buf) - 1] == '\n') {
lineno++;
break;
/* if read the last line */
} else if (feof(fp)) {
break;
/*
* not read a full line so buffer size
* is too small, double it and retry.
*/
} else {
bufsize *= 2;
tmp = realloc(buf, bufsize);
if (tmp == NULL) {
ipqos_msg(MT_ENOSTR,
"realloc");
free(buf);
return (IPQOS_CONF_ERR);
} else {
buf = tmp;
}
/*
* make parameters to fgets read
* into centre of doubled buffer
* so we retain what we've
* already read.
*/
bpos = &buf[(bufsize / 2) - 1];
rembuf = (bufsize / 2) + 1;
}
}
}
st = buf;
/* previous leftover, assign to st */
} else {
st = lo;
lo = NULL;
}
/* truncate at comment */
cmnt = strchr(st, '#');
if (cmnt) {
*cmnt = '\0';
}
/* Skip any whitespace */
while (isspace(*st) && st != '\0') {
st++;
}
} while (*st == '\0');
/* find end of token */
tmp = st;
/* if curl advance 1 char */
if (*tmp == CURL_BEGIN || *tmp == CURL_END) {
tmp++;
/* if dbl quote read until matching quote */
} else if (*tmp == '"') {
quoted++;
tmp = ++st;
while (*tmp != '"' && *tmp != '\n' && *tmp != '\0') {
tmp++;
}
if (*tmp != '"') {
ipqos_msg(MT_ERROR, gettext("Quoted string exceeds "
"line, line %u.\n"), lineno);
free(buf);
return (IPQOS_CONF_ERR);
}
/* normal token */
} else {
/* find first whitespace, curl, newline or string end */
while (!isspace(*tmp) && *tmp != CURL_BEGIN &&
*tmp != CURL_END && *tmp != '\n' && *tmp != '\0') {
tmp++;
}
}
/* copy token to return */
len = tmp - st;
*token = malloc(len + 1);
if (!*token) {
free(buf);
ipqos_msg(MT_ENOSTR, "malloc");
return (IPQOS_CONF_ERR);
}
bcopy(st, *token, len);
(*token)[len] = '\0';
/* if just read quoted string remove quote from remaining string */
if (quoted) {
tmp++;
}
/* if not end of string, store rest for latter parsing */
if (*tmp != '\0' && *tmp != '\n') {
lo = tmp;
}
/* for curl_end and curl_begin return special ret codes */
if ((*token)[1] == '\0') {
if (**token == CURL_BEGIN) {
return (IPQOS_CONF_CURL_BEGIN);
} else if (**token == CURL_END) {
return (IPQOS_CONF_CURL_END);
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Reads an enumeration bitmask definition from line. The format is:
* { NAME=VAL, NAME2=VAL2 }. The resulting names and values are returned.
* RETURNS: NULL on error, else ptr to name/values.
*/
static str_val_nd_t *
read_enum_nvs(char *line, char *module_name)
{
str_val_nd_t *enum_vals = NULL;
char *cp;
char *start;
char *name = NULL;
int len;
uint32_t val;
int ret;
int readc;
IPQOSCDBG1(L1, "In read_enum_nvs, line: %s\n", line);
/* read opening brace */
cp = strchr(line, CURL_BEGIN);
if (cp == NULL) {
IPQOSCDBG0(L1, "missing curl begin\n");
goto fail;
} else {
start = cp + 1;
}
/*
* loop reading 'name = value' entrys seperated by comma until
* reach closing brace.
*/
for (;;) {
SKIPWS(start);
if (*start == '\0') {
IPQOSCDBG0(L1, "missing closing bracket\n");
goto fail;
}
/*
* read name - read until whitespace, '=', closing curl,
* or string end.
*/
for (cp = start;
!isspace(*cp) && *cp != '=' && *cp != CURL_END &&
*cp != '\0'; cp++) {}
if (*cp == '\0') {
IPQOSCDBG0(L1, "Unexpected line end in enum def'n\n");
goto fail;
/* finished definition, exit loop */
} else if (*cp == CURL_END) {
break;
}
/* store name */
len = cp - start;
name = malloc(len + 1);
if (name == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
goto fail;
}
bcopy(start, name, len);
name[len] = NULL;
IPQOSCDBG1(L0, "Stored name: %s\n", name);
/* read assignment */
start = strchr(cp, '=');
if (start == NULL) {
IPQOSCDBG0(L1, "Missing = in enum def'n\n");
goto fail;
}
/* read value */
ret = sscanf(++start, "%x%n", &val, &readc);
if (ret != 1) {
IPQOSCDBG1(L1, "sscanf of value failed, string: %s\n",
cp);
goto fail;
}
/* add name value to set */
ret = add_str_val_entry(&enum_vals, name, val);
if (ret != IPQOS_CONF_SUCCESS) {
IPQOSCDBG0(L1, "Failed to add str_val entry\n");
goto fail;
}
free(name);
name = NULL;
/* try reading comma */
cp = strchr(start, ',');
if (cp != NULL) {
start = cp + 1;
/* no comma, advance to char past value last read */
} else {
start += readc;
}
}
return (enum_vals);
fail:
free_str_val_entrys(enum_vals);
if (name != NULL)
free(name);
/* if a parse error */
if (errno == 0) {
ipqos_msg(MT_ERROR, gettext("Types file for module %s is "
"corrupt.\n"), module_name);
}
return (NULL);
}
/*
* Given mapped_list with is a comma seperated list of map names, and value,
* which is used to index into these maps, the function creates x new entries
* in nvpp, where x is the number of map names specified. Each of these
* entries has the value from the map in the position indexed by value and
* with name module.${MAP_NAME}. The maps are contained in the modules config
* file and have the form:
* map map1 uint32 1,23,32,45,3
* As you can see the map values are uint32, and along with uint8 are the
* only supported types at the moment.
*
* RETURNS: IPQOS_CONF_ERR if one of the maps specified in mapped_list
* doesn't exist, if value is not a valid map position for a map, or if
* there's a resource failure. otherwise IPQOS_CONF_SUCCESS is returned.
*/
static int
read_mapped_values(
FILE *tfp,
nvlist_t **nvlp,
char *module,
char *mapped_list,
int value)
{
char *map_name, *lastparam, *tmpname;
int res;
ipqos_nvtype_t type;
char dfltst[IPQOS_VALST_MAXLEN+1] = "";
str_val_nd_t *enum_nvs;
place_t place;
IPQOSCDBG0(L1, "In read_mapped_values\n");
map_name = (char *)strtok_r(mapped_list, ",", &lastparam);
while (map_name != NULL) {
char *tokval, *lastval;
int index = 0;
/*
* get map info from types file.
*/
place = PL_MAP;
res = readtype(tfp, module, map_name, &type, &enum_nvs,
dfltst, B_FALSE, &place);
if (res != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/*
* Just keep browsing the list till we get to the element
* with the index from the value parameter or the end.
*/
tokval = (char *)strtok_r(dfltst, ",", &lastval);
for (;;) {
if (tokval == NULL) {
ipqos_msg(MT_ERROR,
gettext("Invalid value, %u, line %u.\n"),
value, lineno);
return (IPQOS_CONF_ERR);
}
if (index++ == value) {
break;
}
tokval = (char *)strtok_r(NULL, ",", &lastval);
}
/*
* create fully qualified parameter name for map value.
*/
tmpname = prepend_module_name(map_name, module);
if (tmpname == NULL) {
return (IPQOS_CONF_ERR);
}
/*
* add map value with fqn to parameter nvlist.
*/
IPQOSCDBG2(L0, "Adding map %s, value %u to nvlist\n",
tmpname, atoi(tokval));
switch (type) {
case IPQOS_DATA_TYPE_UINT8: {
res = nvlist_add_byte(*nvlp, tmpname,
(uint8_t)atoi(tokval));
if (res != 0) {
free(tmpname);
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint8");
return (IPQOS_CONF_ERR);
}
break;
}
case IPQOS_DATA_TYPE_UINT32: {
res = nvlist_add_uint32(*nvlp, tmpname,
(uint32_t)atoi(tokval));
if (res != 0) {
free(tmpname);
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint32");
return (IPQOS_CONF_ERR);
}
break;
}
default: {
ipqos_msg(MT_ERROR,
gettext("Types file for module %s is "
"corrupt.\n"), module);
IPQOSCDBG1(L0, "Unsupported map type for "
"parameter %s given in types file.\n",
map_name);
return (IPQOS_CONF_ERR);
}
}
free(tmpname);
map_name = (char *)strtok_r(NULL, ",", &lastparam);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Parses the string info_str into it's components. Its format is:
* SIZE','[ENUM_DEF | RANGE], where SIZE is the size of the array,
* ENUM_DEF is the definition of the enumeration for this array,
* and RANGE is the set of values this array can accept. In
* the event this array has an enumeration definition enum_nvs is
* set to point at a str_val_nd_t structure which stores the names
* and values associated with this enumeration. Otherwise, if this
* is not an enumerated array, lower and upper are set to the lower
* and upper values of RANGE.
* RETURNS: IPQOS_CONF_ERR due to unexpected parse errors, else
* IPQOS_CONF_SUCCESS.
*/
static int
read_int_array_info(
char *info_str,
str_val_nd_t **enum_nvs,
uint32_t *size,
int *lower,
int *upper,
char *module)
{
int res;
char *end;
char *token;
char *tmp;
IPQOSCDBG1(L0, "In read_array_info: info_str: %s\n",
(info_str != NULL) ? info_str : "NULL");
if (info_str == NULL) {
IPQOSCDBG0(L0, "Null info string\n");
goto fail;
}
/*
* read size.
*/
token = strtok(info_str, ",");
*size = (uint32_t)strtol(token, &end, 10);
SKIPWS(end);
if ((end == token) || (*end != NULL)) {
IPQOSCDBG0(L0, "Invalid size\n");
goto fail;
}
IPQOSCDBG1(L0, "read size: %u\n", *size);
/*
* check we have another string.
*/
token = strtok(NULL, "\n");
if (token == NULL) {
IPQOSCDBG0(L0, "Missing range/enum def\n");
goto fail;
}
IPQOSCDBG1(L0, "range/enum def: %s\n", token);
/*
* check if enumeration set or integer set and read enumeration
* definition or integer range respectively.
*/
tmp = strchr(token, CURL_BEGIN);
if (tmp == NULL) { /* a numeric range */
res = readrange(token, lower, upper);
if (res != IPQOS_CONF_SUCCESS) {
IPQOSCDBG0(L0, "Failed reading range\n");
goto fail;
}
} else { /* an enumeration */
*enum_nvs = read_enum_nvs(token, module);
if (*enum_nvs == NULL) {
IPQOSCDBG0(L0, "Failed reading enum def\n");
goto fail;
}
}
return (IPQOS_CONF_SUCCESS);
fail:
ipqos_msg(MT_ERROR,
gettext("Types file for module %s is corrupt.\n"), module);
return (IPQOS_CONF_ERR);
}
/*
* reads the value of an enumeration parameter from first_token and fp.
* first_token is the first token of the value.
* The format expected is NAME | { NAME1 [, NAME2 ] [, NAME3 ] }.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
read_enum_value(
FILE *fp,
char *first_token,
str_val_nd_t *enum_vals,
uint32_t *val)
{
uint32_t u32;
int ret;
char *tk;
char *lo = NULL;
char *cm;
int name_expected = 0;
IPQOSCDBG0(L1, "In read_enum_value\n");
/* init param val */
*val = 0;
/* first token not curl_begin, so lookup its value */
if (*first_token != CURL_BEGIN) {
ret = str_val_list_lookup(enum_vals, first_token, val);
if (ret != IPQOS_CONF_SUCCESS) {
ipqos_msg(MT_ERROR,
gettext("Unrecognized value, %s, line %u.\n"),
first_token, lineno);
return (ret);
}
/* curl_begin, so read values till curl_end, dicing at ',' */
} else {
name_expected++;
for (;;) {
/*
* no leftover from pervious iteration so read new
* token. This leftover happens because readtoken
* doesn't interpret comma's as special characters
* and thus could return 'val1,val2' as one token.
* If this happens the val1 will be used in the
* current iteration and what follows saved in lo
* for processing by successive iterations.
*/
if (lo == NULL) {
ret = readtoken(fp, &tk);
if (ret == IPQOS_CONF_ERR) {
return (ret);
} else if (ret == IPQOS_CONF_EOF) {
ipqos_msg(MT_ERROR,
gettext("Unexpected EOF.\n"));
return (IPQOS_CONF_ERR);
}
} else { /* previous leftover, so use it */
IPQOSCDBG1(L1, "Using leftover %s.\n", lo);
tk = lo;
lo = NULL;
}
if (name_expected) {
if (ret == IPQOS_CONF_CURL_END ||
tk[0] == ',') {
ipqos_msg(MT_ERROR,
gettext("Malformed value list "
"line %u.\n"), lineno);
free(tk);
return (IPQOS_CONF_ERR);
}
/*
* check if this token contains a ',' and
* if so store it and what follows for next
* iteration.
*/
cm = strchr(tk, ',');
if (cm != NULL) {
lo = malloc(strlen(cm) + 1);
if (lo == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
free(tk);
return (IPQOS_CONF_ERR);
}
(void) strcpy(lo, cm);
*cm = '\0';
}
/* get name value and add to total val */
ret = str_val_list_lookup(enum_vals, tk, &u32);
if (ret != IPQOS_CONF_SUCCESS) {
ipqos_msg(MT_ERROR,
gettext("Unrecognized value, %s, "
"line %u.\n"), tk, lineno);
free(tk);
return (IPQOS_CONF_ERR);
}
*val = *val | u32;
name_expected--;
/* comma or curl end accepted */
} else {
/* we've reached curl_end so break */
if (ret == IPQOS_CONF_CURL_END) {
free(tk);
break;
/* not curl end and not comma */
} else if (tk[0] != ',') {
ipqos_msg(MT_ERROR,
gettext("Malformed value list "
"line %u.\n"), lineno);
free(tk);
return (IPQOS_CONF_ERR);
}
/*
* store anything after the comma for next
* iteration.
*/
if (tk[1] != '\0') {
lo = malloc(strlen(&tk[1]) + 1);
if (lo == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
free(tk);
return (IPQOS_CONF_ERR);
}
(void) strcpy(lo, &tk[1]);
}
name_expected++;
}
free(tk);
}
}
IPQOSCDBG1(L1, "value returned is: %u\n", *val);
return (IPQOS_CONF_SUCCESS);
}
/*
* read the set of permanent classes/filter from the types file ref'd by tfp
* and store them in a string table pointed to by perm_items,
* with *nitems getting set to number of items read. perm_filters is set
* to 1 if we're searching for permanent filters, else 0 for classes.
* RETURNS: IPQOS_CONF_ERR if any errors, else IPQOS_CONF_SUCCESS.
*/
static int
read_perm_items(
int perm_filters,
FILE *tfp,
char *module_name,
char ***perm_items,
int *nitems)
{
char lbuf[IPQOS_CONF_TYPE_LINE_LEN];
int cnt = 0;
char name[IPQOS_CONF_NAME_LEN+1];
char foo[IPQOS_CONF_NAME_LEN+1];
int res;
char **items = NULL;
char **tmp;
char *marker;
IPQOSCDBG0(L1, "In read_perm_items\n");
/* seek to start of types file */
if (fseek(tfp, 0, SEEK_SET) != 0) {
ipqos_msg(MT_ENOSTR, "fseek");
return (IPQOS_CONF_ERR);
}
/* select which marker were looking for */
if (perm_filters) {
marker = IPQOS_CONF_PERM_FILTER_MK;
} else {
marker = IPQOS_CONF_PERM_CLASS_MK;
}
/* scan file line by line till end */
while (fgets(lbuf, IPQOS_CONF_TYPE_LINE_LEN, tfp) != NULL) {
/*
* if the line is marked as containing a default item name
* read the name, extend the items string array
* and store the string off the array.
*/
if (strncmp(lbuf, marker, strlen(marker)) == 0) {
res = sscanf(lbuf,
"%" VAL2STR(IPQOS_CONF_NAME_LEN) "s"
"%" VAL2STR(IPQOS_CONF_NAME_LEN) "s",
foo, name);
if (res < 2) {
ipqos_msg(MT_ERROR,
gettext("Types file for module %s is "
"corrupt.\n"), module_name);
IPQOSCDBG1(L0, "Missing name with a %s.\n",
marker);
goto fail;
}
/* extend items array to accomodate new item */
tmp = realloc(items, (cnt + 1) * sizeof (char *));
if (tmp == NULL) {
ipqos_msg(MT_ENOSTR, "realloc");
goto fail;
} else {
items = tmp;
}
/* copy and store item name */
items[cnt] = malloc(strlen(name) + 1);
if (items[cnt] == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
goto fail;
}
(void) strcpy(items[cnt], name);
cnt++;
IPQOSCDBG1(L1, "stored %s in perm items array\n",
name);
}
}
*perm_items = items;
*nitems = cnt;
return (IPQOS_CONF_SUCCESS);
fail:
for (cnt--; cnt >= 0; cnt--)
free(items[cnt]);
free(items);
return (IPQOS_CONF_ERR);
}
/*
* Searches types file ref'd by tfp for the parameter named name
* with the place corresponding with place parameter. The format
* of the lines in the file are:
* PLACE NAME TYPE [ ENUM_DEF ] [ DEFAULT_STR ]
* The ENUM_DEF is an enumeration definition and is only present
* for parameters of type enum. DEFAULT_STR is a default value for
* this parameter. If present type is set to the appropriate type
* enumeration and dfltst filled with DEFAULT_STR if one was set.
* Also if the type is enum enum_nvps is made to point at a
* set of name value pairs representing ENUM_DEF.
*
* RETURNS: If any resource errors occur, or a matching parameter
* isn't found IPQOS_CONF_ERR is returned, else IPQOS_CONF_SUCCESS.
*/
static int
readtype(
FILE *tfp,
char *module_name,
char *name,
ipqos_nvtype_t *type,
str_val_nd_t **enum_nvps,
char *dfltst,
boolean_t allow_ipgpc_priv,
place_t *place)
{
int ac;
char lbuf[IPQOS_CONF_TYPE_LINE_LEN];
char param[IPQOS_CONF_PNAME_LEN+1];
char typest[IPQOS_CONF_TYPE_LEN+1];
char place_st[IPQOS_CONF_TYPE_LEN+1];
char *cp;
int x;
char *ipgpc_nm;
int found = 0;
IPQOSCDBG1(L1, "In readtype: param: %s\n", name);
/*
* if allow_ipgpc_priv is true then we allow ipgpc parameters that are
* private between ipqosconf and ipgpc. eg. address masks, port masks.
*/
if (allow_ipgpc_priv && strcmp(module_name, IPGPC_NAME) == 0) {
ipgpc_nm = prepend_module_name(name, IPGPC_NAME);
if (ipgpc_nm == NULL) {
return (IPQOS_CONF_ERR);
}
if (strcmp(ipgpc_nm, IPGPC_SADDR_MASK) == 0 ||
strcmp(ipgpc_nm, IPGPC_DADDR_MASK) == 0) {
*type = IPQOS_DATA_TYPE_ADDRESS_MASK;
return (IPQOS_CONF_SUCCESS);
} else if (strcmp(ipgpc_nm, IPGPC_SPORT_MASK) == 0 ||
strcmp(ipgpc_nm, IPGPC_DPORT_MASK) == 0) {
*type = IPQOS_DATA_TYPE_UINT16;
return (IPQOS_CONF_SUCCESS);
} else if (strcmp(ipgpc_nm, IPGPC_FILTER_TYPE) == 0) {
*type = IPQOS_DATA_TYPE_UINT32;
return (IPQOS_CONF_SUCCESS);
} else if (strcmp(ipgpc_nm, IPGPC_IF_INDEX) == 0) {
*type = IPQOS_DATA_TYPE_IFINDEX;
return (IPQOS_CONF_SUCCESS);
}
free(ipgpc_nm);
}
/*
* read upto and including module version line.
*/
if (read_tfile_ver(tfp, IPQOS_MOD_STR, module_name) == -1)
return (IPQOS_CONF_ERR);
/*
* loop reading lines of the types file until named parameter
* found or EOF.
*/
while (fgets(lbuf, IPQOS_CONF_TYPE_LINE_LEN, tfp) != NULL) {
/*
* check whether blank or commented line; if so skip
*/
for (cp = lbuf; isspace(*cp) && *cp != '\0'; cp++) {}
if (*cp == '\0' || *cp == '#') {
continue;
}
dfltst[0] = '\0';
/*
* read place, param, type and if present default str
* from line.
*/
ac = sscanf(lbuf,
"%" VAL2STR(IPQOS_CONF_TYPE_LEN) "s "
"%" VAL2STR(IPQOS_CONF_PNAME_LEN) "s "
"%" VAL2STR(IPQOS_CONF_TYPE_LEN) "s "
"%" VAL2STR(IPQOS_VALST_MAXLEN) "s",
place_st, param, typest, dfltst);
if (ac < 3) {
ipqos_msg(MT_ERROR,
gettext("Types file for module %s is corrupt.\n"),
module_name);
IPQOSCDBG0(L0, "sscanf failed to read 3 strings.\n");
return (IPQOS_CONF_ERR);
}
/*
* if the place and name match no need to look any further.
*/
if ((*place == PL_ANY) ||
((*place == PL_PARAMS) &&
strcmp(place_st, IPQOS_PLACE_PRM_STR) == 0) ||
((*place == PL_FILTER) &&
strcmp(place_st, IPQOS_PLACE_FILTER_STR) == 0) ||
((*place == PL_MAP) &&
strcmp(place_st, IPQOS_PLACE_MAP_STR) == 0)) {
if (strcmp(param, name) == 0) {
found++;
break;
}
}
}
if (found == 0) {
ipqos_msg(MT_ERROR,
gettext("Invalid parameter, %s, line %u.\n"), name,
lineno);
return (IPQOS_CONF_ERR);
}
/*
* set the place parameter to the actual place when the PL_ANY flag
* was set.
*/
if (*place == PL_ANY) {
if (strcmp(place_st, IPQOS_PLACE_PRM_STR) == 0) {
*place = PL_PARAMS;
} else if (strcmp(place_st, IPQOS_PLACE_FILTER_STR) == 0) {
*place = PL_FILTER;
} else if (strcmp(place_st, IPQOS_PLACE_MAP_STR) == 0) {
*place = PL_MAP;
}
}
/*
* get type enumeration
*/
for (x = 0; nv_types[x].string[0]; x++) {
if (strcmp(nv_types[x].string, typest) == 0) {
break;
}
}
/*
* check that we have a type corresponding with the one the types
* file specifies.
*/
if (nv_types[x].string[0] == '\0') {
ipqos_msg(MT_ERROR,
gettext("Types file for module %s is corrupt.\n"),
module_name);
return (IPQOS_CONF_ERR);
}
*type = nv_types[x].value;
/*
* if enumeration type get set of name/vals and any default value
*/
if (*type == IPQOS_DATA_TYPE_ENUM) {
*enum_nvps = read_enum_nvs(lbuf, module_name);
if (*enum_nvps == NULL) {
return (IPQOS_CONF_ERR);
}
dfltst[0] = '\0';
cp = strchr(lbuf, CURL_END);
(void) sscanf(++cp,
"%" VAL2STR(IPQOS_VALST_MAXLEN) "s", dfltst);
}
IPQOSCDBG2(L1, "read type: %s default: %s\n", nv_types[x].string,
*dfltst ? dfltst : "None");
return (IPQOS_CONF_SUCCESS);
}
/*
* Reads a name and a value from file ref'd by cfp into list indirectly
* ref'd by nvlp; If this list is NULL it will be created to accomodate
* the name/value. The name must be either a special token for
* for the place, or be present in the module types file ref'd by tfp.
* *type is set to the enumeration of the type of the parameter and
* nvp to point at the element with the nvlp ref'd list.
* RETURNS: IPQOS_CONF_CURL_END if read CURL_END as name,
* IPQOS_CONF_ERR on errors, else IPQOS_CONF_SUCCESS.
*/
static int
readnvpair(
FILE *cfp,
FILE *tfp,
nvlist_t **nvlp,
nvpair_t **nvp,
ipqos_nvtype_t *type,
place_t place,
char *module_name)
{
char *name = NULL;
char *valst = NULL;
int res;
char *tmp;
str_val_nd_t *enum_nvs = NULL;
char dfltst[IPQOS_VALST_MAXLEN+1];
IPQOSCDBG0(L1, "in readnvpair\n");
/*
* read nvpair name
*/
res = readtoken(cfp, &name);
/*
* if reached eof, curl end or error encountered return to caller
*/
if (res == IPQOS_CONF_EOF) {
ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n"));
return (IPQOS_CONF_ERR);
} else if (res == IPQOS_CONF_ERR) {
return (res);
} else if (res == IPQOS_CONF_CURL_END) {
free(name);
return (res);
}
/*
* read nvpair value
*/
res = readtoken(cfp, &valst);
/*
* check we've read a valid value
*/
if (res != IPQOS_CONF_SUCCESS && res != IPQOS_CONF_CURL_BEGIN) {
if (res == IPQOS_CONF_EOF) {
ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n"));
} else if (res == IPQOS_CONF_CURL_END) {
ipqos_msg(MT_ERROR,
gettext("Missing parameter value line %u.\n"),
lineno);
free(valst);
} /* we do nothing special for IPQOS_CONF_ERR */
free(name);
return (IPQOS_CONF_ERR);
}
/*
* check for generic parameters.
*/
if ((place == PL_CLASS) &&
strcmp(name, IPQOS_CONF_NEXT_ACTION_STR) == 0) {
*type = IPQOS_DATA_TYPE_ACTION;
} else if (place == PL_PARAMS &&
strcmp(name, IPQOS_CONF_GLOBAL_STATS_STR) == 0 ||
place == PL_CLASS &&
strcmp(name, IPQOS_CONF_STATS_ENABLE_STR) == 0) {
*type = IPQOS_DATA_TYPE_BOOLEAN;
} else if (tfp == NULL ||
((place != PL_PARAMS) && strcmp(name, IPQOS_CONF_NAME_STR) == 0) ||
(place == PL_FILTER) && (strcmp(name, IPQOS_CONF_CLASS_STR) ==
0) ||
(place == PL_ACTION) && (strcmp(name, IPQOS_CONF_MODULE_STR) ==
0)) {
*type = IPQOS_DATA_TYPE_STRING;
} else { /* if not generic parameter */
/*
* get type from types file
*/
if (readtype(tfp, module_name, name, type, &enum_nvs, dfltst,
B_FALSE, &place) != IPQOS_CONF_SUCCESS) {
free(name);
free(valst);
return (IPQOS_CONF_ERR);
}
/*
* get full module prefix parameter name
*/
tmp = name;
if ((name = prepend_module_name(name, module_name)) == NULL) {
name = tmp;
goto fail;
}
free(tmp);
}
IPQOSCDBG3(L1, "NVP, name: %s, str_value: %s, type: %s\n", name,
valst, nv_types[*type].string);
/*
* create nvlist if not present already
*/
if (*nvlp == NULL) {
res = nvlist_alloc(nvlp, NV_UNIQUE_NAME, 0);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_alloc");
free(name);
free(valst);
return (IPQOS_CONF_ERR);
}
}
/*
* check we haven't already read this parameter
*/
if (find_nvpair(*nvlp, name)) {
ipqos_msg(MT_ERROR, gettext("Duplicate parameter line %u.\n"),
lineno);
goto fail;
}
/*
* convert value string to appropriate type and add to nvlist
*/
switch (*type) {
case IPQOS_DATA_TYPE_IFNAME: {
uint32_t ifidx;
res = readifindex(valst, (int *)&ifidx);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_uint32(*nvlp, IPGPC_IF_INDEX,
ifidx);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint32");
goto fail;
}
(void) nvlist_remove_all(*nvlp, name);
/*
* change name to point at the name of the
* new ifindex nvlist entry as name is used
* later in the function.
*/
free(name);
name = malloc(strlen(IPGPC_IF_INDEX) + 1);
if (name == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
goto fail;
}
(void) strcpy(name, IPGPC_IF_INDEX);
}
break;
}
case IPQOS_DATA_TYPE_PROTO: {
uint8_t proto;
res = readproto(valst, &proto);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_byte(*nvlp, name, proto);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
goto fail;
}
}
break;
}
case IPQOS_DATA_TYPE_PORT: {
uint16_t port;
res = readport(valst, &port);
if (res == IPQOS_CONF_SUCCESS) {
/* add port */
res = nvlist_add_uint16(*nvlp, name, port);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint16");
goto fail;
}
/* add appropriate all ones port mask */
if (strcmp(name, IPGPC_DPORT) == 0) {
res = nvlist_add_uint16(*nvlp,
IPGPC_DPORT_MASK, ~0);
} else if (strcmp(name, IPGPC_SPORT) == 0) {
res = nvlist_add_uint16(*nvlp,
IPGPC_SPORT_MASK, ~0);
}
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint16");
goto fail;
}
}
break;
}
case IPQOS_DATA_TYPE_ADDRESS:
case IPQOS_DATA_TYPE_ACTION:
case IPQOS_DATA_TYPE_STRING:
res = nvlist_add_string(*nvlp, name, valst);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_string");
goto fail;
}
break;
case IPQOS_DATA_TYPE_BOOLEAN: {
boolean_t b;
res = readbool(valst, &b);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_uint32(*nvlp, name,
(uint32_t)b);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint32");
goto fail;
}
}
break;
}
case IPQOS_DATA_TYPE_UINT8: {
uint8_t u8;
res = readuint8(valst, &u8, &tmp);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_byte(*nvlp, name, u8);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
goto fail;
}
}
break;
}
case IPQOS_DATA_TYPE_INT16: {
int16_t i16;
res = readint16(valst, &i16, &tmp);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_int16(*nvlp, name, i16);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_int16");
goto fail;
}
}
break;
}
case IPQOS_DATA_TYPE_UINT16: {
uint16_t u16;
res = readuint16(valst, &u16, &tmp);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_uint16(*nvlp, name, u16);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_int16");
goto fail;
}
}
break;
}
case IPQOS_DATA_TYPE_INT32: {
int i32;
res = readint32(valst, &i32, &tmp);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_int32(*nvlp, name, i32);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_int32");
goto fail;
}
}
break;
}
case IPQOS_DATA_TYPE_UINT32: {
uint32_t u32;
res = readuint32(valst, &u32, &tmp);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_uint32(*nvlp, name, u32);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint32");
goto fail;
}
}
break;
}
case IPQOS_DATA_TYPE_ENUM: {
uint32_t val;
res = read_enum_value(cfp, valst, enum_nvs, &val);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_uint32(*nvlp, name, val);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint32");
goto fail;
}
} else {
goto fail;
}
break;
}
/*
* For now the dfltst contains a comma separated list of the
* type we need this parameter to be mapped to.
* read_mapped_values will fill in all the mapped parameters
* and their values in the nvlist.
*/
case IPQOS_DATA_TYPE_M_INDEX: {
uint8_t u8;
res = readuint8(valst, &u8, &tmp);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_byte(*nvlp, name, u8);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint8");
goto fail;
}
} else {
*type = IPQOS_DATA_TYPE_UINT8;
break;
}
res = read_mapped_values(tfp, nvlp, module_name,
dfltst, u8);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
break;
}
case IPQOS_DATA_TYPE_INT_ARRAY: {
str_val_nd_t *arr_enum_nvs = NULL;
uint32_t size;
int llimit = 0, ulimit = 0;
int *arr;
/*
* read array info from types file.
*/
res = read_int_array_info(dfltst, &arr_enum_nvs, &size,
&llimit, &ulimit, module_name);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/*
* read array contents from config file and construct
* array with them.
*/
res = read_int_array(cfp, valst, &arr, size, llimit,
ulimit, arr_enum_nvs);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/*
* add array to nvlist.
*/
res = nvlist_add_int32_array(*nvlp, name, arr, size);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_int32");
goto fail;
}
/*
* free uneeded resources.
*/
free(arr);
if (arr_enum_nvs)
free_str_val_entrys(arr_enum_nvs);
break;
}
case IPQOS_DATA_TYPE_USER: {
uid_t uid;
res = readuser(valst, &uid);
if (res == IPQOS_CONF_SUCCESS) {
res = nvlist_add_int32(*nvlp, name, (int)uid);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_int32");
goto fail;
}
}
break;
}
#ifdef _IPQOS_CONF_DEBUG
default: {
/*
* we shouldn't have a type that doesn't have a switch
* entry.
*/
assert(1);
}
#endif
}
if (res != 0) {
ipqos_msg(MT_ERROR, gettext("Invalid %s, line %u.\n"),
nv_types[*type].string, lineno);
goto fail;
}
/* set the nvp parameter to point at the newly added nvlist entry */
*nvp = find_nvpair(*nvlp, name);
free(name);
free(valst);
if (enum_nvs)
free_str_val_entrys(enum_nvs);
return (IPQOS_CONF_SUCCESS);
fail:
if (name != NULL)
free(name);
if (valst != NULL)
free(valst);
if (enum_nvs != NULL)
free_str_val_entrys(enum_nvs);
return (IPQOS_CONF_ERR);
}
/*
* read a parameter clause from cfp into *params.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
readparams(
FILE *cfp,
FILE *tfp,
char *module_name,
ipqos_conf_params_t *params)
{
int res;
nvpair_t *nvp;
ipqos_nvtype_t type;
boolean_t bl;
char *nm;
char *action;
char tmp[IPQOS_CONF_PNAME_LEN];
int read_stats = 0;
IPQOSCDBG0(L0, "in readparams\n");
/* read beginning curl */
res = read_curl_begin(cfp);
if (res != IPQOS_CONF_SUCCESS) {
return (res);
}
/*
* loop reading nvpairs, adding to params nvlist until encounter
* CURL_END.
*/
for (;;) {
/* read nvpair */
res = readnvpair(cfp, tfp, &params->nvlist,
&nvp, &type, PL_PARAMS, module_name);
if (res == IPQOS_CONF_ERR) {
goto fail;
/* we have finished reading params */
} else if (res == IPQOS_CONF_CURL_END) {
break;
}
/*
* read global stats - place into params struct and remove
* from nvlist.
*/
if (strcmp(nvpair_name(nvp), IPQOS_CONF_GLOBAL_STATS_STR) ==
0) {
/* check we haven't read stats before */
if (read_stats) {
ipqos_msg(MT_ERROR,
gettext("Duplicate parameter line %u.\n"),
lineno);
goto fail;
}
read_stats++;
(void) nvpair_value_uint32(nvp, (uint32_t *)&bl);
params->stats_enable = bl;
(void) nvlist_remove_all(params->nvlist,
IPQOS_CONF_GLOBAL_STATS_STR);
/*
* read action type parameter - add it to list of action refs.
* also, if it's one of continue or drop virtual actions
* change the action name to their special ipp names in
* the action ref list and the nvlist.
*/
} else if (type == IPQOS_DATA_TYPE_ACTION) {
/* get name and value from nvlist */
nm = nvpair_name(nvp);
(void) nvpair_value_string(nvp, &action);
/* if virtual action names change to ipp name */
if ((strcmp(action, IPQOS_CONF_CONT_STR) == 0) ||
strcmp(action, IPQOS_CONF_DROP_STR) == 0) {
/*
* we copy nm to a seperate buffer as nv_pair
* name above gave us a ptr to internal
* memory which causes strange behaviour
* when we re-value that nvlist element.
*/
(void) strlcpy(tmp, nm, sizeof (tmp));
nm = tmp;
/* modify nvlist entry and change action */
if (strcmp(action, IPQOS_CONF_CONT_STR) == 0) {
action = IPP_ANAME_CONT;
res = nvlist_add_string(params->nvlist,
nm, action);
} else {
action = IPP_ANAME_DROP;
res = nvlist_add_string(params->nvlist,
nm, action);
}
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_string");
goto fail;
}
}
/* add action reference to params */
res = add_aref(&params->actions, nm, action);
}
}
return (IPQOS_CONF_SUCCESS);
fail:
if (params->nvlist) {
nvlist_free(params->nvlist);
params->nvlist = NULL;
}
if (params->actions) {
free_arefs(params->actions);
params->actions = NULL;
}
return (IPQOS_CONF_ERR);
}
/* ************************* class manip fns ****************************** */
/*
* make dst point at a dupicate class struct with duplicate elements to src.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
dup_class(
ipqos_conf_class_t *src,
ipqos_conf_class_t **dst)
{
ipqos_conf_class_t *cls;
int res;
IPQOSCDBG1(DIFF, "In dup_class: class: %s\n", src->name);
cls = alloc_class();
if (cls == NULL) {
return (IPQOS_CONF_ERR);
}
/* struct copy */
*cls = *src;
/* we're not interested in the nvlist for a class */
cls->nvlist = NULL;
/* copy first action reference */
cls->alist = NULL;
res = add_aref(&cls->alist, src->alist->field, src->alist->name);
if (res != IPQOS_CONF_SUCCESS) {
free(cls);
return (res);
}
*dst = cls;
return (IPQOS_CONF_SUCCESS);
}
/*
* create a zero'd class struct and return a ptr to it.
* RETURNS: ptr to struct on success, NULL otherwise.
*/
static ipqos_conf_class_t *
alloc_class()
{
ipqos_conf_class_t *class;
class = malloc(sizeof (ipqos_conf_class_t));
if (class) {
bzero(class, sizeof (ipqos_conf_class_t));
} else {
ipqos_msg(MT_ENOSTR, "malloc");
}
return (class);
}
/* frees up all memory occupied by a filter struct and its contents. */
static void
free_class(ipqos_conf_class_t *cls)
{
if (cls == NULL)
return;
/* free its nvlist if present */
nvlist_free(cls->nvlist);
/* free its action refs if present */
if (cls->alist)
free_arefs(cls->alist);
/* finally free class itself */
free(cls);
}
/*
* Checks whether there is a class called class_nm in classes list.
* RETURNS: ptr to first matched class, else if not matched NULL.
*/
static ipqos_conf_class_t *
classexist(
char *class_nm,
ipqos_conf_class_t *classes)
{
ipqos_conf_class_t *cls;
IPQOSCDBG1(L1, "In classexist: name: %s\n", class_nm);
for (cls = classes; cls; cls = cls->next) {
if (strcmp(class_nm, cls->name) == 0) {
break;
}
}
return (cls);
}
/* ************************** filter manip fns **************************** */
/*
* Checks whether there is a filter called filter_nm with instance number
* instance in filters list created by us or permanent. Instance value -1
* is a wildcard.
* RETURNS: ptr to first matched filter, else if not matched NULL.
*/
static ipqos_conf_filter_t *
filterexist(
char *filter_nm,
int instance,
ipqos_conf_filter_t *filters)
{
IPQOSCDBG2(L1, "In filterexist: name :%s, inst: %d\n", filter_nm,
instance);
while (filters) {
if (strcmp(filters->name, filter_nm) == 0 &&
(instance == -1 || filters->instance == instance) &&
(filters->originator == IPP_CONFIG_IPQOSCONF ||
filters->originator == IPP_CONFIG_PERMANENT)) {
break;
}
filters = filters->next;
}
return (filters);
}
/*
* allocate and zero a filter structure.
* RETURNS: NULL on error, else ptr to filter struct.
*/
static ipqos_conf_filter_t *
alloc_filter()
{
ipqos_conf_filter_t *flt;
flt = malloc(sizeof (ipqos_conf_filter_t));
if (flt) {
bzero(flt, sizeof (ipqos_conf_filter_t));
flt->instance = -1;
} else {
ipqos_msg(MT_ENOSTR, "malloc");
}
return (flt);
}
/* free flt and all it's contents. */
static void
free_filter(ipqos_conf_filter_t *flt)
{
IPQOSCDBG2(L1, "In free_filter: filter: %s, inst: %d\n", flt->name,
flt->instance);
if (flt == NULL)
return;
if (flt->src_nd_name)
free(flt->src_nd_name);
if (flt->dst_nd_name)
free(flt->dst_nd_name);
if (flt->nvlist) {
nvlist_free(flt->nvlist);
}
free(flt);
}
/*
* makes a copy of ofilter and its contents and points nfilter at it. It
* also adds an instance number to the filter and if either saddr or
* daddr are non-null that address to the filters nvlist along with
* an all 1s address mask and the af.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
dup_filter(
ipqos_conf_filter_t *ofilter,
ipqos_conf_filter_t **nfilter,
int af,
int inv6, /* if saddr or daddr set and v4 filter are they in v6 addr */
void *saddr,
void *daddr,
int inst)
{
ipqos_conf_filter_t *nf;
int res;
in6_addr_t v6addr;
in6_addr_t all_1s_v6;
IPQOSCDBG4(MHME, "In dup_filter: name: %s, af: %u, inv6: %u, ins: %d\n",
ofilter->name, af, inv6, inst);
/* show src address and dst address if present */
#ifdef _IPQOS_CONF_DEBUG
if (ipqosconf_dbg_flgs & MHME) {
char st[100];
if (saddr) {
(void) fprintf(stderr, "saddr: %s\n",
inet_ntop(inv6 ? AF_INET6 : AF_INET, saddr, st,
100));
}
if (daddr) {
(void) fprintf(stderr, "daddr: %s\n",
inet_ntop(inv6 ? AF_INET6 : AF_INET, daddr, st,
100));
}
}
#endif /* _IPQOS_CONF_DEBUG */
/* init local v6 address to 0 */
(void) bzero(&v6addr, sizeof (in6_addr_t));
/* create an all 1s address for use as mask */
(void) memset(&all_1s_v6, ~0, sizeof (in6_addr_t));
/* create a new filter */
nf = alloc_filter();
if (nf == NULL) {
return (IPQOS_CONF_ERR);
}
/* struct copy old filter to new */
*nf = *ofilter;
/* copy src filters nvlist if there is one to copy */
if (ofilter->nvlist) {
res = nvlist_dup(ofilter->nvlist, &nf->nvlist, 0);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_dup");
goto fail;
}
}
/* copy src and dst node names if present */
if (ofilter->src_nd_name) {
nf->src_nd_name = malloc(strlen(ofilter->src_nd_name) + 1);
if (nf->src_nd_name == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
goto fail;
}
(void) strcpy(nf->src_nd_name, ofilter->src_nd_name);
}
if (ofilter->dst_nd_name) {
nf->dst_nd_name = malloc(strlen(ofilter->dst_nd_name) + 1);
if (nf->dst_nd_name == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
goto fail;
}
(void) strcpy(nf->dst_nd_name, ofilter->dst_nd_name);
}
/* add filter addresses type */
res = nvlist_add_byte(nf->nvlist, IPGPC_FILTER_TYPE,
af == AF_INET ? IPGPC_V4_FLTR : IPGPC_V6_FLTR);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_byte");
goto fail;
}
IPQOSCDBG1(MHME, "adding address type %s in dup filter\n",
af == AF_INET ? "AF_INET" : "AF_INET6");
/* add saddr if present */
if (saddr) {
if (af == AF_INET && !inv6) {
V4_PART_OF_V6(v6addr) = *(uint32_t *)saddr;
saddr = &v6addr;
}
/* add address and all 1's mask */
if (nvlist_add_uint32_array(nf->nvlist, IPGPC_SADDR,
(uint32_t *)saddr, 4) != 0 ||
nvlist_add_uint32_array(nf->nvlist, IPGPC_SADDR_MASK,
(uint32_t *)&all_1s_v6, 4) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32_array");
goto fail;
}
}
/* add daddr if present */
if (daddr) {
if (af == AF_INET && !inv6) {
V4_PART_OF_V6(v6addr) = *(uint32_t *)daddr;
daddr = &v6addr;
}
/* add address and all 1's mask */
if (nvlist_add_uint32_array(nf->nvlist, IPGPC_DADDR,
(uint32_t *)daddr, 4) != 0 ||
nvlist_add_uint32_array(nf->nvlist, IPGPC_DADDR_MASK,
(uint32_t *)&all_1s_v6, 4) != 0) {
ipqos_msg(MT_ENOSTR, "nvlist_add_uint32_array");
goto fail;
}
}
/* add filter instance */
nf->instance = inst;
*nfilter = nf;
return (IPQOS_CONF_SUCCESS);
fail:
free_filter(nf);
return (IPQOS_CONF_ERR);
}
/* ************************* action manip fns ********************** */
/*
* create and zero action structure and a params structure hung off of it.
* RETURNS: ptr to allocated action on success, else NULL.
*/
static ipqos_conf_action_t *
alloc_action()
{
ipqos_conf_action_t *action;
action = (ipqos_conf_action_t *)malloc(sizeof (ipqos_conf_action_t));
if (action == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (action);
}
bzero(action, sizeof (ipqos_conf_action_t));
action->params = (ipqos_conf_params_t *)
malloc(sizeof (ipqos_conf_params_t));
if (action->params == NULL) {
free(action);
return (NULL);
}
bzero(action->params, sizeof (ipqos_conf_params_t));
action->params->stats_enable = B_FALSE;
return (action);
}
/*
* free all the memory used in all the actions in actions list.
*/
static void
free_actions(
ipqos_conf_action_t *actions)
{
ipqos_conf_action_t *act = actions;
ipqos_conf_action_t *next;
ipqos_conf_filter_t *flt, *nf;
ipqos_conf_class_t *cls, *nc;
while (act != NULL) {
/* free parameters */
if (act->params != NULL) {
free_arefs(act->params->actions);
if (act->params->nvlist != NULL) {
nvlist_free(act->params->nvlist);
}
free(act->params);
}
/* free action nvlist */
if (act->nvlist != NULL)
free(act->nvlist);
/* free filters */
flt = act->filters;
while (flt != NULL) {
nf = flt->next;
free_filter(flt);
flt = nf;
}
/* free classes */
cls = act->classes;
while (cls != NULL) {
nc = cls->next;
free_class(cls);
cls = nc;
}
/* free permanent classes table */
cleanup_string_table(act->perm_classes, act->num_perm_classes);
/* free filters to retry */
flt = act->retry_filters;
while (flt != NULL) {
nf = flt->next;
free_filter(flt);
flt = nf;
}
/* free dependency pointers */
free_arefs(act->dependencies);
next = act->next;
free(act);
act = next;
}
}
/*
* Checks whether there is an action called action_name in actions list.
* RETURNS: ptr to first matched action, else if not matched NULL.
*
*/
static ipqos_conf_action_t *
actionexist(
char *action_name,
ipqos_conf_action_t *actions)
{
IPQOSCDBG1(L1, "In actionexist: name: %s\n", action_name);
while (actions) {
if (strcmp(action_name, actions->name) == 0) {
break;
}
actions = actions->next;
}
return (actions);
}
/* **************************** act ref manip fns ******************** */
/*
* add an action reference element with parameter field and action
* action_name to arefs.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
add_aref(
ipqos_conf_act_ref_t **arefs,
char *field,
char *action_name)
{
ipqos_conf_act_ref_t *aref;
IPQOSCDBG1(L1, "add_aref: action: %s.\n", action_name);
/* allocate zero'd aref */
aref = malloc(sizeof (ipqos_conf_act_ref_t));
if (aref == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (IPQOS_CONF_ERR);
}
(void) bzero(aref, sizeof (ipqos_conf_act_ref_t));
/* copy parameter name if present */
if (field)
(void) strlcpy(aref->field, field, IPQOS_CONF_PNAME_LEN);
/* copy action name */
(void) strlcpy(aref->name, action_name, IPQOS_CONF_NAME_LEN);
/* place at head of list */
aref->next = *arefs;
*arefs = aref;
return (IPQOS_CONF_SUCCESS);
}
/*
* free all the memory used by the action references in arefs.
*/
static void
free_arefs(
ipqos_conf_act_ref_t *arefs)
{
ipqos_conf_act_ref_t *aref = arefs;
ipqos_conf_act_ref_t *next;
while (aref) {
nvlist_free(aref->nvlist);
next = aref->next;
free(aref);
aref = next;
}
}
/* *************************************************************** */
/*
* checks whether aname is a valid action name.
* RETURNS: IPQOS_CONF_ERR if invalid, else IPQOS_CONF_SUCCESS.
*/
static int
valid_aname(char *aname)
{
/*
* dissallow the use of the name of a virtual action, either
* the ipqosconf name, or the longer ipp names.
*/
if (strcmp(aname, IPQOS_CONF_CONT_STR) == 0 ||
strcmp(aname, IPQOS_CONF_DEFER_STR) == 0 ||
strcmp(aname, IPQOS_CONF_DROP_STR) == 0 ||
virtual_action(aname)) {
ipqos_msg(MT_ERROR, gettext("Invalid action name line %u.\n"),
lineno);
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Opens a stream to the types file for module module_name (assuming
* that the file path is TYPES_FILE_DIR/module_name.types). if
* a file open failure occurs, *openerr is set to 1.
* RETURNS: NULL on error, else stream ptr to module types file.
*/
static FILE *
validmod(
char *module_name,
int *openerr)
{
FILE *fp;
char *path;
IPQOSCDBG1(L1, "In validmod: module_name: %s\n", module_name);
*openerr = 0;
/* create modules type file path */
path = malloc(strlen(TYPES_FILE_DIR) + strlen(module_name) +
strlen(".types") + 1);
if (path == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (NULL);
}
(void) strcpy(path, TYPES_FILE_DIR);
(void) strcat(path, module_name);
(void) strcat(path, ".types");
IPQOSCDBG1(L1, "opening file %s\n", path);
/* open stream to types file */
fp = fopen(path, "r");
if (fp == NULL) {
(*openerr)++;
}
free(path);
return (fp);
}
/*
* read a class clause from cfp into a class struct and point class at this.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
readclass(
FILE *cfp,
char *module_name,
ipqos_conf_class_t **class,
char **perm_classes,
int num_perm_classes)
{
int nm, act;
int res;
nvpair_t *nvp;
ipqos_nvtype_t type;
char *name;
char *action;
int stats;
IPQOSCDBG0(L0, "in readclass\n");
/* create and zero class struct */
*class = alloc_class();
if (!*class) {
return (IPQOS_CONF_ERR);
}
(*class)->originator = IPP_CONFIG_IPQOSCONF;
/* get starting line for error reporting */
(*class)->lineno = lineno;
/* read curl_begin */
res = read_curl_begin(cfp);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* loop reading parameters till read curl_end */
stats = nm = act = 0;
for (;;) {
/* read nvpair */
res = readnvpair(cfp, NULL, &(*class)->nvlist,
&nvp, &type, PL_CLASS, module_name);
if (res == IPQOS_CONF_ERR) {
goto fail;
/* reached end of class clause */
} else if (res == IPQOS_CONF_CURL_END) {
break;
}
/*
* catch name and action nv pairs and stats if present
* and place values in class structure.
*/
/* name */
if (nm == 0 &&
strcmp(nvpair_name(nvp), IPQOS_CONF_NAME_STR) == 0) {
(void) nvpair_value_string(nvp, &name);
if (valid_name(name) != IPQOS_CONF_SUCCESS) {
goto fail;
}
(void) strcpy((*class)->name, name);
nm++;
/* next action */
} else if (act == 0 &&
strcmp(nvpair_name(nvp), IPQOS_CONF_NEXT_ACTION_STR) == 0) {
(void) nvpair_value_string(nvp, &action);
/*
* if next action string continue string set action to
* IPP_ANAME_CONT, else if drop string IPP_ANAME_DROP
*/
if (strcmp(action, IPQOS_CONF_CONT_STR) == 0) {
action = IPP_ANAME_CONT;
} else if (strcmp(action, IPQOS_CONF_DROP_STR) == 0) {
action = IPP_ANAME_DROP;
}
/* add an action reference to action list */
res = add_aref(&(*class)->alist,
IPQOS_CONF_NEXT_ACTION_STR, action);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
act++;
/* class stats enable */
} else if (stats == 0 &&
strcmp(nvpair_name(nvp), IPQOS_CONF_STATS_ENABLE_STR) ==
0) {
boolean_t bl;
(void) nvpair_value_uint32(nvp, (uint32_t *)&bl);
(*class)->stats_enable = bl;
stats++;
/* no other / duplicate parameters allowed */
} else {
ipqos_msg(MT_ERROR,
gettext("Unexpected parameter line %u.\n"), lineno);
goto fail;
}
}
if (nm == 0 || act == 0) {
ipqos_msg(MT_ERROR,
gettext("Missing class name/next action before line %u.\n"),
lineno);
goto fail;
}
/* change class originator field to permanent if permanent class */
if (in_string_table(perm_classes, num_perm_classes, (*class)->name)) {
IPQOSCDBG1(L0, "Setting class %s as permanent.\n", (*class)->name);
(*class)->originator = IPP_CONFIG_PERMANENT;
}
return (IPQOS_CONF_SUCCESS);
fail:
if (*class)
free_class(*class);
return (IPQOS_CONF_ERR);
}
/*
* This function assumes either src_nd_name or dst_node_nm are set in filter.
*
* Creates one of more copies of filter according to the ip versions
* requested (or assumed) and the resolution of the src and dst address
* node names if spec'd. If both node names are spec'd then a filter is
* created for each pair of addresses (one from each node name) that is
* compatible with the chosen address family, otherwise a filter copy is
* created for just each address of the single node name that is
* compatible.
* If filter->ip_versions has been set that is used to determine the
* af's we will create filters for, else if a numeric address was
* added the family of that will be used, otherwise we fall back
* to both v4 and v6 addresses.
*
* Any name lookup failures that occur are checked to see whether the failure
* was a soft or hard failure and the nlerr field of filter set accordingly
* before the error is returned.
*
* RETURNS: IPQOS_CONF_ERR on any error, else IPQOS_CONF_SUCCESS.
*/
static int
domultihome(
ipqos_conf_filter_t *filter,
ipqos_conf_filter_t **flist,
boolean_t last_retry)
{
uint32_t ftype;
int v4 = 1, v6 = 1; /* default lookup family is v4 and v6 */
int saf, daf;
struct hostent *shp = NULL;
struct hostent *dhp = NULL;
in6_addr_t daddr, saddr;
int idx = 0;
ipqos_conf_filter_t *nfilter;
int res;
int ernum;
int in32b = 0;
char **sp, **dp;
IPQOSCDBG3(MHME, "In domultihome: filter: %s, src_node: %s, "
"dst_node: %s\n", filter->name,
(filter->src_nd_name ? filter->src_nd_name : "NULL"),
(filter->dst_nd_name ? filter->dst_nd_name : "NULL"));
/* check if we've read an ip_version request to get the versions */
if (filter->ip_versions != 0) {
v4 = VERSION_IS_V4(filter);
v6 = VERSION_IS_V6(filter);
/* otherwise check if we've read a numeric address and get versions */
} else if (nvlist_lookup_uint32(filter->nvlist, IPGPC_FILTER_TYPE,
&ftype) == 0) {
if (ftype == IPGPC_V4_FLTR) {
v6--;
} else {
v4--;
}
}
/* read saddrs if src node name */
if (filter->src_nd_name) {
/* v4 only address */
if (v4 && !v6) {
in32b++;
shp = getipnodebyname(filter->src_nd_name, AF_INET,
AI_ADDRCONFIG, &ernum);
/* v6 only */
} else if (v6 && !v4) {
shp = getipnodebyname(filter->src_nd_name, AF_INET6,
AI_DEFAULT, &ernum);
/* v4 and v6 */
} else if (v6 && v4) {
shp = getipnodebyname(filter->src_nd_name, AF_INET6,
AI_DEFAULT|AI_ALL, &ernum);
}
#ifdef TESTING_RETRY
if (!last_retry) {
filter->nlerr = IPQOS_LOOKUP_RETRY;
goto fail;
}
#endif
/*
* if lookup error determine whether it was a soft or hard
* failure and mark as such in filter.
*/
if (shp == NULL) {
if (ernum != TRY_AGAIN) {
ipqos_msg(MT_ERROR, gettext("Failed to "
"resolve src host name for filter at "
"line %u, ignoring filter.\n"),
filter->lineno);
filter->nlerr = IPQOS_LOOKUP_FAIL;
} else {
if (last_retry) {
ipqos_msg(MT_ERROR, gettext("Failed "
"to resolve src host name for "
"filter at line %u, ignoring "
"filter.\n"), filter->lineno);
}
filter->nlerr = IPQOS_LOOKUP_RETRY;
}
goto fail;
}
}
/* read daddrs if dst node name */
if (filter->dst_nd_name) {
/* v4 only address */
if (v4 && !v6) {
in32b++;
dhp = getipnodebyname(filter->dst_nd_name, AF_INET,
AI_ADDRCONFIG, &ernum);
/* v6 only */
} else if (v6 && !v4) {
dhp = getipnodebyname(filter->dst_nd_name, AF_INET6,
AI_DEFAULT, &ernum);
/* v6 and v4 addresses */
} else {
dhp = getipnodebyname(filter->dst_nd_name, AF_INET6,
AI_DEFAULT|AI_ALL, &ernum);
}
if (dhp == NULL) {
if (ernum != TRY_AGAIN) {
ipqos_msg(MT_ERROR, gettext("Failed to "
"resolve dst host name for filter at "
"line %u, ignoring filter.\n"),
filter->lineno);
filter->nlerr = IPQOS_LOOKUP_FAIL;
} else {
if (last_retry) {
ipqos_msg(MT_ERROR, gettext("Failed "
"to resolve dst host name for "
"filter at line %u, ignoring "
"filter.\n"), filter->lineno);
}
filter->nlerr = IPQOS_LOOKUP_RETRY;
}
goto fail;
}
}
/*
* if src and dst node name, create set of filters; one for each
* src and dst address of matching types.
*/
if (filter->src_nd_name && filter->dst_nd_name) {
for (sp = shp->h_addr_list; *sp != NULL; sp++) {
(void) bcopy(*sp, &saddr, shp->h_length);
/* get saddr family */
if (in32b || IN6_IS_ADDR_V4MAPPED(&saddr)) {
saf = AF_INET;
} else {
saf = AF_INET6;
}
for (dp = dhp->h_addr_list; *dp != NULL; dp++) {
(void) bcopy(*dp, &daddr, dhp->h_length);
/* get daddr family */
if (in32b || IN6_IS_ADDR_V4MAPPED(&daddr)) {
daf = AF_INET;
} else {
daf = AF_INET6;
}
/*
* if saddr and daddr same af duplicate
* filter adding addresses and new instance
* number and add to flist filter list.
*/
if (daf == saf) {
res = dup_filter(filter, &nfilter, saf,
!in32b, &saddr, &daddr, ++idx);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
ADD_TO_LIST(flist, nfilter);
}
}
}
/* if src name only create set of filters, one for each node address */
} else if (filter->src_nd_name) {
for (sp = shp->h_addr_list; *sp != NULL; sp++) {
(void) bcopy(*sp, &saddr, shp->h_length);
/* get af */
if (in32b || IN6_IS_ADDR_V4MAPPED(&saddr)) {
saf = AF_INET;
} else {
saf = AF_INET6;
}
/*
* dup filter adding saddr and new instance num and
* add to flist filter list.
*/
res = dup_filter(filter, &nfilter, saf, !in32b, &saddr,
NULL, ++idx);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
ADD_TO_LIST(flist, nfilter);
}
/* if dname only create set of filters, one for each node address */
} else {
for (dp = dhp->h_addr_list; *dp != NULL; dp++) {
(void) bcopy(*dp, &daddr, dhp->h_length);
/* get af */
if (in32b || IN6_IS_ADDR_V4MAPPED(&daddr)) {
daf = AF_INET;
} else {
daf = AF_INET6;
}
/*
* dup filter adding daddr and new instance num and
* add to flist filter list.
*/
res = dup_filter(filter, &nfilter, daf, !in32b, NULL,
&daddr, ++idx);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
ADD_TO_LIST(flist, nfilter);
}
}
if (shp)
freehostent(shp);
if (dhp)
freehostent(dhp);
return (IPQOS_CONF_SUCCESS);
fail:
/*
* should really clean up any filters that we have created,
* however, free_actions called from readaction will cleam them up.
*/
if (shp)
freehostent(shp);
if (dhp)
freehostent(dhp);
return (IPQOS_CONF_ERR);
}
/*
* read a filter clause from cfp into a filter struct and point filter
* at this.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
readfilter(
FILE *cfp,
FILE *tfp,
char *module_name,
ipqos_conf_filter_t **filter,
char **perm_filters,
int num_perm_filters)
{
int res;
int nm, cls, ipv;
in6_addr_t mask;
char *addr_str;
char *sl = NULL;
in6_addr_t addr;
int sa;
struct hostent *hp;
int err_num;
int v4 = 0, v6 = 0;
uchar_t mlen;
char *tmp;
nvpair_t *nvp;
ipqos_nvtype_t type;
char *name;
char *class;
uchar_t b;
in6_addr_t v6addr;
IPQOSCDBG0(L0, "in readfilter\n");
/* create and zero filter struct */
*filter = alloc_filter();
if (*filter == NULL) {
return (IPQOS_CONF_ERR);
}
(*filter)->originator = IPP_CONFIG_IPQOSCONF;
/* get starting line for error reporting */
(*filter)->lineno = lineno;
/* read beginning curl */
res = read_curl_begin(cfp);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/*
* loop reading nvpairs onto nvlist until encounter CURL_END
*/
ipv = nm = cls = 0;
for (;;) {
/* read nvpair */
res = readnvpair(cfp, tfp, &(*filter)->nvlist,
&nvp, &type, PL_FILTER, module_name);
if (res == IPQOS_CONF_ERR) {
goto fail;
/* reached the end of filter definition */
} else if (res == IPQOS_CONF_CURL_END) {
break;
}
/*
* catch name and class and place value into filter
* structure.
*/
/* read filter name */
if (strcmp(nvpair_name(nvp), IPQOS_CONF_NAME_STR) == 0) {
if (nm != 0) {
ipqos_msg(MT_ERROR,
gettext("Duplicate parameter line %u.\n"),
lineno);
goto fail;
}
(void) nvpair_value_string(nvp, &name);
if (valid_name(name) != IPQOS_CONF_SUCCESS) {
goto fail;
}
(void) strcpy((*filter)->name, name);
(void) nvlist_remove_all((*filter)->nvlist,
IPQOS_CONF_NAME_STR);
nm++;
/* read class name */
} else if (strcmp(nvpair_name(nvp), IPQOS_CONF_CLASS_STR) ==
0) {
if (cls != 0) {
ipqos_msg(MT_ERROR,
gettext("Duplicate parameter line %u.\n"),
lineno);
goto fail;
}
if (nvpair_value_string(nvp, &class) != 0) {
ipqos_msg(MT_ENOSTR, "nvpair_value_string");
break;
}
if (valid_name(class) != IPQOS_CONF_SUCCESS) {
goto fail;
}
(void) strcpy((*filter)->class_name, class);
(void) nvlist_remove_all((*filter)->nvlist,
IPQOS_CONF_CLASS_STR);
cls++;
/*
* if a src or dst ip node name/address. For those that
* are determined to be addresses we convert them from
* strings here and add to the filter nvlist; for node names
* we add the name to the filter struct for readaction to
* process.
*/
} else if (strcmp(nvpair_name(nvp), IPGPC_SADDR) == 0 ||
strcmp(nvpair_name(nvp), IPGPC_DADDR) == 0) {
sa = 0;
if (strcmp(nvpair_name(nvp), IPGPC_SADDR) == 0) {
sa++;
}
(void) nvpair_value_string(nvp, &addr_str);
/*
* get the address mask if present.
* make a copy so that the nvlist element that
* it is part of doesn't dissapear and causes probs.
*/
sl = strchr(addr_str, '/');
if (sl) {
*sl = '\0';
tmp = malloc(strlen(++sl) + 1);
if (tmp == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
goto fail;
}
(void) strcpy(tmp, sl);
sl = tmp;
}
/* if a numeric address */
if (inet_pton(AF_INET, addr_str, &addr) == 1 ||
inet_pton(AF_INET6, addr_str, &addr) == 1) {
/* get address */
hp = getipnodebyname(addr_str, AF_INET6,
AI_DEFAULT, &err_num);
if (hp == NULL) {
ipqos_msg(MT_ENOSTR,
"getipnodebyname");
goto fail;
}
(void) bcopy(hp->h_addr_list[0], &v6addr,
hp->h_length);
freehostent(hp);
/* determine address type */
v4 = IN6_IS_ADDR_V4MAPPED(&v6addr);
if (!v4) {
v6++;
}
/*
* check any previous addresses have same
* version.
*/
if (nvlist_lookup_byte((*filter)->nvlist,
IPGPC_FILTER_TYPE, &b) == 0) {
if (v4 && b != IPGPC_V4_FLTR ||
v6 && b != IPGPC_V6_FLTR) {
ipqos_msg(MT_ERROR,
gettext("Incompatible "
"address version line "
"%u.\n"), lineno);
goto fail;
}
}
/*
* check that if ip_version spec'd it
* corresponds.
*/
if ((*filter)->ip_versions != 0) {
if (v4 && !VERSION_IS_V4(*filter) ||
v6 && !VERSION_IS_V6(*filter)) {
ipqos_msg(MT_ERROR,
gettext("Incompatible "
"address version line %u"
".\n"), lineno);
goto fail;
}
}
/* add the address type */
res = nvlist_add_byte(
(*filter)->nvlist, IPGPC_FILTER_TYPE,
v4 ? IPGPC_V4_FLTR : IPGPC_V6_FLTR);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_byte");
goto fail;
}
/* add address to list */
res = nvlist_add_uint32_array((*filter)->nvlist,
sa ? IPGPC_SADDR : IPGPC_DADDR,
(uint32_t *)&v6addr, 4);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint32_array");
goto fail;
}
/*
* add mask entry in list.
*/
if (sl) { /* have CIDR mask */
char *lo;
res = readuint8(sl, &mlen, &lo);
if (res != IPQOS_CONF_SUCCESS ||
v4 && mlen > 32 ||
!v4 && mlen > 128 ||
mlen == 0) {
ipqos_msg(MT_ERROR,
gettext("Invalid CIDR "
"mask line %u.\n"), lineno);
goto fail;
}
setmask(mlen, &mask,
v4 ? AF_INET : AF_INET6);
free(sl);
} else {
/* no CIDR mask spec'd - use all 1s */
(void) memset(&mask, ~0,
sizeof (in6_addr_t));
}
res = nvlist_add_uint32_array((*filter)->nvlist,
sa ? IPGPC_SADDR_MASK : IPGPC_DADDR_MASK,
(uint32_t *)&mask, 4);
if (res != 0) {
ipqos_msg(MT_ENOSTR,
"nvlist_add_uint32_arr");
goto fail;
}
/* inet_pton returns fail - we assume a node name */
} else {
/*
* doesn't make sense to have a mask
* with a node name.
*/
if (sl) {
ipqos_msg(MT_ERROR,
gettext("Address masks aren't "
"allowed for host names line "
"%u.\n"), lineno);
goto fail;
}
/*
* store node name in filter struct for
* later resolution.
*/
if (sa) {
(*filter)->src_nd_name =
malloc(strlen(addr_str) + 1);
(void) strcpy((*filter)->src_nd_name,
addr_str);
} else {
(*filter)->dst_nd_name =
malloc(strlen(addr_str) + 1);
(void) strcpy((*filter)->dst_nd_name,
addr_str);
}
}
/* ip_version enumeration */
} else if (strcmp(nvpair_name(nvp), IPQOS_CONF_IP_VERSION) ==
0) {
/* check we haven't read ip_version before */
if (ipv) {
ipqos_msg(MT_ERROR,
gettext("Duplicate parameter line %u.\n"),
lineno);
goto fail;
}
ipv++;
/* get bitmask value */
(void) nvpair_value_uint32(nvp,
&(*filter)->ip_versions);
/*
* check that if either ip address is spec'd it
* corresponds.
*/
if (v4 && !VERSION_IS_V4(*filter) ||
v6 && !VERSION_IS_V6(*filter)) {
ipqos_msg(MT_ERROR, gettext("Incompatible "
"address version line %u.\n"), lineno);
goto fail;
}
/* remove ip_version from nvlist */
(void) nvlist_remove_all((*filter)->nvlist,
IPQOS_CONF_IP_VERSION);
}
}
if (nm == 0 || cls == 0) {
ipqos_msg(MT_ERROR, gettext("Missing filter/class name "
"before line %u.\n"), lineno);
goto fail;
}
if (in_string_table(perm_filters, num_perm_filters, (*filter)->name)) {
IPQOSCDBG1(L0, "Setting filter %s as permanent.\n",
(*filter)->name);
(*filter)->originator = IPP_CONFIG_PERMANENT;
}
return (IPQOS_CONF_SUCCESS);
fail:
if (*filter)
free_filter(*filter);
if (hp)
freehostent(hp);
if (sl)
free(sl);
return (IPQOS_CONF_ERR);
}
/*
* reads the curl begin token from cfp stream.
* RETURNS: IPQOS_CONF_ERR if not read successfully, else IPQOS_CONF_SUCCES.
*/
static int
read_curl_begin(FILE *cfp)
{
int res;
char *st;
res = readtoken(cfp, &st);
if (res != IPQOS_CONF_CURL_BEGIN) {
if (res == IPQOS_CONF_EOF) {
ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n"));
/* if CURL_END or something else */
} else if (res != IPQOS_CONF_ERR) {
free(st);
ipqos_msg(MT_ERROR, gettext("\'{\' missing at line "
"%u.\n"), lineno);
}
return (IPQOS_CONF_ERR);
}
free(st);
return (IPQOS_CONF_SUCCESS);
}
/*
* This function parses the parameter string version into a version of the
* form "%u.%u" (as a sscanf format string). It then encodes this into an
* int and returns this encoding.
* RETURNS: -1 if an invalid string, else the integer encoding.
*/
static int
ver_str_to_int(
char *version)
{
uint32_t major, minor;
int ver;
if (sscanf(version, "%u.%u", &major, &minor) != 2) {
IPQOSCDBG0(L0, "Failed to process version number string\n");
return (-1);
}
ver = (int)((major * 10000) + minor);
return (ver);
}
/*
* This function scans through the stream fp line by line looking for
* a line beginning with version_tag and returns a integer encoding of
* the version following it.
*
* RETURNS: If the version definition isn't found or the version is not
* a valid version (%u.%u) then -1 is returned, else an integer encoding
* of the read version.
*/
static int
read_tfile_ver(
FILE *fp,
char *version_tag,
char *module_name)
{
char lbuf[IPQOS_CONF_LINEBUF_SZ];
char buf[IPQOS_CONF_LINEBUF_SZ+1];
char buf2[IPQOS_CONF_LINEBUF_SZ+1];
int found = 0;
int version;
/*
* reset to file start
*/
if (fseek(fp, 0, SEEK_SET) != 0) {
ipqos_msg(MT_ENOSTR, "fseek");
return (-1);
}
/*
* loop reading lines till found the one beginning with version_tag.
*/
while (fgets(lbuf, IPQOS_CONF_LINEBUF_SZ, fp) != NULL) {
if ((sscanf(lbuf,
"%" VAL2STR(IPQOS_CONF_LINEBUF_SZ) "s"
"%" VAL2STR(IPQOS_CONF_LINEBUF_SZ) "s",
buf, buf2) == 2) &&
(strcmp(buf, version_tag) == 0)) {
found++;
break;
}
}
if (found == 0) {
ipqos_msg(MT_ERROR, gettext("Types file for module %s is "
"corrupt.\n"), module_name);
IPQOSCDBG1(L1, "Couldn't find %s in types file\n",
version_tag);
return (-1);
}
/*
* convert version string into int.
*/
if ((version = ver_str_to_int(buf2)) == -1) {
ipqos_msg(MT_ERROR, gettext("Types file for module %s is "
"corrupt.\n"), module_name);
return (-1);
}
return (version);
}
/*
* read action clause and params/classes/filters clauses within and
* store in and hang off an action structure, and point action at it.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
readaction(
FILE *cfp,
ipqos_conf_action_t **action)
{
char *st;
FILE *tfp = NULL;
int nm, md;
int readprms = 0;
int res;
char *strval;
char *name;
nvpair_t *nvp;
ipqos_nvtype_t type;
ipqos_conf_filter_t *filter;
ipqos_conf_class_t *class;
int oe;
char **perm_filters;
int num_perm_filters;
int tf_fmt_ver;
IPQOSCDBG0(L0, "in readaction\n");
res = readtoken(cfp, &st);
if (res == IPQOS_CONF_ERR || res == IPQOS_CONF_EOF) {
return (res);
} else if (strcmp(st, IPQOS_CONF_ACTION_STR) != 0) {
ipqos_msg(MT_ERROR, gettext("Missing %s token line "
"%u.\n"), IPQOS_CONF_ACTION_STR, lineno);
free(st);
return (IPQOS_CONF_ERR);
}
free(st);
/* create action structure */
*action = alloc_action();
if (*action == NULL) {
return (IPQOS_CONF_ERR);
}
(*action)->params->originator = IPP_CONFIG_IPQOSCONF;
/* get starting line for error reporting */
(*action)->lineno = lineno;
/* read beginning curl */
res = read_curl_begin(cfp);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* loop till read both action name and module */
nm = md = 0;
do {
/* read nvpair */
res = readnvpair(cfp, NULL, &(*action)->nvlist, &nvp, &type,
PL_ACTION, NULL);
if (res == IPQOS_CONF_ERR) {
goto fail;
/* read curl_end */
} else if (res == IPQOS_CONF_CURL_END) {
if (nm == 0 || md == 0) {
ipqos_msg(MT_ERROR,
gettext("Missing action name/ module "
"before line %u.\n"), lineno);
goto fail;
}
}
/* store name and module in action structure */
name = nvpair_name(nvp);
/* read action name */
if (nm == 0 && strcmp(name, IPQOS_CONF_NAME_STR) == 0) {
(void) nvpair_value_string(nvp, &strval);
/* check name is valid */
if (valid_name(strval) != IPQOS_CONF_SUCCESS ||
valid_aname(strval) != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* store and remove from list */
(void) strcpy((*action)->name, strval);
/* remove name from nvlist */
(void) nvlist_remove_all((*action)->nvlist,
IPQOS_CONF_NAME_STR);
nm++;
/* read module name */
} else if (md == 0 &&
strcmp(name, IPQOS_CONF_MODULE_STR) == 0) {
/*
* check that module has a type file and get
* open stream to it.
*/
(void) nvpair_value_string(nvp, &strval);
if ((tfp = validmod(strval, &oe)) == NULL) {
if (oe) {
if (errno == ENOENT) {
ipqos_msg(MT_ERROR,
gettext("Invalid "
"module name line %u.\n"),
lineno);
} else {
ipqos_msg(MT_ENOSTR, "fopen");
}
}
goto fail;
}
/*
* move module name to action struct
*/
(void) strlcpy((*action)->module, strval,
IPQOS_CONF_NAME_LEN);
(void) nvlist_remove_all((*action)->nvlist,
IPQOS_CONF_MODULE_STR);
md++;
/* duplicate/other parameter */
} else {
ipqos_msg(MT_ERROR,
gettext("Unexpected parameter line %u.\n"),
lineno);
goto fail;
}
} while (nm == 0 || md == 0);
/*
* check that if the ipgpc action it is named correctly
*/
if ((strcmp((*action)->module, IPGPC_NAME) == 0) &&
(strcmp((*action)->name, IPGPC_CLASSIFY) != 0)) {
ipqos_msg(MT_ERROR,
gettext("%s action has incorrect name line %u.\n"),
IPGPC_NAME, (*action)->lineno);
goto fail;
}
/* get list of permanent classes */
res = read_perm_items(0, tfp, (*action)->module,
&(*action)->perm_classes, &(*action)->num_perm_classes);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* get list of permanent filters */
res = read_perm_items(1, tfp, (*action)->module,
&perm_filters, &num_perm_filters);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/*
* get types file format version and check its supported.
*/
if ((tf_fmt_ver = read_tfile_ver(tfp, IPQOS_FMT_STR,
(*action)->module)) == -1)
goto fail;
if (IPP_MAJOR_MODULE_VER(tf_fmt_ver) > 1 ||
IPP_MINOR_MODULE_VER(tf_fmt_ver) > 0) {
ipqos_msg(MT_ERROR, gettext("Types file for module %s is "
"incompatible.\n"), (*action)->module);
IPQOSCDBG0(L1, "Unsupported fmt major/minor version\n");
goto fail;
}
/*
* get module version
*/
if (((*action)->module_version = read_tfile_ver(tfp, IPQOS_MOD_STR,
(*action)->module)) == -1)
goto fail;
/* read filter/class/params blocks until CURL_END */
for (;;) {
/* read token */
res = readtoken(cfp, &st);
if (res == IPQOS_CONF_ERR) {
goto fail;
} else if (res == IPQOS_CONF_EOF) {
ipqos_msg(MT_ERROR, gettext("Unexpected EOF.\n"));
goto fail;
/* read CURL_END - end of action definition */
} else if (res == IPQOS_CONF_CURL_END) {
free(st);
break;
}
/*
* read in either a filter/class or parameter block.
*/
/* read filter */
if (strcmp(st, IPQOS_CONF_FILTER_STR) == 0) {
free(st);
res = readfilter(cfp, tfp, (*action)->module, &filter,
perm_filters, num_perm_filters);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/*
* if we read a host name for either src or dst addr
* resolve the hostnames and create the appropriate
* number of filters.
*/
if (filter->src_nd_name || filter->dst_nd_name) {
res = domultihome(filter, &(*action)->filters,
B_FALSE);
/*
* if a lookup fails and the filters
* marked as retry we add it to a list
* for another attempt later, otherwise
* it is thrown away.
*/
if (res != IPQOS_CONF_SUCCESS) {
/* if not name lookup problem */
if (filter->nlerr == 0) {
free_filter(filter);
goto fail;
/* name lookup problem */
/*
* if intermitent lookup failure
* add to list of filters to
* retry later.
*/
} else if (filter->nlerr ==
IPQOS_LOOKUP_RETRY) {
filter->nlerr = 0;
ADD_TO_LIST(
&(*action)->retry_filters,
filter);
/*
* for non-existing names
* ignore the filter.
*/
} else {
free_filter(filter);
}
/* creation of new filters successful */
} else {
free_filter(filter);
}
/* non-node name filter */
} else {
ADD_TO_LIST(&(*action)->filters, filter);
}
/* read class */
} else if (strcmp(st, IPQOS_CONF_CLASS_STR) == 0) {
free(st);
res = readclass(cfp, (*action)->module, &class,
(*action)->perm_classes,
(*action)->num_perm_classes);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
ADD_TO_LIST(&(*action)->classes, class);
/* read params */
} else if (strcmp(st, IPQOS_CONF_PARAMS_STR) == 0) {
free(st);
if (readprms) {
ipqos_msg(MT_ERROR,
gettext("Second parameter clause not "
"supported line %u.\n"), lineno);
goto fail;
}
res = readparams(cfp, tfp, (*action)->module,
(*action)->params);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
readprms++;
/* something unexpected */
} else {
free(st);
ipqos_msg(MT_ERROR,
gettext("Params/filter/class clause expected "
"line %u.\n"), lineno);
goto fail;
}
}
(void) fclose(tfp);
return (IPQOS_CONF_SUCCESS);
fail:
if (tfp)
(void) fclose(tfp);
if (*action) {
free_actions(*action);
*action = NULL;
}
return (IPQOS_CONF_ERR);
}
/*
* check that each of the actions in actions is uniquely named. If one isn't
* set *name to point at the name of the duplicate action.
* RETURNS: IPQOS_CONF_ERR if a non-unique action, else IPQOS_CONF_SUCCESS.
*/
static int
actions_unique(ipqos_conf_action_t *actions, char **name)
{
IPQOSCDBG0(L1, "In actions_unique.\n");
while (actions) {
if (actionexist(actions->name, actions->next)) {
*name = actions->name;
return (IPQOS_CONF_ERR);
}
actions = actions->next;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* checks whether the action parameter is involved in an action cycle.
* RETURNS: 1 if involved in a cycle, 0 otherwise.
*/
static int
in_cycle(
ipqos_conf_action_t *action)
{
ipqos_conf_act_ref_t *aref;
ipqos_conf_class_t *c;
IPQOSCDBG1(L0, "in_cycle: visiting action %s\n", action->name);
/* have we visited this action before? */
if (action->visited == INCYCLE_VISITED) {
action->visited = 0;
return (1);
}
action->visited = INCYCLE_VISITED;
/*
* recurse down the child actions of this action through the
* classes next action and parameter actions.
*/
for (aref = action->params->actions; aref != NULL; aref = aref->next) {
/* skip virtual actions - they can't be in a cycle */
if (virtual_action(aref->name)) {
continue;
}
if (in_cycle(aref->action)) {
action->visited = 0;
return (1);
}
}
for (c = action->classes; c != NULL; c = c->next) {
aref = c->alist;
if (virtual_action(aref->name)) {
continue;
}
if (in_cycle(aref->action)) {
action->visited = 0;
return (1);
}
}
IPQOSCDBG0(L0, "in_cycle: return\n");
action->visited = 0;
return (0);
}
/*
* checks that the configuration in actions is a valid whole, that
* all actions are unique, all filters and classes are unique within
* their action, that classes referenced by filters exist and actions
* referenced by classes and params exist. Also checks that there are no root
* actions but ipgpc and that no actions are involved in cycles. As
* a consequence of checking that the actions exist two way pointers
* are created between the dependee and dependant actions.
*
* In the case the the userconf flag is zero only this link creation is
* set as we trust the kernel to return a valid configuration.
*
* RETURNS: IPQOS_CONF_ERR if config isn't valid, else IPQOS_CONF_SUCCESS.
*
*/
static int
validconf(
ipqos_conf_action_t *actions,
int userconf) /* are we checking a conf file ? */
{
char *name;
ipqos_conf_action_t *act;
int res;
ipqos_conf_action_t *dact;
ipqos_conf_filter_t *flt;
ipqos_conf_class_t *cls;
ipqos_conf_params_t *params;
ipqos_conf_act_ref_t *aref;
IPQOSCDBG0(L0, "In validconf\n");
/* check actions are unique */
if (userconf && actions_unique(actions, &name) != IPQOS_CONF_SUCCESS) {
ipqos_msg(MT_ERROR, gettext("Duplicate named action %s.\n"),
name);
return (IPQOS_CONF_ERR);
}
for (act = actions; act; act = act->next) {
/*
* check filters (for user land configs only).
* check they are unique in this action and their class exists.
*/
if (userconf) {
for (flt = act->filters; flt; flt = flt->next) {
/* check unique name */
if (filterexist(flt->name, flt->instance,
flt->next)) {
ipqos_msg(MT_ERROR,
gettext("Duplicate named filter "
"%s in action %s.\n"), flt->name,
act->name);
return (IPQOS_CONF_ERR);
}
/*
* check existence of class and error if
* class doesn't exist and not a perm class
*/
if (!classexist(flt->class_name,
act->classes)) {
if (!in_string_table(act->perm_classes,
act->num_perm_classes,
flt->class_name)) {
ipqos_msg(MT_ERROR,
gettext("Undefined "
"class in filter %s, "
"action %s.\n"), flt->name,
act->name);
return (IPQOS_CONF_ERR);
}
}
}
}
/* check classes */
for (cls = act->classes; cls; cls = cls->next) {
/* check if class name unique (userland only) */
if (userconf && classexist(cls->name, cls->next)) {
ipqos_msg(MT_ERROR,
gettext("Duplicate named class %s in "
"action %s.\n"), cls->name, act->name);
return (IPQOS_CONF_ERR);
}
/*
* virtual actions always exist so don't check for next
* action.
*/
if (virtual_action(cls->alist->name)) {
continue;
}
/*
* check existance of next action and create link to
* it.
*/
if ((cls->alist->action =
actionexist(cls->alist->name, actions)) == NULL) {
ipqos_msg(MT_ERROR,
gettext("Undefined action in class %s, "
"action %s.\n"), cls->name, act->name);
return (IPQOS_CONF_ERR);
}
/* create backwards link - used for deletions */
dact = cls->alist->action;
res = add_aref(&dact->dependencies, NULL, act->name);
if (res != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
dact->dependencies->action = act;
}
/* check actions exist for action type parameters */
params = act->params;
for (aref = params->actions; aref; aref = aref->next) {
/* skip virtuals */
if (virtual_action(aref->name)) {
continue;
}
/*
* check existance of action in this ref
* and if present create a ptr to it.
*/
aref->action = actionexist(aref->name, actions);
if (aref->action == NULL) {
ipqos_msg(MT_ERROR,
gettext("Undefined action in parameter "
"%s, action %s.\n"),
SHORT_NAME(aref->field), act->name);
return (IPQOS_CONF_ERR);
}
/* create backwards link */
dact = aref->action;
res = add_aref(&dact->dependencies, NULL,
act->name);
if (res != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
dact->dependencies->action = act;
}
}
/* for kernel retrieved configs we don't do the following checks. */
if (!userconf) {
return (IPQOS_CONF_SUCCESS);
}
/* check for cycles in config and orphaned actions other than ipgpc */
for (act = actions; act; act = act->next) {
/* check if involved in cycle */
if (in_cycle(act)) {
ipqos_msg(MT_ERROR,
gettext("Action %s involved in cycle.\n"),
act->name);
return (IPQOS_CONF_ERR);
}
/* check that this action has a parent (except ipgpc) */
if (act->dependencies == NULL &&
strcmp(act->name, IPGPC_CLASSIFY) != 0) {
ipqos_msg(MT_ERROR, gettext("Action %s isn't "
"referenced by any other actions.\n"), act->name);
return (IPQOS_CONF_ERR);
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Read the version from the config file with stream cfp with
* the tag version_tag. The tag-value pair should be the first tokens
* encountered.
*
* RETURNS: -1 if a missing or invalid version or a read error,
* else an integer encoding of the version.
*/
static int
read_cfile_ver(
FILE *cfp,
char *version_tag)
{
char *sp = NULL;
int res;
int version;
IPQOSCDBG0(L1, "In read_cfile_ver:\n");
/*
* read version tag string.
*/
res = readtoken(cfp, &sp);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
} else if (strcasecmp(sp, version_tag) != 0) {
goto fail;
}
free(sp);
sp = NULL;
/*
* read version number string.
*/
res = readtoken(cfp, &sp);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/*
* encode version into int.
*/
if ((version = ver_str_to_int(sp)) == -1) {
goto fail;
}
free(sp);
return (version);
fail:
ipqos_msg(MT_ERROR,
gettext("Missing/Invalid config file %s.\n"), version_tag);
if (sp != NULL)
free(sp);
return (-1);
}
/*
* read the set of actions definitions from the stream cfp and store
* them in a list pointed to by conf.
* RETURNS: IPQOS_CONF_ERR if any errors, else IPQOS_CONF_SUCCESS.
*/
static int
readconf(
FILE *cfp,
ipqos_conf_action_t **conf)
{
int res;
ipqos_conf_action_t *action;
boolean_t ipgpc_action = B_FALSE;
int fmt_ver;
IPQOSCDBG0(L0, "In readconf\n");
*conf = NULL;
/*
* get config file format version.
*/
fmt_ver = read_cfile_ver(cfp, IPQOS_FMT_VERSION_STR);
if (fmt_ver == -1) {
return (IPQOS_CONF_ERR);
} else {
/*
* check version is valid
*/
if ((IPP_MAJOR_MODULE_VER(fmt_ver) > 1) ||
(IPP_MINOR_MODULE_VER(fmt_ver) > 0)) {
ipqos_msg(MT_ERROR, gettext("Unsupported config file "
"format version.\n"));
return (IPQOS_CONF_ERR);
}
}
/* loop reading actions adding to conf till EOF */
for (;;) {
action = NULL;
/* readaction */
res = readaction(cfp, &action);
if (res == IPQOS_CONF_ERR) {
goto fail;
}
/* reached eof, finish */
if (res == IPQOS_CONF_EOF) {
break;
}
ADD_TO_LIST(conf, action);
/* check if we just read an ipgpc action */
if (strcmp(action->name, IPGPC_CLASSIFY) == 0)
ipgpc_action = B_TRUE;
}
/* check that there is one or more actions and that one is ipgpc */
if (ipgpc_action == B_FALSE) {
ipqos_msg(MT_ERROR, gettext("No %s action defined.\n"),
IPGPC_NAME);
goto fail;
}
return (IPQOS_CONF_SUCCESS);
fail:
free_actions(*conf);
*conf = NULL;
return (IPQOS_CONF_ERR);
}
/* ************************ kernel config retrieval ************************ */
/*
* read the current configuration from the kernel and make *conf a ptr to it.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
readkconf(ipqos_conf_action_t **conf)
{
int res;
char **modnames = NULL;
int nmods;
char **actnames = NULL;
int nacts;
int x, y;
FILE *tfp;
int openerr;
ipqos_actinfo_prm_t ai_prm;
IPQOSCDBG0(L0, "In readkconf\n");
/* initialise conf to NULL */
*conf = NULL;
/* get list of modules currently loaded */
res = ipp_list_mods(&modnames, &nmods);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "ipp_list_mods");
return (IPQOS_CONF_ERR);
}
/*
* iterate through all loaded modules retrieving their list of actions
* and then retrieving the configuration of each of these
* and attatching it to conf.
*/
for (x = 0; x < nmods; x++) {
/* skip actions of modules that we can't open types file of */
if ((tfp = validmod(modnames[x], &openerr)) == NULL) {
/* mem error */
if (!openerr) {
goto fail;
/*
* fopen fail - if we failed because the file didn't
* exist we assume this is an unknown module and
* ignore this module, otherwise error.
*/
} else {
if (errno == ENOENT) {
continue;
} else {
ipqos_msg(MT_ENOSTR, "fopen");
goto fail;
}
}
}
(void) fclose(tfp);
/* get action list for this module */
res = ipp_mod_list_actions(modnames[x], &actnames, &nacts);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "ipp_mod_list_actions");
goto fail;
}
/* read config of each action of this module */
for (y = 0; y < nacts; y++) {
ai_prm.action = alloc_action();
if (ai_prm.action == NULL) {
goto fail;
}
/* copy action name into action struct */
(void) strlcpy(ai_prm.action->name, actnames[y],
IPQOS_CONF_NAME_LEN);
/* copy module name into action struct */
(void) strlcpy(ai_prm.action->module, modnames[x],
IPQOS_CONF_NAME_LEN);
/* get action info */
res = ipp_action_info(actnames[y],
(int (*)(nvlist_t *, void *))parse_kaction,
(void *)&ai_prm, 0);
if (res != 0) {
/* was this an ipp error */
if (ai_prm.intl_ret == IPQOS_CONF_SUCCESS) {
ipqos_msg(MT_ENOSTR,
"ipp_action_info");
}
goto fail;
}
ADD_TO_LIST(conf, ai_prm.action);
}
cleanup_string_table(actnames, nacts);
}
cleanup_string_table(modnames, nmods);
return (IPQOS_CONF_SUCCESS);
fail:
free_actions(*conf);
*conf = NULL;
cleanup_string_table(modnames, nmods);
cleanup_string_table(actnames, nacts);
return (IPQOS_CONF_ERR);
}
/*
* This is passed as a parameter to ipp_action_info() in readkaction and
* is called back one for each configuration element within the action
* specified. This results in filters and classes being created and chained
* off of action, and action having its params set.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCESS.
*/
static int
parse_kaction(
nvlist_t *nvl,
ipqos_actinfo_prm_t *ai_prm)
{
int ret;
uint8_t cfgtype;
ipqos_conf_filter_t *filter = NULL;
ipqos_conf_class_t *class = NULL;
ipqos_conf_action_t *action = ai_prm->action;
IPQOSCDBG1(KRET, "In parse_kaction: action_name: %s\n", action->name);
/* get config type */
(void) nvlist_lookup_byte(nvl, IPP_CONFIG_TYPE, &cfgtype);
(void) nvlist_remove_all(nvl, IPP_CONFIG_TYPE);
switch (cfgtype) {
case CLASSIFIER_ADD_FILTER: {
/*
* parse the passed filter nvlist
* and add result to action's filter list.
*/
filter = alloc_filter();
if (filter == NULL) {
ai_prm->intl_ret = IPQOS_CONF_ERR;
return (IPQOS_CONF_ERR);
}
ret = parse_kfilter(filter, nvl);
if (ret != IPQOS_CONF_SUCCESS) {
free_filter(filter);
ai_prm->intl_ret = IPQOS_CONF_ERR;
return (ret);
}
ADD_TO_LIST(&action->filters, filter);
break;
}
case CLASSIFIER_ADD_CLASS:
case CLASSIFIER_MODIFY_CLASS: {
/*
* parse the passed class nvlist
* and add result to action's class list.
*/
class = alloc_class();
if (class == NULL) {
ai_prm->intl_ret = IPQOS_CONF_ERR;
return (IPQOS_CONF_ERR);
}
ret = parse_kclass(class, nvl);
if (ret != IPQOS_CONF_SUCCESS) {
free_class(class);
ai_prm->intl_ret = IPQOS_CONF_ERR;
return (ret);
}
ADD_TO_LIST(&action->classes, class);
break;
}
case IPP_SET: {
/*
* we don't alloc a params struct as it is created
* as part of an action.
*/
/* parse the passed params nvlist */
ret = parse_kparams(action->module, action->params,
nvl);
if (ret != IPQOS_CONF_SUCCESS) {
ai_prm->intl_ret = IPQOS_CONF_ERR;
return (ret);
}
}
}
ai_prm->intl_ret = IPQOS_CONF_SUCCESS;
return (IPQOS_CONF_SUCCESS);
}
/*
* parses a params nvlist returned from the kernel.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
int
parse_kparams(
char *module,
ipqos_conf_params_t *params,
nvlist_t *nvl) {
int ret;
ipqos_nvtype_t type;
str_val_nd_t *tmp;
char *act;
uint32_t u32;
nvpair_t *nvp;
FILE *tfp;
char dfltst[IPQOS_VALST_MAXLEN];
char *param;
nvlist_t *nvlcp;
int openerr;
place_t place;
IPQOSCDBG0(KRET, "In parse_kparams:\n");
/* get stream to module types file */
tfp = validmod(module, &openerr);
if (tfp == NULL) {
if (openerr) {
ipqos_msg(MT_ENOSTR, "fopen");
}
return (IPQOS_CONF_ERR);
}
/* make copy of passed in nvlist as it is freed by the caller */
ret = nvlist_dup(nvl, &nvlcp, 0);
if (ret != 0) {
return (IPQOS_CONF_ERR);
}
/*
* get config originator and remove from nvlist. If no owner we
* assume ownership.
*/
ret = nvlist_lookup_uint32(nvlcp, IPP_CONFIG_ORIGINATOR, &u32);
if (ret == 0) {
params->originator = u32;
(void) nvlist_remove_all(nvlcp, IPP_CONFIG_ORIGINATOR);
} else {
params->originator = IPP_CONFIG_IPQOSCONF;
}
/* get action stats and remove from nvlist */
ret = nvlist_lookup_uint32(nvlcp, IPP_ACTION_STATS_ENABLE, &u32);
if (ret == 0) {
params->stats_enable = *(boolean_t *)&u32;
(void) nvlist_remove_all(nvlcp, IPP_ACTION_STATS_ENABLE);
}
/*
* loop throught nvlist elements and for those that are actions create
* action ref entrys for them.
*/
nvp = nvlist_next_nvpair(nvlcp, NULL);
while (nvp != NULL) {
param = SHORT_NAME(nvpair_name(nvp));
place = PL_ANY;
ret = readtype(tfp, module, param, &type, &tmp, dfltst,
B_FALSE, &place);
if (ret != IPQOS_CONF_SUCCESS) {
goto fail;
}
if ((place == PL_PARAMS) && /* avoid map entries */
(type == IPQOS_DATA_TYPE_ACTION)) {
(void) nvpair_value_string(nvp, &act);
ret = add_aref(&params->actions, nvpair_name(nvp), act);
if (ret != IPQOS_CONF_SUCCESS) {
goto fail;
}
}
nvp = nvlist_next_nvpair(nvlcp, nvp);
}
/* assign copied nvlist to params struct */
params->nvlist = nvlcp;
(void) fclose(tfp);
return (IPQOS_CONF_SUCCESS);
fail:
(void) fclose(tfp);
free_arefs(params->actions);
params->actions = NULL;
nvlist_free(nvlcp);
return (IPQOS_CONF_ERR);
}
/*
* parses a classes nvlist returned from the kernel.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
parse_kclass(
ipqos_conf_class_t *class,
nvlist_t *nvl)
{
int ret;
uint32_t u32;
char *str;
IPQOSCDBG0(KRET, "In parse_kclass:\n");
/* lookup object originator */
ret = nvlist_lookup_uint32(nvl, IPP_CONFIG_ORIGINATOR, &u32);
if (ret == 0) {
class->originator = u32;
} else {
class->originator = IPP_CONFIG_IPQOSCONF;
}
/* lookup name */
(void) nvlist_lookup_string(nvl, CLASSIFIER_CLASS_NAME, &str);
(void) strlcpy(class->name, str, IPQOS_CONF_NAME_LEN);
IPQOSCDBG1(KRET, "reading class %s\n", class->name);
/* lookup next action */
(void) nvlist_lookup_string(nvl, CLASSIFIER_NEXT_ACTION, &str);
ret = add_aref(&class->alist, NULL, str);
if (ret != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* lookup stats enable */
ret = nvlist_lookup_uint32(nvl, CLASSIFIER_CLASS_STATS_ENABLE, &u32);
if (ret == 0) {
class->stats_enable = *(boolean_t *)&u32;
}
return (IPQOS_CONF_SUCCESS);
}
/*
* parses a filters nvlist returned from the kernel.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
parse_kfilter(
ipqos_conf_filter_t *filter,
nvlist_t *nvl)
{
int ret;
char *str;
uint32_t u32;
nvlist_t *nvlcp;
char *end;
IPQOSCDBG0(KRET, "In parse_kfilter:\n");
/* make copy of passed in nvlist as it is freed by the caller */
ret = nvlist_dup(nvl, &nvlcp, 0);
if (ret != 0) {
return (IPQOS_CONF_ERR);
}
/* lookup originator */
ret = nvlist_lookup_uint32(nvlcp, IPP_CONFIG_ORIGINATOR, &u32);
if (ret == 0) {
filter->originator = u32;
(void) nvlist_remove_all(nvlcp, IPP_CONFIG_ORIGINATOR);
} else {
filter->originator = IPP_CONFIG_IPQOSCONF;
}
/* lookup filter name */
(void) nvlist_lookup_string(nvlcp, CLASSIFIER_FILTER_NAME, &str);
(void) strlcpy(filter->name, str, IPQOS_CONF_NAME_LEN);
(void) nvlist_remove_all(nvlcp, CLASSIFIER_FILTER_NAME);
/* lookup class name */
(void) nvlist_lookup_string(nvlcp, CLASSIFIER_CLASS_NAME, &str);
(void) strlcpy(filter->class_name, str, IPQOS_CONF_NAME_LEN);
(void) nvlist_remove_all(nvlcp, CLASSIFIER_CLASS_NAME);
/* lookup src and dst host names if present */
if (nvlist_lookup_string(nvlcp, IPGPC_SADDR_HOSTNAME, &str) == 0) {
filter->src_nd_name = malloc(strlen(str) + 1);
if (filter->src_nd_name) {
(void) strcpy(filter->src_nd_name, str);
(void) nvlist_remove_all(nvlcp, IPGPC_SADDR_HOSTNAME);
} else {
ipqos_msg(MT_ENOSTR, "malloc");
nvlist_free(nvlcp);
return (IPQOS_CONF_ERR);
}
}
if (nvlist_lookup_string(nvlcp, IPGPC_DADDR_HOSTNAME, &str) == 0) {
filter->dst_nd_name = malloc(strlen(str) + 1);
if (filter->dst_nd_name) {
(void) strcpy(filter->dst_nd_name, str);
(void) nvlist_remove_all(nvlcp, IPGPC_DADDR_HOSTNAME);
} else {
ipqos_msg(MT_ENOSTR, "malloc");
nvlist_free(nvlcp);
return (IPQOS_CONF_ERR);
}
}
/* lookup ip_version if present */
if (nvlist_lookup_string(nvlcp, IPGPC_FILTER_PRIVATE, &str) == 0) {
filter->ip_versions = (uint32_t)strtol(str, &end, 0);
if (end != str) {
(void) nvlist_remove_all(nvlcp, IPGPC_FILTER_PRIVATE);
} else {
ipqos_msg(MT_ERROR,
gettext("Corrupted ip_version returned from "
"kernel.\n"));
nvlist_free(nvlcp);
return (IPQOS_CONF_ERR);
}
}
/* lookup filter instance if present */
ret = nvlist_lookup_int32(nvlcp, IPGPC_FILTER_INSTANCE,
&filter->instance);
if (ret != 0) {
filter->instance = -1;
} else {
(void) nvlist_remove_all(nvlcp, IPGPC_FILTER_INSTANCE);
}
/* attach new trimmed nvlist to filter */
filter->nvlist = nvlcp;
return (IPQOS_CONF_SUCCESS);
}
/*
* determines whether action_name is a virtual action name.
* RETURNS: if virtual action 1, else 0.
*/
static int
virtual_action(char *action_name)
{
if (strcmp(action_name, IPP_ANAME_CONT) == 0 ||
strcmp(action_name, IPP_ANAME_DEFER) == 0 ||
strcmp(action_name, IPP_ANAME_DROP) == 0) {
return (1);
}
return (0);
}
/*
* remove all the actions within the kernel. If there is a failure
* modified is set to represent whether the attempt to flush modified
* the configuration in any way.
* RETURNS: IPQOS_CONF_ERR if the ipp_* functions return any errors,
* else IPQOS_CONF_SUCCESS.
*/
static int
flush(
boolean_t *modified)
{
int res;
char **modnames = NULL;
int nmods;
char **actnames = NULL;
int nacts;
int x, y;
IPQOSCDBG0(L0, "In flush\n");
*modified = B_FALSE;
/*
* get list of modules currently loaded.
*/
res = ipp_list_mods(&modnames, &nmods);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "ipp_list_mods");
return (IPQOS_CONF_ERR);
}
/*
* iterate through all the modules listing their actions and
* deleting all of them.
*/
for (x = 0; x < nmods; x++) {
IPQOSCDBG1(APPLY, "Getting actions of module %s.\n",
modnames[x]);
res = ipp_mod_list_actions(modnames[x], &actnames, &nacts);
if (res != 0) {
ipqos_msg(MT_ENOSTR, "ipp_mod_list_actions");
cleanup_string_table(modnames, nmods);
return (IPQOS_CONF_ERR);
}
for (y = 0; y < nacts; y++) {
IPQOSCDBG1(APPLY, "deleting action %s\n", actnames[y]);
res = ipp_action_destroy(actnames[y], IPP_DESTROY_REF);
/*
* if fails for reason other than action doesn't
* exist or action has dependency.
*/
if (res != 0 && errno != ENOENT && errno != EBUSY) {
ipqos_msg(MT_ENOSTR, "ipp_action_destroy");
cleanup_string_table(modnames, nmods);
cleanup_string_table(actnames, nacts);
return (IPQOS_CONF_ERR);
}
if (res == 0)
*modified = B_TRUE;
}
cleanup_string_table(actnames, nacts);
}
cleanup_string_table(modnames, nmods);
return (IPQOS_CONF_SUCCESS);
}
/*
* Trys to flush the configuration. If it fails and nothing has been modified
* and force_flush is false just return an error, otherwise persist trying to
* completion.
* RETURNS: IPQOS_CONF_ERR if flush attempt failed without modifying anything
* and force_flush was set to false, otherwise IPQOS_CONF_SUCCESS.
*/
static int
atomic_flush(
boolean_t force_flush)
{
int x = 0;
int res;
boolean_t modified = B_FALSE;
/*
* attempt first flush of config.
*/
res = flush(&modified);
if ((force_flush == B_FALSE) && (res != IPQOS_CONF_SUCCESS) &&
(modified == B_FALSE)) {
return (IPQOS_CONF_ERR);
} else if (res == IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_SUCCESS);
}
/*
* failed flush that modified config, or force flush set; loop till
* successful flush.
*/
while (res != IPQOS_CONF_SUCCESS) {
if (x == 5) { /* 10 secs since start/last message. */
ipqos_msg(MT_ERROR,
gettext("Retrying configuration flush.\n"));
x = 0;
}
(void) sleep(2);
x++;
res = flush(&modified);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* Performs a flush of the configuration within a signal blocking region
* so that there's minimal chance of it being killed and the flush only
* partially completing.
* RETURNS: IPQOS_CONF_SUCCESS (for symmetry with the other main functions).
*/
static int
flushconf()
{
int res;
/*
* make sure that flush is as atomic as possible.
*/
if ((res = block_all_signals()) == -1)
return (IPQOS_CONF_ERR);
res = atomic_flush(B_FALSE);
/*
* restore signals.
*/
(void) restore_all_signals();
if (res == IPQOS_CONF_SUCCESS) {
ipqos_msg(MT_LOG, gettext("Configuration flushed.\n"));
} else {
ipqos_msg(MT_ENOSTR, "atomic_flush");
}
return (res);
}
static int
in_string_table(char *stable[], int size, char *string)
{
IPQOSCDBG1(L1, "In in_string_table: search string %s\n", string);
for (--size; size >= 0; size--) {
if (strcmp(stable[size], string) == 0) {
IPQOSCDBG1(L1, "Found %s in string table\n", string);
return (1);
}
}
return (0);
}
/* free the memory occupied by the string table ctable and its contents. */
static void
cleanup_string_table(char *ctable[], int size)
{
int x;
if (ctable) {
for (x = 0; x < size; x++) {
free(ctable[x]);
}
free(ctable);
}
}
#if 0
/*
* makes a copy of a string table and returns a ptr to it.
* RETURNS: NULL on error or if size was 0, else ptr to copied table.
*/
static char **
copy_string_table(char *stable1[], int size)
{
char **st = NULL;
int pos;
/* create char ptr array */
st = malloc(size * sizeof (char *));
if (st == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (st);
}
/* create copy of each string from stable1 in array */
for (pos = size - 1; pos >= 0; pos--) {
st[pos] = malloc(strlen(stable1[pos] + 1));
if (st[pos] == NULL) {
for (pos++; pos < size; pos++)
free(st[pos]);
free(st);
ipqos_msg(MT_ENOSTR, "malloc");
return (NULL);
}
(void) strcpy(st[pos], stable1[pos]);
}
return (st);
}
#endif /* 0 */
/*
* retry lookups on filters that soft failed a previous lookup and
* were put on the retry list.
* RETURNS: IPQOS_CONF_ERR on any errors, else IPQOS_CONF_SUCCESS.
*/
static int
retry_name_lookups(
ipqos_conf_action_t *actions)
{
ipqos_conf_action_t *act;
ipqos_conf_filter_t **new_filters;
ipqos_conf_filter_t *flt;
IPQOSCDBG0(APPLY, "In retry_name_lookups:\n");
for (act = actions; act != NULL; act = act->next) {
/* store start of new resolved filters */
LIST_END(&act->filters, &new_filters);
/*
* do name resolution on retry list adding resolved filters
* to end of actions filters.
*/
for (flt = act->retry_filters; flt != NULL; flt = flt->next) {
if (domultihome(flt, new_filters, B_TRUE) !=
IPQOS_CONF_SUCCESS) {
/* if resource failure */
if (flt->nlerr == 0) {
return (IPQOS_CONF_ERR);
}
}
}
/* add the newly resolved filters to the kernel action */
for (flt = *new_filters; flt != NULL; flt = flt->next) {
if (add_filter(act->name, flt, act->module_version) !=
IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
}
}
return (IPQOS_CONF_SUCCESS);
}
/*
* write the configuration in conf to the file given in dstpath. This
* is done by writing first to a temporary file and then renaming that
* file to dstpath. This assures an atomic write.
* RETURNS: IPQOS_CONF_ERR on any errors, else IPQOS_CONF_SUCCESS.
*/
static int
writeconf(
ipqos_conf_action_t *conf,
char *dstpath)
{
FILE *tmpfp;
char *tmppath;
char *pathend;
ipqos_conf_action_t *act;
int res;
IPQOSCDBG0(L0, "in writeconf\n");
/* construct tmp file path so we can use rename() */
pathend = strrchr(dstpath, '/');
/* dstpath in current dir */
if (pathend == NULL) {
tmppath = malloc(strlen("ipqosconf.tmp") + 1);
if (tmppath == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (IPQOS_CONF_ERR);
}
(void) strcpy(tmppath, "ipqosconf.tmp");
/* dstpath in root dir */
} else if (pathend == dstpath) {
tmppath = malloc(strlen("/ipqosconf.tmp") + 1);
if (tmppath == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (IPQOS_CONF_ERR);
}
(void) strcpy(tmppath, "/ipqosconf.tmp");
/* not pwd or root */
} else {
*pathend = NULL;
tmppath = malloc(strlen(dstpath) + strlen("/ipqosconf.tmp") +
1);
if (tmppath == NULL) {
ipqos_msg(MT_ENOSTR, "malloc");
return (IPQOS_CONF_ERR);
}
(void) strcpy(tmppath, dstpath);
(void) strcat(tmppath, "/ipqosconf.tmp");
*pathend = '/';
}
/* open tmp file */
tmpfp = fopen(tmppath, "w");
if (tmpfp == NULL) {
ipqos_msg(MT_ENOSTR, "fopen");
free(tmppath);
return (IPQOS_CONF_ERR);
}
/* write out format version */
(void) fprintf(tmpfp, "%s %d.%d\n\n", IPQOS_FMT_VERSION_STR,
IPQOS_CUR_FMT_MAJOR_VER, IPQOS_CUR_FMT_MINOR_VER);
/*
* loop through actions in list writing ipqosconf originated
* ones out to the tmp file.
*/
for (act = conf; act != NULL; act = act->next) {
if (act->params->originator == IPP_CONFIG_IPQOSCONF) {
res = printaction(tmpfp, act, 0, 0);
if (res != IPQOS_CONF_SUCCESS) {
free(tmppath);
(void) fclose(tmpfp);
return (res);
}
}
}
(void) fclose(tmpfp);
/* rename tmp file to dst file */
if (rename(tmppath, dstpath) != 0) {
ipqos_msg(MT_ENOSTR, "rename");
free(tmppath);
return (IPQOS_CONF_ERR);
}
free(tmppath);
return (IPQOS_CONF_SUCCESS);
}
/*
* read the configuration back from the kernel and then write each of the
* actions read to IPQOS_CONF_INIT_PATH.
* RETURNS: IPQOS_CONF_ERR if error, else IPQOS_CONF_SUCCESS.
*/
static int
commitconf()
{
int ret;
ipqos_conf_action_t *conf;
IPQOSCDBG0(L0, "In commitconf\n");
/* read the configuration from the kernel */
ret = readkconf(&conf);
if (ret != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* dissallow a null config to be stored (we can't read one in) */
if (conf == NULL) {
ipqos_msg(MT_ERROR,
gettext("Can't commit a null configuration.\n"));
return (IPQOS_CONF_ERR);
}
/* make sure if we create file that perms are 644 */
(void) umask(S_IXUSR | S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH);
/* write the configuration to the init file */
ret = writeconf(conf, IPQOS_CONF_INIT_PATH);
if (ret != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
ipqos_msg(MT_LOG,
gettext("Current configuration saved to init file.\n"));
return (IPQOS_CONF_SUCCESS);
}
/*
* Called in the event of a failed rollback. It first flushes the
* current configuration, then attempts to apply the oconf (the old
* one), and if that fails flushes again.
*
* RETURNS: IPQOS_CONF_ERR if the application of old config fails,
* else IPQOS_CONF_SUCCESS.
*/
static int
rollback_recover(
ipqos_conf_action_t *oconf)
{
int res;
IPQOSCDBG0(RBK, "In rollback_recover\n");
/*
* flush configuration.
*/
(void) atomic_flush(B_TRUE);
/*
* mark all elements of old config for application.
*/
mark_config_new(oconf);
/*
* attempt to apply old config.
*/
res = applydiff(oconf, NULL);
/*
* if failed force flush of config.
*/
if (res != IPQOS_CONF_SUCCESS) {
(void) atomic_flush(B_TRUE);
return (IPQOS_CONF_ERR);
}
return (IPQOS_CONF_SUCCESS);
}
/*
* read and apply the configuration contained if file ifile to the kernel.
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
applyconf(char *ifile)
{
FILE *ifp;
ipqos_conf_action_t *conf = NULL;
ipqos_conf_action_t *oconf = NULL;
ipqos_conf_action_t *act, *oact;
int res;
IPQOSCDBG0(L0, "In applyconf:\n");
/* if filename '-' read from stdin */
if (strcmp(ifile, "-") == 0) {
ifp = stdin;
} else {
ifp = fopen(ifile, "r");
if (ifp == NULL) {
ipqos_msg(MT_ERROR,
gettext("Opening file %s for read: %s.\n"),
ifile, strerror(errno));
return (IPQOS_CONF_ERR);
}
}
/* read in new configuration */
res = readconf(ifp, &conf);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* check configuration is valid */
res = validconf(conf, 1);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* read in kernel configuration */
res = readkconf(&oconf);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/*
* check there are no same named actions in both config file and the
* the kernel that are for a different module. The application
* system can't handle these as we would try to add the new
* action before we deleted the old one and because actions
* in the kernel are indexed solely on their name (their module
* isn't included) the kernel would return an error. We want
* to avoid this error and the resulting rollback.
*/
for (act = conf; act != NULL; act = act->next) {
for (oact = oconf; oact != NULL; oact = oact->next) {
/* found action */
if (strcmp(act->name, oact->name) == 0) {
/* different module */
if (strcmp(act->module, oact->module) != 0) {
ipqos_msg(MT_ERROR,
gettext("Action at line %u has "
"same name as currently "
"installed action, but is for a "
"different module.\n"),
act->lineno);
goto fail;
/* same module - stop search */
} else {
break;
}
}
}
}
/* create links between actions for use with deletions etc.. */
res = validconf(oconf, 0);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* diff conf file against kernel */
res = diffconf(oconf, conf);
if (res != IPQOS_CONF_SUCCESS) {
goto fail;
}
/* make kernel mods as atomic as possible */
if ((res = block_all_signals()) == -1) {
res = IPQOS_CONF_ERR;
goto fail;
}
/* apply difference to kernel */
res = applydiff(conf, oconf);
#ifdef _IPQOS_CONF_DEBUG
if (force_rback || res != IPQOS_CONF_SUCCESS) {
#else
if (res != IPQOS_CONF_SUCCESS) {
#endif /* _IPQOS_CONF_DEBUG */
res = rollback(conf, oconf);
if (res != IPQOS_CONF_SUCCESS) {
res = rollback_recover(oconf);
if (res != IPQOS_CONF_SUCCESS) {
/* system left flushed */
ipqos_msg(MT_ERROR,
gettext("Failed to rollback from failed "
"configuration, configuration flushed.\n"));
res = IPQOS_CONF_RECOVER_ERR;
} else { /* old config re-applied */
ipqos_msg(MT_ERROR,
gettext("Configuration failed, system "
"state unchanged.\n"));
res = IPQOS_CONF_ERR;
}
} else {
ipqos_msg(MT_ERROR,
gettext("Configuration failed, system "
"state unchanged.\n"));
res = IPQOS_CONF_ERR;
}
goto fail;
}
/* retry any soft name lookup failures */
res = retry_name_lookups(conf);
if (res != IPQOS_CONF_SUCCESS) {
res = rollback(conf, oconf);
if (res != IPQOS_CONF_SUCCESS) {
res = rollback_recover(oconf);
if (res != IPQOS_CONF_SUCCESS) {
/* system left flushed */
ipqos_msg(MT_ERROR,
gettext("Failed to rollback from failed "
"configuration, configuration flushed.\n"));
res = IPQOS_CONF_RECOVER_ERR;
} else { /* old config re-applied */
ipqos_msg(MT_ERROR,
gettext("Configuration failed, system "
"state unchanged.\n"));
res = IPQOS_CONF_ERR;
}
} else {
ipqos_msg(MT_ERROR,
gettext("Configuration failed, system "
"state unchanged.\n"));
res = IPQOS_CONF_ERR;
}
goto fail;
}
ipqos_msg(MT_LOG, gettext("IPQoS configuration applied.\n"));
/* re-enable signals */
(void) restore_all_signals();
(void) fclose(ifp);
free_actions(conf);
free_actions(oconf);
return (IPQOS_CONF_SUCCESS);
fail:
(void) fclose(ifp);
(void) restore_all_signals();
if (conf)
free_actions(conf);
if (oconf)
free_actions(oconf);
if (res == IPQOS_CONF_RECOVER_ERR)
ipqos_msg(MT_LOG, gettext("Configuration flushed.\n"));
return (res);
}
static sigset_t set, oset;
static int
block_all_signals()
{
if (sigfillset(&set) == -1) {
ipqos_msg(MT_ENOSTR, "sigfillset");
return (-1);
}
if (sigprocmask(SIG_SETMASK, &set, &oset) == -1) {
ipqos_msg(MT_ENOSTR, "sigprocmask");
return (-1);
}
return (0);
}
static int
restore_all_signals()
{
if (sigprocmask(SIG_SETMASK, &oset, NULL) == -1) {
ipqos_msg(MT_ENOSTR, "sigprocmask");
return (-1);
}
return (0);
}
static int
unlock(int fd)
{
if (lockf(fd, F_ULOCK, 0) == -1) {
ipqos_msg(MT_ENOSTR, "lockf");
return (-1);
}
return (0);
}
static int
lock()
{
int fd;
struct stat sbuf1;
struct stat sbuf2;
/*
* Open the file with O_CREAT|O_EXCL. If it exists already, it
* will fail. If it already exists, check whether it looks like
* the one we created.
*/
(void) umask(0077);
if ((fd = open(IPQOS_CONF_LOCK_FILE, O_EXCL|O_CREAT|O_RDWR,
S_IRUSR|S_IWUSR)) == -1) {
if (errno != EEXIST) {
/* Some other problem. */
ipqos_msg(MT_ENOSTR,
gettext("Cannot open lock file %s"),
IPQOS_CONF_LOCK_FILE);
return (-1);
}
/*
* open() returned an EEXIST error. We don't fail yet
* as it could be a residual from a previous
* execution. However, we need to clear errno here.
* If we don't and print_cmd_buf() is later invoked
* as the result of a parsing error, it
* will assume that the current error is EEXIST and
* that a corresponding error message has already been
* printed, which results in an incomplete error
* message. If errno is zero, print_cmd_buf() will
* assume that it is called as a result of a
* parsing error and will print the appropriate
* error message.
*/
errno = 0;
/*
* File exists. make sure it is OK. We need to lstat()
* as fstat() stats the file pointed to by the symbolic
* link.
*/
if (lstat(IPQOS_CONF_LOCK_FILE, &sbuf1) == -1) {
ipqos_msg(MT_ENOSTR,
gettext("Cannot lstat lock file %s\n"),
IPQOS_CONF_LOCK_FILE);
return (-1);
}
/*
* Check whether it is a regular file and not a symbolic
* link. Its link count should be 1. The owner should be
* root and the file should be empty.
*/
if (!S_ISREG(sbuf1.st_mode) ||
sbuf1.st_nlink != 1 ||
sbuf1.st_uid != 0 ||
sbuf1.st_size != 0) {
ipqos_msg(MT_ERROR, gettext("Bad lock file %s.\n"),
IPQOS_CONF_LOCK_FILE);
return (-1);
}
if ((fd = open(IPQOS_CONF_LOCK_FILE, O_CREAT|O_RDWR,
S_IRUSR|S_IWUSR)) == -1) {
ipqos_msg(MT_ENOSTR,
gettext("Cannot open lock file %s"),
IPQOS_CONF_LOCK_FILE);
return (-1);
}
/* Check whether we opened the file that we lstat()ed. */
if (fstat(fd, &sbuf2) == -1) {
ipqos_msg(MT_ENOSTR,
gettext("Cannot fstat lock file %s\n"),
IPQOS_CONF_LOCK_FILE);
return (-1);
}
if (sbuf1.st_dev != sbuf2.st_dev ||
sbuf1.st_ino != sbuf2.st_ino) {
/* File changed after we did the lstat() above */
ipqos_msg(MT_ERROR, gettext("Bad lock file %s.\n"),
IPQOS_CONF_LOCK_FILE);
return (-1);
}
}
if (lockf(fd, F_LOCK, 0) == -1) {
ipqos_msg(MT_ENOSTR, "lockf");
return (-1);
}
return (fd);
}
/*
* print the current kernel configuration out to stdout. If viewall
* is set this causes more verbose configuration listing including
* showing objects we didn't create, each instance of a mhome filter,
* etc.. see printaction().
* RETURNS: IPQOS_CONF_ERR on error, else IPQOS_CONF_SUCCES.
*/
static int
viewconf(int viewall)
{
ipqos_conf_action_t *conf = NULL;
ipqos_conf_action_t *act;
int ret;
IPQOSCDBG0(L0, "In viewconf\n");
/* get kernel configuration */
ret = readkconf(&conf);
if (ret != IPQOS_CONF_SUCCESS) {
return (IPQOS_CONF_ERR);
}
/* write out format version */
if (conf != NULL) {
(void) fprintf(stdout, "%s %d.%d\n\n", IPQOS_FMT_VERSION_STR,
IPQOS_CUR_FMT_MAJOR_VER, IPQOS_CUR_FMT_MINOR_VER);
}
/* print each of the actions in the kernel config to stdout */
for (act = conf; act != NULL; act = act->next) {
ret = printaction(stdout, act, viewall, 0);
if (ret != IPQOS_CONF_SUCCESS) {
free_actions(conf);
return (ret);
}
(void) fprintf(stdout, "\n");
}
free_actions(conf);
return (IPQOS_CONF_SUCCESS);
}
/*
* debug function that reads the config file and prints it out after
* interpreting to stdout.
*/
#ifdef _IPQOS_CONF_DEBUG
static int
viewcfile(char *cfile)
{
ipqos_conf_action_t *conf;
ipqos_conf_action_t *act;
int res;
FILE *ifp;
int viewall = 1;
IPQOSCDBG0(L0, "In viewcfile\n");
ifp = fopen(cfile, "r");
if (ifp == NULL) {
ipqos_msg(MT_ERROR, gettext("Opening file %s for read: %s.\n"),
cfile, strerror(errno));
return (IPQOS_CONF_ERR);
}
res = readconf(ifp, &conf);
if (res != IPQOS_CONF_SUCCESS) {
free(ifp);
return (IPQOS_CONF_ERR);
}
/* print each of the actions in the kernel config to stdout */
for (act = conf; act != NULL; act = act->next) {
res = printaction(stdout, act, viewall, 0);
if (res != IPQOS_CONF_SUCCESS) {
free(ifp);
return (res);
}
(void) fprintf(stdout, "\n");
}
(void) fprintf(stdout, "\n");
return (IPQOS_CONF_SUCCESS);
}
#endif /* _IPQOS_CONF_DEBUG */
static void
usage(void)
{
(void) fprintf(stderr, gettext("usage:\n"
"\tipqosconf [-sv] -a file|-\n"
"\tipqosconf -c\n"
"\tipqosconf -l\n"
"\tipqosconf -L\n"
"\tipqosconf -f\n"));
}
int
main(int argc, char *argv[])
{
int c;
char *ifile = NULL;
int args;
int ret;
int cmd;
int viewall = 0;
int lfp;
/* init global flags */
use_syslog = verbose = 0;
/* init current line number */
lineno = 0;
/* setup internationalisation */
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
/* setup syslog parameters */
openlog("ipqosconf", 0, LOG_USER);
args = 0;
/* enable debug options */
#ifdef _IPQOS_CONF_DEBUG
#define DBGOPTS "rz:"
#else
#define DBGOPTS
#endif /* _IPQOS_CONF_DEBUG */
while ((c = getopt(argc, argv, "sca:vflL" DBGOPTS)) != EOF) {
switch (c) {
#ifdef _IPQOS_CONF_DEBUG
case 'z':
cmd = -1;
ifile = optarg;
if (*ifile == '\0') {
usage();
exit(1);
}
args++;
break;
case 'r':
force_rback++;
break;
#endif /* _IPQOS_CONF_DEBUG */
case 'c':
cmd = IPQOS_CONF_COMMIT;
args++;
break;
case 'a':
cmd = IPQOS_CONF_APPLY;
ifile = optarg;
if (*ifile == '\0') {
usage();
exit(1);
}
args++;
break;
case 'f':
cmd = IPQOS_CONF_FLUSH;
args++;
break;
case 'l':
cmd = IPQOS_CONF_VIEW;
args++;
break;
case 'L':
cmd = IPQOS_CONF_VIEW;
viewall++;
args++;
break;
case 'v':
verbose++;
break;
case 's':
use_syslog++;
break;
case '?':
usage();
return (1);
}
}
/*
* dissallow non-option args, > 1 cmd args and syslog/verbose flags set
* for anything but apply.
*/
if (optind != argc || args > 1 ||
use_syslog && cmd != IPQOS_CONF_APPLY ||
verbose && cmd != IPQOS_CONF_APPLY) {
usage();
exit(1);
}
/* if no cmd option then show config */
if (args == 0) {
cmd = IPQOS_CONF_VIEW;
}
/* stop concurrent ipqosconf invocations */
lfp = lock();
if (lfp == -1) {
exit(1);
}
switch (cmd) {
#ifdef _IPQOS_CONF_DEBUG
case -1:
ret = viewcfile(ifile);
break;
#endif /* _IPQOS_CONF_DEBUG */
case IPQOS_CONF_APPLY:
ret = applyconf(ifile);
break;
case IPQOS_CONF_COMMIT:
ret = commitconf();
break;
case IPQOS_CONF_VIEW:
ret = viewconf(viewall);
break;
case IPQOS_CONF_FLUSH:
ret = flushconf();
break;
}
(void) unlock(lfp);
return (ret);
}