2014-07-14 11:49:44 +00:00
|
|
|
/** Configuration parser.
|
2014-06-05 09:34:32 +00:00
|
|
|
*
|
2014-06-05 09:35:41 +00:00
|
|
|
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
2014-06-05 09:34:32 +00:00
|
|
|
* @copyright 2014, Institute for Automation of Complex Power Systems, EONERC
|
|
|
|
*/
|
|
|
|
|
2015-01-16 15:42:09 +00:00
|
|
|
#include <stdio.h>
|
2014-06-10 19:44:21 +00:00
|
|
|
#include <stdlib.h>
|
2014-12-05 12:34:45 +01:00
|
|
|
#include <string.h>
|
2014-06-05 09:34:50 +00:00
|
|
|
#include <netdb.h>
|
2014-06-05 09:34:32 +00:00
|
|
|
|
2015-03-18 15:50:02 +01:00
|
|
|
#include "utils.h"
|
|
|
|
#include "list.h"
|
2014-06-25 01:53:35 +00:00
|
|
|
#include "if.h"
|
2014-06-25 01:53:48 +00:00
|
|
|
#include "tc.h"
|
2014-06-05 09:34:50 +00:00
|
|
|
#include "cfg.h"
|
2014-06-05 09:35:23 +00:00
|
|
|
#include "node.h"
|
|
|
|
#include "path.h"
|
2014-08-31 14:43:28 +00:00
|
|
|
#include "hooks.h"
|
2014-06-05 09:34:32 +00:00
|
|
|
|
2014-12-05 12:34:45 +01:00
|
|
|
#include "socket.h"
|
|
|
|
#include "gtfpga.h"
|
2015-03-21 11:47:08 +01:00
|
|
|
#ifdef ENABLE_OPAL_ASYNC
|
2014-12-05 12:34:45 +01:00
|
|
|
#include "opal.h"
|
2015-03-21 11:47:08 +01:00
|
|
|
#endif
|
2014-12-05 12:34:45 +01:00
|
|
|
|
2014-06-25 01:53:46 +00:00
|
|
|
int config_parse(const char *filename, config_t *cfg, struct settings *set,
|
2014-07-04 15:58:10 +00:00
|
|
|
struct node **nodes, struct path **paths)
|
2014-06-05 09:34:32 +00:00
|
|
|
{
|
2014-07-04 09:44:09 +00:00
|
|
|
config_set_auto_convert(cfg, 1);
|
|
|
|
|
2015-03-20 18:55:14 +01:00
|
|
|
int ret = strcmp("-", filename) ? config_read_file(cfg, filename)
|
|
|
|
: config_read(cfg, stdin);
|
2015-01-16 15:42:09 +00:00
|
|
|
|
2015-03-20 18:55:14 +01:00
|
|
|
if (ret != CONFIG_TRUE)
|
2014-06-05 09:34:50 +00:00
|
|
|
error("Failed to parse configuration: %s in %s:%d",
|
2015-03-20 18:55:14 +01:00
|
|
|
config_error_text(cfg),
|
|
|
|
config_error_file(cfg) ? config_error_file(cfg) : filename,
|
2014-06-05 09:34:50 +00:00
|
|
|
config_error_line(cfg)
|
|
|
|
);
|
2015-03-20 18:55:14 +01:00
|
|
|
|
2014-06-10 16:57:40 +00:00
|
|
|
config_setting_t *cfg_root = config_root_setting(cfg);
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2014-06-10 18:47:25 +00:00
|
|
|
/* Parse global settings */
|
2014-12-05 12:39:52 +01:00
|
|
|
if (set) {
|
|
|
|
if (!cfg_root || !config_setting_is_group(cfg_root))
|
|
|
|
error("Missing global section in config file: %s", filename);
|
2014-06-10 16:57:40 +00:00
|
|
|
|
2014-12-05 12:39:52 +01:00
|
|
|
config_parse_global(cfg_root, set);
|
|
|
|
}
|
|
|
|
|
2014-06-10 18:47:25 +00:00
|
|
|
/* Parse nodes */
|
2014-12-05 12:39:52 +01:00
|
|
|
if (nodes) {
|
|
|
|
config_setting_t *cfg_nodes = config_setting_get_member(cfg_root, "nodes");
|
|
|
|
if (!cfg_nodes || !config_setting_is_group(cfg_nodes))
|
|
|
|
error("Missing node section in config file: %s", filename);
|
|
|
|
|
|
|
|
for (int i = 0; i < config_setting_length(cfg_nodes); i++) {
|
|
|
|
config_setting_t *cfg_node = config_setting_get_elem(cfg_nodes, i);
|
|
|
|
config_parse_node(cfg_node, nodes);
|
|
|
|
}
|
2014-06-05 09:34:32 +00:00
|
|
|
}
|
|
|
|
|
2014-06-05 09:34:50 +00:00
|
|
|
/* Parse paths */
|
2014-12-05 12:39:52 +01:00
|
|
|
if (paths) {
|
|
|
|
config_setting_t *cfg_paths = config_setting_get_member(cfg_root, "paths");
|
|
|
|
if (!cfg_paths || !config_setting_is_list(cfg_paths))
|
|
|
|
error("Missing path section in config file: %s", filename);
|
|
|
|
|
|
|
|
for (int i = 0; i < config_setting_length(cfg_paths); i++) {
|
|
|
|
config_setting_t *cfg_path = config_setting_get_elem(cfg_paths, i);
|
|
|
|
config_parse_path(cfg_path, paths, nodes);
|
|
|
|
}
|
2014-06-05 09:34:32 +00:00
|
|
|
}
|
|
|
|
|
2015-03-20 18:55:14 +01:00
|
|
|
return 0;
|
2014-06-05 09:34:32 +00:00
|
|
|
}
|
|
|
|
|
2014-06-10 18:47:25 +00:00
|
|
|
int config_parse_global(config_setting_t *cfg, struct settings *set)
|
2014-06-05 09:34:32 +00:00
|
|
|
{
|
2014-06-10 18:47:25 +00:00
|
|
|
config_setting_lookup_int(cfg, "affinity", &set->affinity);
|
|
|
|
config_setting_lookup_int(cfg, "priority", &set->priority);
|
2014-09-07 16:28:50 +00:00
|
|
|
config_setting_lookup_int(cfg, "debug", &set->debug);
|
2014-09-07 16:28:54 +00:00
|
|
|
config_setting_lookup_float(cfg, "stats", &set->stats);
|
2014-09-07 16:28:50 +00:00
|
|
|
|
2014-12-05 12:39:52 +01:00
|
|
|
_debug = set->debug;
|
2014-06-05 09:34:32 +00:00
|
|
|
|
2014-06-10 18:47:25 +00:00
|
|
|
set->cfg = cfg;
|
2014-06-05 09:35:41 +00:00
|
|
|
|
2015-03-20 18:55:14 +01:00
|
|
|
return 0;
|
2014-06-05 09:34:32 +00:00
|
|
|
}
|
|
|
|
|
2014-06-10 18:47:25 +00:00
|
|
|
int config_parse_path(config_setting_t *cfg,
|
2014-06-25 01:53:46 +00:00
|
|
|
struct path **paths, struct node **nodes)
|
2014-06-05 09:34:32 +00:00
|
|
|
{
|
2015-03-18 15:50:02 +01:00
|
|
|
const char *in;
|
2014-06-05 09:34:50 +00:00
|
|
|
int enabled = 1;
|
|
|
|
int reverse = 0;
|
2015-03-18 15:50:02 +01:00
|
|
|
|
2015-03-17 23:21:31 +01:00
|
|
|
struct path *p = alloc(sizeof(struct path));
|
2014-06-25 01:53:35 +00:00
|
|
|
|
2015-03-18 15:50:02 +01:00
|
|
|
/* Input node */
|
|
|
|
struct config_setting_t *cfg_in = config_setting_get_member(cfg, "in");
|
|
|
|
if (!cfg_in || config_setting_type(cfg_in) != CONFIG_TYPE_STRING)
|
|
|
|
cerror(cfg, "Invalid input node for path");
|
|
|
|
|
|
|
|
in = config_setting_get_string(cfg_in);
|
2014-09-11 14:40:43 +00:00
|
|
|
p->in = node_lookup_name(in, *nodes);
|
|
|
|
if (!p->in)
|
2015-03-20 18:55:14 +01:00
|
|
|
cerror(cfg_in, "Invalid input node '%s'", in);
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2015-03-18 15:50:02 +01:00
|
|
|
/* Output node(s) */
|
|
|
|
struct config_setting_t *cfg_out = config_setting_get_member(cfg, "out");
|
|
|
|
if (cfg_out)
|
|
|
|
config_parse_nodelist(cfg_out, &p->destinations, nodes);
|
|
|
|
|
|
|
|
if (list_length(&p->destinations) >= 1)
|
|
|
|
p->out = list_first(&p->destinations)->node;
|
|
|
|
else
|
|
|
|
cerror(cfg, "Missing output node for path");
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2014-06-25 17:50:30 +00:00
|
|
|
/* Optional settings */
|
2015-03-18 16:13:18 +01:00
|
|
|
struct config_setting_t *cfg_hook = config_setting_get_member(cfg, "hook");
|
|
|
|
if (cfg_hook)
|
|
|
|
config_parse_hooks(cfg_hook, &p->hooks);
|
2014-08-31 14:43:28 +00:00
|
|
|
|
2014-06-25 17:50:30 +00:00
|
|
|
config_setting_lookup_bool(cfg, "enabled", &enabled);
|
|
|
|
config_setting_lookup_bool(cfg, "reverse", &reverse);
|
2014-09-11 14:40:43 +00:00
|
|
|
config_setting_lookup_float(cfg, "rate", &p->rate);
|
2014-08-31 14:43:28 +00:00
|
|
|
|
2014-09-11 14:40:43 +00:00
|
|
|
p->cfg = cfg;
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2014-06-25 01:53:35 +00:00
|
|
|
if (enabled) {
|
2014-09-11 14:40:48 +00:00
|
|
|
p->in->refcnt++;
|
2015-03-18 15:50:02 +01:00
|
|
|
FOREACH(&p->destinations, it)
|
|
|
|
it->node->refcnt++;
|
|
|
|
|
2014-06-25 01:53:32 +00:00
|
|
|
if (reverse) {
|
2015-03-18 15:50:02 +01:00
|
|
|
if (list_length(&p->destinations) > 1)
|
|
|
|
warn("Using first destination '%s' as source for reverse path. "
|
|
|
|
"Ignoring remaining nodes", p->out->name);
|
2014-09-11 14:40:48 +00:00
|
|
|
|
2015-03-18 15:50:02 +01:00
|
|
|
struct path *r = alloc(sizeof(struct path));
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2015-03-18 15:50:02 +01:00
|
|
|
r->in = p->out; /* Swap in/out */
|
|
|
|
r->out = p->in;
|
|
|
|
|
|
|
|
list_push(&r->destinations, r->out);
|
2014-06-25 01:53:48 +00:00
|
|
|
|
2015-03-18 15:50:02 +01:00
|
|
|
r->in->refcnt++;
|
|
|
|
r->out->refcnt++;
|
|
|
|
|
|
|
|
list_add(*paths, r);
|
2014-06-25 01:53:32 +00:00
|
|
|
}
|
2015-03-18 15:50:02 +01:00
|
|
|
|
|
|
|
list_add(*paths, p);
|
2014-06-05 09:34:50 +00:00
|
|
|
}
|
2014-06-25 01:53:35 +00:00
|
|
|
else {
|
2015-03-18 15:45:06 +01:00
|
|
|
char buf[33];
|
|
|
|
path_print(p, buf, sizeof(buf));
|
|
|
|
|
|
|
|
warn("Path %s is not enabled", buf);
|
2015-03-18 15:47:18 +01:00
|
|
|
path_destroy(p);
|
2014-06-25 01:53:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2014-06-05 09:34:50 +00:00
|
|
|
}
|
|
|
|
|
2015-03-18 15:50:02 +01:00
|
|
|
int config_parse_nodelist(config_setting_t *cfg, struct list *nodes, struct node **all) {
|
|
|
|
const char *str;
|
|
|
|
struct node *node;
|
|
|
|
|
|
|
|
switch (config_setting_type(cfg)) {
|
|
|
|
case CONFIG_TYPE_STRING:
|
|
|
|
str = config_setting_get_string(cfg);
|
|
|
|
node = node_lookup_name(str, *all);
|
|
|
|
if (!node)
|
|
|
|
cerror(cfg, "Invalid outgoing node '%s'", str);
|
|
|
|
|
|
|
|
list_push(nodes, node);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_ARRAY:
|
|
|
|
for (int i=0; i<config_setting_length(cfg); i++) {
|
|
|
|
str = config_setting_get_string_elem(cfg, i);
|
|
|
|
node = node_lookup_name(str, *all);
|
|
|
|
if (!node)
|
|
|
|
cerror(config_setting_get_elem(cfg, i), "Invalid outgoing node '%s'", str);
|
|
|
|
|
|
|
|
list_push(nodes, node);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
cerror(cfg, "Invalid output node(s)");
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-03-18 16:13:18 +01:00
|
|
|
int config_parse_hooks(config_setting_t *cfg, struct list *hooks) {
|
|
|
|
const char *str;
|
|
|
|
hook_cb_t hook;
|
|
|
|
|
|
|
|
switch (config_setting_type(cfg)) {
|
|
|
|
case CONFIG_TYPE_STRING:
|
|
|
|
str = config_setting_get_string(cfg);
|
|
|
|
hook = hook_lookup(str);
|
|
|
|
if (!hook)
|
|
|
|
cerror(cfg, "Invalid hook function '%s'", str);
|
|
|
|
|
|
|
|
list_push(hooks, hook);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_ARRAY:
|
|
|
|
for (int i=0; i<config_setting_length(cfg); i++) {
|
|
|
|
str = config_setting_get_string_elem(cfg, i);
|
|
|
|
hook = hook_lookup(str);
|
|
|
|
if (!hook)
|
|
|
|
cerror(config_setting_get_elem(cfg, i), "Invalid hook function '%s'", str);
|
|
|
|
|
|
|
|
list_push(hooks, hook);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
cerror(cfg, "Invalid hook functions");
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-04 15:58:10 +00:00
|
|
|
int config_parse_node(config_setting_t *cfg, struct node **nodes)
|
2014-06-05 09:34:50 +00:00
|
|
|
{
|
2014-12-05 12:39:52 +01:00
|
|
|
const char *type;
|
2014-09-10 12:22:21 +00:00
|
|
|
int ret;
|
2014-06-25 01:53:35 +00:00
|
|
|
|
2015-03-17 23:21:31 +01:00
|
|
|
struct node *n = alloc(sizeof(struct node));
|
2014-06-05 09:34:50 +00:00
|
|
|
|
|
|
|
/* Required settings */
|
2014-12-05 12:39:52 +01:00
|
|
|
n->cfg = cfg;
|
2014-09-11 14:40:43 +00:00
|
|
|
n->name = config_setting_name(cfg);
|
|
|
|
if (!n->name)
|
2014-06-10 18:47:25 +00:00
|
|
|
cerror(cfg, "Missing node name");
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2015-01-20 10:52:03 +00:00
|
|
|
if (config_setting_lookup_string(cfg, "type", &type)) {
|
|
|
|
n->vt = node_lookup_vtable(type);
|
|
|
|
if (!n->vt)
|
|
|
|
cerror(cfg, "Invalid type for node '%s'", n->name);
|
2015-03-12 22:56:58 +01:00
|
|
|
|
|
|
|
if (!n->vt->parse)
|
|
|
|
cerror(cfg, "Node type '%s' is not allowed in the config", type);
|
2015-01-20 10:52:03 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
n->vt = node_lookup_vtable("udp");
|
2014-12-05 12:39:52 +01:00
|
|
|
|
|
|
|
ret = n->vt->parse(cfg, n);
|
|
|
|
|
2015-03-20 18:55:14 +01:00
|
|
|
if (!ret)
|
|
|
|
list_add(*nodes, n);
|
2014-12-05 12:39:52 +01:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2014-12-05 12:34:45 +01:00
|
|
|
|
2015-03-21 11:47:08 +01:00
|
|
|
#ifdef ENABLE_OPAL_ASYNC
|
2015-03-17 22:46:46 +01:00
|
|
|
/** @todo: Remove this global variable. */
|
|
|
|
extern struct opal_global *og;
|
|
|
|
|
2014-12-05 12:34:45 +01:00
|
|
|
int config_parse_opal(config_setting_t *cfg, struct node *n)
|
2015-03-17 22:46:46 +01:00
|
|
|
{
|
|
|
|
if (!og) {
|
2015-03-20 18:55:14 +01:00
|
|
|
warn("Skipping node '%s', because this server is not running as an OPAL Async process!", n->name);
|
2015-03-17 22:46:46 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-03-21 11:54:14 +01:00
|
|
|
struct opal *o = (struct opal *) alloc(sizeof(struct opal));
|
2015-03-17 22:46:46 +01:00
|
|
|
|
|
|
|
config_setting_lookup_int(cfg, "send_id", &o->send_id);
|
2015-03-20 18:55:14 +01:00
|
|
|
config_setting_lookup_int(cfg, "recv_id", &o->recv_id);
|
2015-03-17 22:46:46 +01:00
|
|
|
config_setting_lookup_bool(cfg, "reply", &o->reply);
|
|
|
|
|
|
|
|
/* Search for valid send and recv ids */
|
|
|
|
int sfound = 0, rfound = 0;
|
|
|
|
for (int i=0; i<og->send_icons; i++)
|
|
|
|
sfound += og->send_ids[i] == o->send_id;
|
|
|
|
for (int i=0; i<og->send_icons; i++)
|
|
|
|
rfound += og->send_ids[i] == o->send_id;
|
|
|
|
|
|
|
|
if (!sfound)
|
|
|
|
cerror(config_setting_get_member(cfg, "send_id"), "Invalid send_id '%u' for node '%s'", o->send_id, n->name);
|
|
|
|
if (!rfound)
|
2015-03-20 18:55:14 +01:00
|
|
|
cerror(config_setting_get_member(cfg, "recv_id"), "Invalid recv_id '%u' for node '%s'", o->recv_id, n->name);
|
2015-03-12 22:56:58 +01:00
|
|
|
|
|
|
|
n->opal = o;
|
2015-03-17 22:46:46 +01:00
|
|
|
n->opal->global = og;
|
|
|
|
n->cfg = cfg;
|
2015-03-12 22:56:58 +01:00
|
|
|
|
2014-12-05 12:34:45 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2015-03-21 11:47:08 +01:00
|
|
|
#endif /* ENABLE_OPAL_ASYNC */
|
2014-12-05 12:34:45 +01:00
|
|
|
|
2015-03-21 11:47:08 +01:00
|
|
|
|
|
|
|
#ifdef ENABLE_GTFPGA
|
2014-12-05 12:34:45 +01:00
|
|
|
/** @todo Implement */
|
|
|
|
int config_parse_gtfpga(config_setting_t *cfg, struct node *n)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
2015-03-21 11:47:08 +01:00
|
|
|
#endif /* ENABLE_GTFPGA */
|
2014-12-05 12:34:45 +01:00
|
|
|
|
|
|
|
int config_parse_socket(config_setting_t *cfg, struct node *n)
|
|
|
|
{
|
|
|
|
const char *local, *remote;
|
|
|
|
int ret;
|
|
|
|
|
2015-03-17 23:23:13 +01:00
|
|
|
struct socket *s = alloc(sizeof(struct socket));
|
2014-12-05 12:34:45 +01:00
|
|
|
|
2014-08-31 14:43:28 +00:00
|
|
|
if (!config_setting_lookup_string(cfg, "remote", &remote))
|
2014-09-11 14:40:43 +00:00
|
|
|
cerror(cfg, "Missing remote address for node '%s'", n->name);
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2014-08-31 14:43:28 +00:00
|
|
|
if (!config_setting_lookup_string(cfg, "local", &local))
|
2014-09-11 14:40:43 +00:00
|
|
|
cerror(cfg, "Missing local address for node '%s'", n->name);
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2014-12-05 12:34:45 +01:00
|
|
|
ret = socket_parse_addr(local, (struct sockaddr *) &s->local, node_type(n), AI_PASSIVE);
|
2014-09-10 12:22:21 +00:00
|
|
|
if (ret)
|
|
|
|
cerror(cfg, "Failed to resolve local address '%s' of node '%s': %s",
|
2014-09-11 14:40:43 +00:00
|
|
|
local, n->name, gai_strerror(ret));
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2014-12-05 12:34:45 +01:00
|
|
|
ret = socket_parse_addr(remote, (struct sockaddr *) &s->remote, node_type(n), 0);
|
2014-09-10 12:22:21 +00:00
|
|
|
if (ret)
|
|
|
|
cerror(cfg, "Failed to resolve remote address '%s' of node '%s': %s",
|
2014-09-11 14:40:43 +00:00
|
|
|
remote, n->name, gai_strerror(ret));
|
2014-06-05 09:34:50 +00:00
|
|
|
|
2014-12-05 12:34:45 +01:00
|
|
|
/** @todo Netem settings are not usable AF_UNIX */
|
2014-06-25 01:53:48 +00:00
|
|
|
config_setting_t *cfg_netem = config_setting_get_member(cfg, "netem");
|
|
|
|
if (cfg_netem) {
|
2015-03-17 23:21:31 +01:00
|
|
|
s->netem = (struct netem *) alloc(sizeof(struct netem));
|
2015-01-20 10:52:05 +00:00
|
|
|
|
2014-12-05 12:34:45 +01:00
|
|
|
config_parse_netem(cfg_netem, s->netem);
|
2014-06-25 01:53:48 +00:00
|
|
|
}
|
2014-12-05 12:34:45 +01:00
|
|
|
|
|
|
|
n->socket = s;
|
2014-06-25 01:53:48 +00:00
|
|
|
|
2015-03-20 18:55:14 +01:00
|
|
|
return 0;
|
2014-06-05 09:34:32 +00:00
|
|
|
}
|
2014-06-25 01:53:48 +00:00
|
|
|
|
|
|
|
int config_parse_netem(config_setting_t *cfg, struct netem *em)
|
|
|
|
{
|
|
|
|
em->valid = 0;
|
|
|
|
|
|
|
|
if (config_setting_lookup_string(cfg, "distribution", &em->distribution))
|
|
|
|
em->valid |= TC_NETEM_DISTR;
|
|
|
|
if (config_setting_lookup_int(cfg, "delay", &em->delay))
|
|
|
|
em->valid |= TC_NETEM_DELAY;
|
|
|
|
if (config_setting_lookup_int(cfg, "jitter", &em->jitter))
|
|
|
|
em->valid |= TC_NETEM_JITTER;
|
|
|
|
if (config_setting_lookup_int(cfg, "loss", &em->loss))
|
|
|
|
em->valid |= TC_NETEM_LOSS;
|
|
|
|
if (config_setting_lookup_int(cfg, "duplicate", &em->duplicate))
|
|
|
|
em->valid |= TC_NETEM_DUPL;
|
|
|
|
if (config_setting_lookup_int(cfg, "corrupt", &em->corrupt))
|
|
|
|
em->valid |= TC_NETEM_CORRUPT;
|
|
|
|
|
2014-12-05 12:39:52 +01:00
|
|
|
/** @todo Validate netem config values */
|
2014-06-25 01:53:48 +00:00
|
|
|
|
2015-03-20 18:55:14 +01:00
|
|
|
return 0;
|
2014-06-25 01:53:48 +00:00
|
|
|
}
|