diff options
author | Francis Dupont <fdupont@isc.org> | 2017-04-21 16:52:40 +0200 |
---|---|---|
committer | Francis Dupont <fdupont@isc.org> | 2018-11-29 16:42:13 +0100 |
commit | d904376d815e8275eaf88a7e9b611ffee611f20c (patch) | |
tree | 5aa75cda5276cec89728f0be901971598febde63 /keama | |
parent | ba9e4b40b2dbaf441a81476547f7527954886989 (diff) | |
download | isc-dhcp-d904376d815e8275eaf88a7e9b611ffee611f20c.tar.gz |
Checkpoint (wrote doc, began group)
Diffstat (limited to 'keama')
-rw-r--r-- | keama/confparse.c | 249 | ||||
-rw-r--r-- | keama/data.c | 40 | ||||
-rw-r--r-- | keama/data.h | 16 | ||||
-rw-r--r-- | keama/doc.txt | 446 | ||||
-rw-r--r-- | keama/keama.h | 3 | ||||
-rw-r--r-- | keama/parse.c | 7 | ||||
-rw-r--r-- | keama/reduce.c | 62 |
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(¶m->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(¶m->comments, comment); + } + comment = createComment("/// unhandled parameter"); + TAILQ_INSERT_TAIL(¶m->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")) /* |