summaryrefslogtreecommitdiff
path: root/keama
diff options
context:
space:
mode:
authorFrancis Dupont <fdupont@isc.org>2017-04-21 16:52:40 +0200
committerFrancis Dupont <fdupont@isc.org>2018-11-29 16:42:13 +0100
commitd904376d815e8275eaf88a7e9b611ffee611f20c (patch)
tree5aa75cda5276cec89728f0be901971598febde63 /keama
parentba9e4b40b2dbaf441a81476547f7527954886989 (diff)
downloadisc-dhcp-d904376d815e8275eaf88a7e9b611ffee611f20c.tar.gz
Checkpoint (wrote doc, began group)
Diffstat (limited to 'keama')
-rw-r--r--keama/confparse.c249
-rw-r--r--keama/data.c40
-rw-r--r--keama/data.h16
-rw-r--r--keama/doc.txt446
-rw-r--r--keama/keama.h3
-rw-r--r--keama/parse.c7
-rw-r--r--keama/reduce.c62
7 files changed, 791 insertions, 32 deletions
diff --git a/keama/confparse.c b/keama/confparse.c
index 60206291..b35168a4 100644
--- a/keama/confparse.c
+++ b/keama/confparse.c
@@ -98,6 +98,7 @@ conf_file_parse(struct parse *cfile)
mapRemove(cfile->stack[1], "reservations");
orphans = createList();
+ orphans->kind = HOST_DECL;
while (listSize(hosts) > 0) {
host = listGet(hosts, 0);
listRemove(hosts, 0);
@@ -108,6 +109,7 @@ conf_file_parse(struct parse *cfile)
dest = mapGet(where, "reservations");
if (dest == NULL) {
dest = createList();
+ dest->kind = HOST_DECL;
mapSet(where, dest, "reservations");
}
listPush(dest, host);
@@ -180,7 +182,7 @@ conf_file_subparse(struct parse *cfile, int type)
{
const char *val;
enum dhcp_token token;
- int declaration = 0;
+ isc_boolean_t declaration = ISC_FALSE;
for (;;) {
token = peek_token(&val, NULL, cfile);
@@ -224,8 +226,8 @@ conf_file_subparse(struct parse *cfile, int type)
| USER_CLASS class-declaration
| RANGE address-range-declaration */
-int
-parse_statement(struct parse *cfile, int type, int declaration)
+isc_boolean_t
+parse_statement(struct parse *cfile, int type, isc_boolean_t declaration)
{
enum dhcp_token token;
const char *val;
@@ -762,6 +764,7 @@ parse_pool_statement(struct parse *cfile, int type)
pools = mapGet(cfile->stack[cfile->stack_top], "pools");
if (pools == NULL) {
pools = createList();
+ pools->kind = POOL_DECL;
mapSet(cfile->stack[cfile->stack_top], pools, "pools");
} else if ((type == SHARED_NET_DECL) && (listSize(pools) > 0)) {
cfile->stack[cfile->stack_top]->skip = ISC_TRUE;
@@ -1034,6 +1037,7 @@ parse_host_declaration(struct parse *cfile)
hosts = mapGet(where, "reservations");
if (hosts == NULL) {
hosts = createList();
+ hosts->kind = HOST_DECL;
mapSet(where, hosts, "reservations");
}
listPush(hosts, host);
@@ -1086,6 +1090,7 @@ parse_class_declaration(struct parse *cfile, int type)
classes = mapGet(cfile->stack[1], "client-classes");
if (classes == NULL) {
classes = createList();
+ classes->kind = CLASS_DECL;
mapSet(cfile->stack[1], classes, "client-classes");
} else {
int i;
@@ -1480,12 +1485,14 @@ parse_shared_net_declaration(struct parse *cfile)
subnets = mapGet(cfile->stack[1], "subnet4");
if (subnets == NULL) {
subnets = createList();
+ subnets->kind = SUBNET_DECL;
mapSet(cfile->stack[1], subnets, "subnet4");
}
} else {
subnets = mapGet(cfile->stack[1], "subnet6");
if (subnets == NULL) {
subnets = createList();
+ subnets->kind = SUBNET_DECL;
mapSet(cfile->stack[1], subnets, "subnet6");
}
}
@@ -1588,6 +1595,7 @@ parse_subnet_declaration(struct parse *cfile)
subnets = mapGet(cfile->stack[1], "subnet4");
if (subnets == NULL) {
subnets = createList();
+ subnets->kind = SUBNET_DECL;
mapSet(cfile->stack[1], subnets, "subnet4");
}
}
@@ -1674,6 +1682,7 @@ parse_subnet6_declaration(struct parse *cfile)
subnets = mapGet(cfile->stack[1], "subnet6");
if (subnets == NULL) {
subnets = createList();
+ subnets->kind = SUBNET_DECL;
mapSet(cfile->stack[1], subnets, "subnet6");
}
}
@@ -1727,10 +1736,13 @@ parse_group_declaration(struct parse *cfile)
isc_boolean_t dynamicp = ISC_FALSE;
isc_boolean_t staticp = ISC_FALSE;
+ if (mapContains(cfile->stack[cfile->stack_top], "group"))
+ parse_error(cfile, "another group is already open");
group = createMap();
group->skip = ISC_TRUE;
group->kind = GROUP_DECL;
TAILQ_CONCAT(&group->comments, &cfile->comments);
+ mapSet(cfile->stack[cfile->stack_top], group, "group");
token = peek_token(&val, NULL, cfile);
if (is_identifier(token) || token == STRING) {
@@ -1773,11 +1785,228 @@ parse_group_declaration(struct parse *cfile)
cfile->stack_top--;
- if (name && !deletedp) {
+ if (name && !deletedp)
mapSet(group, createString(name), "name");
- /* Kea todo */
+ dissolve_group(cfile, group);
+}
+
+void
+dissolve_group(struct parse *cfile, struct element *group)
+{
+ struct handle *handle;
+ struct element *parent;
+ struct element *item;
+ /*struct element *decl;*/
+ struct element *param;
+ struct handle *hosts = NULL;
+ struct handle *shares = NULL;
+ struct handle *subnets = NULL;
+ struct handle *classes = NULL;
+ struct handle *pools = NULL;
+ struct handle *pdpools = NULL;
+ struct handle *downs = NULL;
+ struct comment *comment;
+ const char *key;
+ const char *name = NULL;
+ unsigned order = 0;
+ isc_boolean_t marked = ISC_FALSE;
+
+ /* check that group is on its parent */
+
+ parent = cfile->stack[cfile->stack_top];
+ item = mapGet(parent, "group");
+ if (item == NULL)
+ parse_error(cfile, "no group in parent");
+ if (item != group)
+ parse_error(cfile, "got a different group from parent");
+
+ /* classify content */
+ while (mapSize(group) > 0) {
+ handle = mapPop(group);
+ if ((handle == NULL) || (handle->key == NULL) ||
+ (handle->value == NULL))
+ parse_error(cfile, "can't get group item at %u",
+ order);
+ handle->order = order++;
+ switch (handle->value->kind) {
+ case TOPLEVEL:
+ case ROOT_GROUP:
+ case GROUP_DECL:
+ badkind:
+ parse_error(cfile, "impossible group item (kind %d) "
+ "for %s at order %u",
+ handle->value->kind, handle->key, order);
+
+ case HOST_DECL:
+ if (strcmp(handle->key, "reservations") != 0)
+ parse_error(cfile, "expected reservations "
+ "got %s at %u",
+ handle->key, order);
+ if (hosts != NULL)
+ parse_error(cfile, "got reservations twice "
+ "at %u and %u",
+ hosts->order, order);
+ hosts = handle;
+ handle = NULL;
+ break;
+
+ case SHARED_NET_DECL:
+ if (strcmp(handle->key, "shared-network") != 0)
+ parse_error(cfile, "expected shared-network "
+ "got %s at %u",
+ handle->key, order);
+ if (shares == NULL)
+ shares = handle;
+ else
+ TAILQ_INSERT_TAIL(&shares->values, handle);
+ handle = NULL;
+ break;
+
+ case SUBNET_DECL:
+ key = local_family == AF_INET ? "subnet4" : "subnet6";
+ if (strcmp(handle->key, key) != 0)
+ parse_error(cfile, "expected %s got %s at %u",
+ key, handle->key, order);
+ if (subnets != NULL)
+ parse_error(cfile, "got %s twice at %u and %u",
+ key, subnets->order, order);
+ subnets = handle;
+ handle = NULL;
+ break;
+
+ case CLASS_DECL:
+ if (strcmp(handle->key, "client-classes") != 0)
+ parse_error(cfile, "expected client-classes "
+ "got %s at %u",
+ handle->key, order);
+ if (classes == NULL)
+ classes = handle;
+ else
+ TAILQ_INSERT_TAIL(&classes->values, handle);
+ handle = NULL;
+ break;
+
+ case POOL_DECL:
+ if (strcmp(handle->key, "pools") == 0) {
+ if (pools != NULL)
+ parse_error(cfile, "got pools twice "
+ "at %u and %u",
+ pools->order, order);
+ pools = handle;
+ } else if (strcmp(handle->key, "pd-pools") == 0) {
+ if (pdpools != NULL)
+ parse_error(cfile, "got pd-pools "
+ "twice at %u and %u",
+ pdpools->order, order);
+ pdpools = handle;
+ } else
+
+ handle = NULL;
+ break;
+ default:
+ if (handle->value->kind != PARAMETER)
+ goto badkind;
+ }
+ if (handle == NULL)
+ continue;
+
+ /* we have a parameter */
+ param = handle->value;
+ /* group name */
+ if (strcmp(handle->key, "name") == 0) {
+ name = stringValue(param)->content;
+ continue;
+ }
+ /* unexpected values */
+ if ((strcmp(handle->key, "reservations") == 0) ||
+ (strcmp(handle->key, "group") == 0) ||
+ (strcmp(handle->key, "shared-network") == 0) ||
+ (strcmp(handle->key, "subnets") == 0) ||
+ (strcmp(handle->key, "subnet4") == 0) ||
+ (strcmp(handle->key, "subnet6") == 0) ||
+ (strcmp(handle->key, "subnet") == 0) ||
+ (strcmp(handle->key, "client-classes") == 0) ||
+ (strcmp(handle->key, "hw-address") == 0) ||
+ (strcmp(handle->key, "ip-address") == 0) ||
+ (strcmp(handle->key, "extra-ip-addresses") == 0) ||
+ (strcmp(handle->key, "ip-addresses") == 0) ||
+ (strcmp(handle->key, "prefixes") == 0) ||
+ (strcmp(handle->key, "pool") == 0) ||
+ (strcmp(handle->key, "prefix") == 0) ||
+ (strcmp(handle->key, "delegated-len") == 0) ||
+ (strcmp(handle->key, "prefix-len") == 0) ||
+ (strcmp(handle->key, "prefix-highest") == 0) ||
+ (strcmp(handle->key, "option-def") == 0) ||
+ (strcmp(handle->key, "hostname") == 0) ||
+ (strcmp(handle->key, "client-id") == 0) ||
+ (strcmp(handle->key, "host-identifier") == 0) ||
+ (strcmp(handle->key, "flex-id") == 0) ||
+ (strcmp(handle->key, "test") == 0) ||
+ (strcmp(handle->key, "dhcp-ddns") == 0) ||
+ (strcmp(handle->key, "host-reservation-identifiers") == 0))
+ parse_error(cfile, "unexpected parameter %s "
+ "in group at %u",
+ handle->key, order);
+ /* to parent at group position */
+ if ((strcmp(handle->key, "authoritative") == 0) ||
+ (strcmp(handle->key, "option-space") == 0) ||
+ (strcmp(handle->key, "server-duid") == 0) ||
+ (strcmp(handle->key, "statement") == 0) ||
+ (strcmp(handle->key, "config") == 0) ||
+ (strcmp(handle->key, "ddns-update-style") == 0) ||
+ (strcmp(handle->key, "echo-client-id") == 0)) {
+ if (!marked) {
+ struct string *msg;
+
+ marked = ISC_TRUE;
+ msg = makeString(-1, "/// moved from group");
+ if (name != NULL)
+ appendString(msg, " ");
+ appendString(msg, name);
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&param->comments, comment);
+ }
+ TAILQ_INSERT_AFTER(&parent->value.map_value,
+ group, param);
+ continue;
+ }
+ /* To reconsider: qualifying-suffix, enable-updates */
+ if ((strcmp(handle->key, "option-data") == 0) ||
+ (strcmp(handle->key, "allow") == 0) ||
+ (strcmp(handle->key, "deny") == 0) ||
+ (strcmp(handle->key, "interface") == 0) ||
+ (strcmp(handle->key, "valid-lifetime") == 0) ||
+ (strcmp(handle->key, "boot-file-name") == 0) ||
+ (strcmp(handle->key, "server-hostname") == 0) ||
+ (strcmp(handle->key, "next-server") == 0) ||
+ (strcmp(handle->key, "preferred-lifetime") == 0) ||
+ (strcmp(handle->key, "match-client-id") == 0)) {
+ if (downs == NULL)
+ downs = handle;
+ else
+ TAILQ_INSERT_TAIL(&downs->values, handle);
+ continue;
+ }
+ /* unknown */
+ if (!marked) {
+ struct string *msg;
+
+ marked = ISC_TRUE;
+ msg = makeString(-1, "/// moved from group");
+ if (name != NULL)
+ appendString(msg, " ");
+ appendString(msg, name);
+ comment = createComment(msg->content);
+ TAILQ_INSERT_TAIL(&param->comments, comment);
+ }
+ comment = createComment("/// unhandled parameter");
+ TAILQ_INSERT_TAIL(&param->comments, comment);
+ param->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ TAILQ_INSERT_AFTER(&parent->value.map_value, group, param);
}
- cfile->issue_counter++;
+ /* to finish */
+ TAILQ_REMOVE(&parent->value.map_value, group);
}
/* fixed-addr-parameter :== ip-addrs-or-hostnames SEMI
@@ -1899,6 +2128,7 @@ parse_address_range(struct parse *cfile, int type, size_t where)
pools = mapGet(group, "pools");
if (pools == NULL) {
pools = createList();
+ pools->kind = POOL_DECL;
mapSet(group, pools, "pools");
}
listPush(pools, pool);
@@ -2016,6 +2246,7 @@ parse_address_range6(struct parse *cfile, int type, size_t where)
pools = mapGet(group, "pools");
if (pools == NULL) {
pools = createList();
+ pools->kind = POOL_DECL;
mapSet(group, pools, "pools");
}
listPush(pools, pool);
@@ -2085,6 +2316,7 @@ parse_prefix6(struct parse *cfile, int type, size_t where)
pools = mapGet(group, "pd-pools");
if (pools == NULL) {
pools = createList();
+ pools->kind = POOL_DECL;
mapSet(group, pools, "pd-pools");
}
listPush(pools, pool);
@@ -2203,6 +2435,7 @@ parse_pool6_statement(struct parse *cfile, int type)
pools = mapGet(cfile->stack[cfile->stack_top], "pools");
if (pools == NULL) {
pools = createList();
+ pools->kind = POOL_DECL;
mapSet(cfile->stack[cfile->stack_top], pools, "pools");
}
listPush(pools, pool);
@@ -2247,7 +2480,7 @@ parse_pool6_statement(struct parse *cfile, int type)
done = ISC_TRUE;
break;
- case END_OF_FILE:
+ case END_OF_FILE:
/*
* We can get to END_OF_FILE if, for instance,
* the parse_statement() reads all available tokens
@@ -2255,7 +2488,7 @@ parse_pool6_statement(struct parse *cfile, int type)
*/
parse_error(cfile, "unexpected end of file");
- default:
+ default:
declaration = parse_statement(cfile, POOL_DECL,
declaration);
break;
diff --git a/keama/data.c b/keama/data.c
index 45ac72ac..78865f44 100644
--- a/keama/data.c
+++ b/keama/data.c
@@ -883,8 +883,8 @@ copy(struct element *e)
result->kind = e->kind;
result->skip = e->skip;
/* don't copy key */
+ /* copy comments */
TAILQ_FOREACH(comment, &e->comments) {
- /* share comment content */
comment = createComment(comment->line);
TAILQ_INSERT_TAIL(&result->comments, comment);
}
@@ -915,24 +915,28 @@ copyMap(struct element *m)
return result;
}
-isc_boolean_t
-derive(struct element *parent, struct element *child, const char *param)
+struct handle *
+mapPop(struct element *m)
{
- struct element *x;
- struct element *y;
+ struct element *item;
+ struct handle *h;
- assert(parent != NULL);
- assert(parent->type == ELEMENT_MAP);
- assert(child != NULL);
- assert(child->type == ELEMENT_MAP);
- assert(param != NULL);
+ assert(m != NULL);
+ assert(m->type == ELEMENT_MAP);
- x = mapGet(parent, param);
- if (x == NULL)
- return ISC_FALSE;
- y = mapGet(child, param);
- if (y != NULL)
- return ISC_FALSE;
- mapSet(child, x, param);
- return ISC_TRUE;
+ h = (struct handle *)malloc(sizeof(struct handle));
+ assert(h != NULL);
+ memset(h, 0, sizeof(struct handle));
+ TAILQ_INIT(&h->values);
+
+ item = TAILQ_FIRST(&m->value.map_value);
+ assert(item != NULL);
+ assert(item->key != NULL);
+ h->key = strdup(item->key);
+ assert(h->key != NULL);
+ h->value = item;
+
+ TAILQ_REMOVE(&m->value.map_value, item);
+
+ return h;
}
diff --git a/keama/data.h b/keama/data.h
index c050effd..7c810890 100644
--- a/keama/data.h
+++ b/keama/data.h
@@ -275,9 +275,17 @@ struct element *copy(struct element *e);
struct element *copyList(struct element *l);
struct element *copyMap(struct element *m);
-/* Inheritance */
-isc_boolean_t derive(struct element *parent,
- struct element *child,
- const char *param);
+/* Handle */
+TAILQ_HEAD(handles, handle);
+
+struct handle {
+ unsigned order; /* order */
+ char *key; /* key */
+ struct element *value; /* value */
+ struct handles values; /* children */
+ TAILQ_ENTRY(handle) next; /* siblings */
+};
+
+struct handle* mapPop(struct element *);
#endif /* DATA_H */
diff --git a/keama/doc.txt b/keama/doc.txt
new file mode 100644
index 00000000..f7ae353e
--- /dev/null
+++ b/keama/doc.txt
@@ -0,0 +1,446 @@
+Part 1: Kea Migration Assistant support
+=======================================
+
+Files:
+------
+ - data.h (tailq list and element type declarations)
+ - data.c (element type code)
+ - keama.h (DHCP declarations)
+ - keama.c (main() code)
+ - json.c (JSON parser)
+ - option.c (option tables and code)
+ - keama.8
+
+The code heavily uses tailq lists, i.e. doubled linked lists with
+a pointer to the last (tail) element.
+
+The element structure mimics the Kea Element class with a few differences:
+ - no smart pointers
+ - extra fields to handle declaration kind, skip and comments
+ - maps are implemented as lists with an extra key field so the order
+ of insertion is kept and duplicates are possible
+ - strings are length + content (vs C strings)
+
+There is no attempt to avoid memory leaks.
+
+The skip flag is printed as '//' at the beginning of lines. It is set
+when something cannot be converted and the issue counter (returned
+by the keama command) incremented.
+
+Part 2: ISC DHCP lexer organization
+===================================
+
+Files:
+-----
+ - dhctoken.h (from includes, enum dhcp_token definition)
+ - conflex.c (from common, lexical analyzer code)
+
+Tokens (dhcp_token enum): characters are set to their ASCII value,
+ others are >= 256 without real organization (e.g. END_OF_FILE is 607).
+
+The state is in a parse structure named "cfile". There is one per file
+and a few routine save it in order to do a backtrack on a larger
+set than the usual lookahead.
+The largest function is intern() which recognizes keywords with
+a switch on the first character and a tree of if strcasecmp's.
+
+Standard routines:
+-----------------
+enum dhcp_token
+next_token(const char **rval, unsigned *rlen, struct parse *cfile);
+
+and
+
+enum dhcp_token
+peek_token(const char **rval, unsigned *rlen, struct parse *cfile);
+
+rval: if not null the content of the token is put in it
+rlen: if not null the length of the token is put in it
+cfile: lexer context
+return: the integer value of the token
+
+Changes:
+-------
+
+Added LBRACKET '[' and RBRACKET ']' tokens for JSON parser
+(switch on dhcp_token type).
+
+Added comments to collect ISC DHCP # comments, element stack to follow
+declaration hierarchy, and issue counter to struct parse.
+
+Moved the parse_warn (renamed into parse_error and made fatal) routine
+from conflex.c to keama.c
+
+Part 3: ISC DHCP parser organization
+====================================
+
+Files:
+-----
+ - confparse.c (from server)
+ for the server in parse_statement())
+ - parse.c (from common)
+
+4 classes: parameters, declarations, executable statements and expressions.
+
+entry point
+ |
+ V
+conf_file_parse
+ |
+ V
+conf_file_subparse <- read_conf_file (for include)
+ until END_OF_FILE call
+ |
+ V
+parse_statement
+ parse parameters and declarations
+ switch on token and call parse_xxx_declaration routines
+ on default or DHCPv6 token in DHCPv4 mode call parse_executable_statement
+ and put the result under the "statement" key
+ |
+ V
+parse_executable_statement
+
+According to comments the grammar is:
+
+ conf-file :== parameters declarations END_OF_FILE
+ parameters :== <nil> | parameter | parameters parameter
+ declarations :== <nil> | declaration | declarations declaration
+
+ statement :== parameter | declaration
+
+ parameter :== DEFAULT_LEASE_TIME lease_time
+ | MAX_LEASE_TIME lease_time
+ | DYNAMIC_BOOTP_LEASE_CUTOFF date
+ | DYNAMIC_BOOTP_LEASE_LENGTH lease_time
+ | BOOT_UNKNOWN_CLIENTS boolean
+ | ONE_LEASE_PER_CLIENT boolean
+ | GET_LEASE_HOSTNAMES boolean
+ | USE_HOST_DECL_NAME boolean
+ | NEXT_SERVER ip-addr-or-hostname SEMI
+ | option_parameter
+ | SERVER-IDENTIFIER ip-addr-or-hostname SEMI
+ | FILENAME string-parameter
+ | SERVER_NAME string-parameter
+ | hardware-parameter
+ | fixed-address-parameter
+ | ALLOW allow-deny-keyword
+ | DENY allow-deny-keyword
+ | USE_LEASE_ADDR_FOR_DEFAULT_ROUTE boolean
+ | AUTHORITATIVE
+ | NOT AUTHORITATIVE
+
+ declaration :== host-declaration
+ | group-declaration
+ | shared-network-declaration
+ | subnet-declaration
+ | VENDOR_CLASS class-declaration
+ | USER_CLASS class-declaration
+ | RANGE address-range-declaration
+
+Typically declarations use { } and are associated with a group
+(changed to a type) in ROOT_GROUP (global), HOST_DECL, SHARED_NET_DECL,
+SUBNET_DECL, CLASS_DECL, GROUP_DECL and POOL_DECL.
+
+isc_boolean_t
+parse_statement(struct parse *cfile, int type, isc_boolean_t declaration);
+
+cfile: parser context
+type: declaration type
+declaration and return: declaration or parameter
+
+On the common side:
+
+ executable-statements :== executable-statement executable-statements |
+ executable-statement
+
+ executable-statement :==
+ IF if-statement |
+ ADD class-name SEMI |
+ BREAK SEMI |
+ OPTION option-parameter SEMI |
+ SUPERSEDE option-parameter SEMI |
+ PREPEND option-parameter SEMI |
+ APPEND option-parameter SEMI
+
+isc_boolean_t
+parse_executable_statement(struct element *result,
+ struct parse *cfile, isc_boolean_t *lose,
+ enum expression_context case_context,
+ isc_boolean_t direct);
+
+result: map element where to put the statement
+cfile: parser context
+lose: set to ISC_TRUE on failure
+case_context: expression context
+direct: called directly by parse_statement so can execute config stuff
+return: success
+
+parse_executable_statement
+ switch on keywords (far more than in the comments)
+ on default with an identifier try a config option, on number or name
+ call parse_expression for a function call
+ |
+ V
+parse_expression
+
+expressions are divided into boolean, data (string) and numeric expressions
+
+ boolean_expression :== CHECK STRING |
+ NOT boolean-expression |
+ data-expression EQUAL data-expression |
+ data-expression BANG EQUAL data-expression |
+ data-expression REGEX_MATCH data-expression |
+ boolean-expression AND boolean-expression |
+ boolean-expression OR boolean-expression
+ EXISTS OPTION-NAME
+
+ data_expression :== SUBSTRING LPAREN data-expression COMMA
+ numeric-expression COMMA
+ numeric-expression RPAREN |
+ CONCAT LPAREN data-expression COMMA
+ data-expression RPAREN
+ SUFFIX LPAREN data_expression COMMA
+ numeric-expression RPAREN |
+ LCASE LPAREN data_expression RPAREN |
+ UCASE LPAREN data_expression RPAREN |
+ OPTION option_name |
+ HARDWARE |
+ PACKET LPAREN numeric-expression COMMA
+ numeric-expression RPAREN |
+ V6RELAY LPAREN numeric-expression COMMA
+ data-expression RPAREN |
+ STRING |
+ colon_separated_hex_list
+
+ numeric-expression :== EXTRACT_INT LPAREN data-expression
+ COMMA number RPAREN |
+ NUMBER
+
+parse_boolean_expression, parse_data_expression and parse_numeric_expression
+calls parse_expression and check its result
+
+parse_expression itself is divided into parse_non_binary and internal
+handling of binary operators
+
+isc_boolean_t
+parse_non_binary(struct element *expr, struct parse *cfile,
+ isc_boolean_t *lose, enum expression_context context)
+
+isc_boolean_t
+parse_expression(struct element *expr, struct parse *cfile,
+ isc_boolean_t *lose, enum expression_context context,
+ struct element *lhs, enum expr_op binop)
+
+expr: map element where to put the result
+cfile: parser context
+lose: set to ISC_TRUE on failure
+context: expression context
+lhs: NULL or left hand side
+binop: expr_none or binary operation
+return: success
+
+parse_non_binary
+ switch on unary and nullary operator keywords
+ on default try a variable reference or a function call
+
+parse_expression
+ call parse_non_binary to get the right hand side
+ switch on binary operator keywords to get the next operation
+ with one side if expr_none return else get the second hand
+ handle operator precedence, can call itself
+ return a map entry with the operator name as the key, and
+ left and right expression branches
+
+Part 4: Expression processing
+=============================
+
+Files:
+------
+ - reduce.c (new)
+
+Print:
+------
+
+const char *
+print_boolean_expression(struct element *expr, isc_boolean_t *lose);
+const char *
+print_data_expression(struct element *expr, isc_boolean_t *lose);
+const char *
+print_numeric_expression(struct element *expr, isc_boolean_t *lose);
+
+expr: expression to print
+lose: failure (??? in output) flag
+return: the text representing the expression
+
+Reduce:
+-------
+
+struct element *
+reduce_boolean_expression(struct element *expr);
+struct element *
+reduce_data_expression(struct element *expr, isc_boolean_t *literalp);
+struct element *
+reduce_numeric_expression(struct element *expr);
+
+expr: expression to reduce
+literalp: literal flag for quoting
+return: NULL or the reduced expression as a Kea eval string
+
+Part 5: Specific issues
+=======================
+
+Reservations:
+-------------
+ ISC DHCP host declarations are global, Kea reservations are per subnet.
+ It is possible to use the fixed address but:
+ - it is possible to finish with orphan reservations, i.e.
+ reservations with an address which match no subnets
+ - a reservation can have no fixed address. In this case the MA puts
+ the reservation in the last declared subnet.
+TODO: add a comment
+ - a reservation can have more than one fixed address and these
+ addresses can belong to different subnets. Current code pushes
+ IPv4 extra addresses in a commented extra-ip-addresses but
+ it is legal feature for IPv6.
+TODO: use fixed prefix6 too
+ The use of groups in host declarations is unclear.
+ ISC DHCP UID is mapped to client-id, host-identifier to flex-id
+TODO finish this
+ Only the two extreme cases for DHCPv6 relay options work (cf #5249)
+ Host reservation identifiers are generated on first use.
+
+Groups:
+-------
+TODO: spread parameters to child declarations.
+
+Shared-Networks:
+----------------
+ Waiting for the feature to be supported by Kea.
+ Currently at the end of a shared network declaration:
+ - if there is no subnets it is a fatal error
+TODO: check what ISC DHCP does
+ - if there is one subnet the shared-network is squeezed
+ - if there are more than one subnet the shared-network is commented
+TODO: put the reference when the ticket will be created.
+
+Vendor-Classes:
+---------------
+TODO: specific code
+
+User-Classes:
+-------------
+TODO: specific code
+
+Classes:
+--------
+ Only pure client-classes are supported by kea.
+ Dynamic/deleted stuff is not supported but does it make sense?
+ To spawn classes is not supported.
+ Match class selector is converted to Kea eval test when the corresponding
+ expression can be reduced. Fortunately it seems to be the common case!
+TODO: simplify the hardware address matching
+ Lease limit is not supported.
+
+Subclasses:
+-----------
+ No equivalent in Kea so waiting for an interesting and convertible use case.
+
+Hardware Addresses:
+-------------------
+ Kea supports only Ethernet.
+TODO: create a ticket
+
+Pools:
+------
+ All permissions are not supported by Kea at the exception of class members
+ but in a very different way so not convertible.
+ Mixed DHCPv6 address and prefix pools are not supported, perhaps in this
+ case the pool should be duplicated into pool and pd-pool instances?
+TODO implement and check move
+ The bootp stuff was ifdef's as bootp is obsolete.
+ Temporary (aka IA_TA) is commented ny the MA.
+ ISC DHCP supports interval ranges for prefix6. Kea has a different
+ and IMHO more powerful model.
+ Pool6 permissions are not supported.
+
+Authoritative declaration:
+--------------------------
+ No problem when set in the global scope and nothing else, so raise
+ an error when parsing finishes without a positive authoritative declaration.
+ Used no make some subnets known: ISC DHCP uses these subnets for
+ subnet selection but sends no response including to info only.
+ This is a feature without equivalent in Kea but IMHO not required.
+ Negative authoritative declarations are used as a kind of disabling
+ some parts so the MA comments them.
+
+Failover:
+---------
+ Raise a fatal error on usage attempts.
+
+Interfaces:
+-----------
+ Referenced interface names are pushed to an interfaces-config but it is
+ very (too!) easy to finish with a Kea config without any interface.
+TODO: add a comment in this case
+
+Hostnames:
+----------
+ ISC DHCP does dynamic resolution in parse_ip_addr_or_hostname.
+ Static (at conversion time) resolution to one address is done by
+ the MA for fixed-address. Resolution is considered as painful
+ in option values, i.e. there are better (and safer) ways to do this.
+TODO: check the multiple address comment is correctly taken
+
+Server DUID:
+------------
+ Kea does a similar thing in an incompatible way.
+
+Options:
+--------
+ Some options are known only in ISC DHCP (cf #5227), a few only by Kea.
+ Formats are supposed to be the same, the only known exception
+ (DHCPv4 domain-search) was fixed by #5087.
+ For option spaces DHCPv4 vendor-encapsulated-options (code 43, in general
+ associated to vendor-class-identifier code 60) uses a dedicated feature
+ which has currently no equivalent in Kea (but #5073 Kea 1.3 ticket
+ could fix this).
+ Option definitions are convertible with a few exception:
+ - no support in Kea for an array of records (mainly by the lack
+ of a corresponding syntax). BTW there is no known use too.
+ - no support in Kea for an array at the end of a record
+ (more useful, cf #5226)
+ - X format means ASCII or hexa, it is mapped to string and
+ the data parser checks for hexa and uses not CSV in such case.
+ It works well for monotyped options, less at the end of a record.
+ Note there is only one standard option with a problematic format,
+ the issue is more with user defined options.
+TODO check lq-relay-data DHCPv6 6X code 47
+ We have no example in ISC DHCP, Kea or standard but it is possible
+ than an option defined as a fixed sized record followed by
+ (encapsulated) suboptions bugs (it already bugs toElement).
+ For operations on options ISC DHCP has supersede, send, append,
+ prepend, default (set if not yet present), Kea puts them in code order
+ with a few built-in exceptions.
+ To finish there is no way today to enforce Kea to add an option
+ in a response (cf #5241 Kea 1.3 ticket).
+
+Duplicates:
+-----------
+ Many things in ISC DHCP can be duplicated:
+ - options can be redefined
+ - same host identifier used twice
+ - same fixed address used in tow different hosts
+ etc.
+ Kea is far more strict and IMHO it is a good thing. Now the MA does
+ no particular check and multiple definitions work only for classes
+ (because it is the way the ISC DHCP parse works).
+ If we have Docsis space options, they are standard in Kea so they
+ will conflict.
+
+Dynamic DNS:
+------------
+ Details are very different so the MA maps only basic parameters
+ at the global scope.
+
+
diff --git a/keama/keama.h b/keama/keama.h
index 9f3fc625..6f158bf8 100644
--- a/keama/keama.h
+++ b/keama/keama.h
@@ -301,7 +301,7 @@ enum dhcp_token peek_raw_token(const char **rval, unsigned *rlen,
size_t conf_file_parse(struct parse *);
void read_conf_file(struct parse *, const char *, int);
size_t conf_file_subparse(struct parse *, int);
-int parse_statement(struct parse *, int, int);
+isc_boolean_t parse_statement(struct parse *, int, isc_boolean_t);
void get_permit(struct parse *, struct element *);
void parse_pool_statement(struct parse *, int);
void parse_lbrace(struct parse *);
@@ -311,6 +311,7 @@ void parse_shared_net_declaration(struct parse *);
void parse_subnet_declaration(struct parse *);
void parse_subnet6_declaration(struct parse *);
void parse_group_declaration(struct parse *);
+void dissolve_group(struct parse *, struct element *);
struct element *parse_fixed_addr_param(struct parse *, enum dhcp_token);
void parse_address_range(struct parse *, int, size_t);
void parse_address_range6(struct parse *, int, size_t);
diff --git a/keama/parse.c b/keama/parse.c
index 713aadc8..bcfcd81b 100644
--- a/keama/parse.c
+++ b/keama/parse.c
@@ -3142,14 +3142,17 @@ parse_non_binary(struct element *expr,
if (context == context_numeric ||
context == context_data_or_numeric) {
skip_token(&val, NULL, cfile);
+ /* can also return a const-int */
resetInt(expr, atoi(val));
break;
}
case NUMBER_OR_NAME:
+ /* Return a const-data to make a difference with
+ a string literal. */
data = makeString(-1, "0x");
concatString(data, parse_hexa(cfile, NULL));
- resetString(expr, data);
+ mapSet(expr, createString(data), "const-data");
break;
case NS_FORMERR:
@@ -4845,6 +4848,7 @@ is_data_expression(struct element *expr)
mapContains(expr, "uppercase") ||
mapContains(expr, "option") ||
mapContains(expr, "hardware") ||
+ mapContains(expr, "const-data") ||
mapContains(expr, "packet") ||
mapContains(expr, "concat") ||
mapContains(expr, "encapsulate") ||
@@ -4872,6 +4876,7 @@ is_numeric_expression(struct element *expr)
mapContains(expr, "extract-int8") ||
mapContains(expr, "extract-int16") ||
mapContains(expr, "extract-int32") ||
+ mapContains(expr, "const-int") ||
mapContains(expr, "lease-time") ||
mapContains(expr, "add") ||
mapContains(expr, "subtract") ||
diff --git a/keama/reduce.c b/keama/reduce.c
index c3a553b5..4cc12fcb 100644
--- a/keama/reduce.c
+++ b/keama/reduce.c
@@ -953,6 +953,20 @@ print_data_expression(struct element *expr, isc_boolean_t *lose)
if (mapContains(expr, "hardware"))
return "hardware";
+ /* const-data */
+ if (mapContains(expr, "const-data")) {
+ struct element *arg;
+
+ arg = mapGet(expr, "const-data");
+ if ((arg == NULL) || (arg->type != ELEMENT_STRING)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ concatString(result, stringValue(arg));
+ return result->content;
+ }
+
/* packet */
if (mapContains(expr, "packet")) {
struct element *arg;
@@ -1586,6 +1600,22 @@ reduce_data_expression(struct element *expr, isc_boolean_t *literalp)
return createString(result);
}
+ /* const-data */
+ if (mapContains(expr, "const-data")) {
+ /*
+ * syntax := { "const-data": <string> }
+ * semantic: embedded string value
+ */
+ struct element *arg;
+
+ arg = mapGet(expr, "const-data");
+ if ((arg == NULL) || (arg->type != ELEMENT_STRING)) {
+ debug("can't get const-data argument");
+ return NULL;
+ }
+ return createString(stringValue(arg));
+ }
+
/* packet */
if (mapContains(expr, "packet"))
/*
@@ -2321,6 +2351,22 @@ print_numeric_expression(struct element *expr, isc_boolean_t *lose)
return result->content;
}
+ /* const-int */
+ if (mapContains(expr, "const-int")) {
+ struct element *arg;
+ char buf[20];
+
+ arg = mapGet(expr, "const-int");
+ if ((arg == NULL) || (arg->type != ELEMENT_INTEGER)) {
+ *lose = ISC_TRUE;
+ appendString(result, "???");
+ return result->content;
+ }
+ snprintf(buf, sizeof(buf), "%lld", (long long)intValue(arg));
+ result = makeString(-1, buf);
+ return result->content;
+ }
+
/* lease-time */
if (mapContains(expr, "lease-time"))
return "lease-time";
@@ -2684,6 +2730,22 @@ reduce_numeric_expression(struct element *expr)
return createInt(val);
}
+ /* const-int */
+ if (mapContains(expr, "const-int")) {
+ /*
+ * syntax := { "const-int": <integer> }
+ * semantic: embedded integer value
+ */
+ struct element *arg;
+
+ arg = mapGet(expr, "const-int");
+ if ((arg == NULL) || (arg->type != ELEMENT_INTEGER)) {
+ debug("can't get const-int argument");
+ return NULL;
+ }
+ return createInt(intValue(arg));
+ }
+
/* lease-time */
if (mapContains(expr, "lease-time"))
/*