diff options
author | Ted Lemon <source@isc.org> | 1998-04-19 23:24:48 +0000 |
---|---|---|
committer | Ted Lemon <source@isc.org> | 1998-04-19 23:24:48 +0000 |
commit | 07378a2a623376443b2efb1f1f59118c8532fad6 (patch) | |
tree | 66f872d0f68d9316e79af047d0c7e5b619cebe44 /server/class.c | |
parent | 38b1978d0e32ef1c800f3528e91425736d015fc8 (diff) | |
download | isc-dhcp-07378a2a623376443b2efb1f1f59118c8532fad6.tar.gz |
Support for classifying clients.
Diffstat (limited to 'server/class.c')
-rw-r--r-- | server/class.c | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/server/class.c b/server/class.c new file mode 100644 index 00000000..83069efe --- /dev/null +++ b/server/class.c @@ -0,0 +1,621 @@ +/* class.c + + Handling for client classes. */ + +/* + * Copyright (c) 1998 The Internet Software Consortium. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Internet Software Consortium nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This software has been written for the Internet Software Consortium + * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie + * Enterprises. To learn more about the Internet Software Consortium, + * see ``http://www.vix.com/isc''. To learn more about Vixie + * Enterprises, see ``http://www.vix.com''. + */ + +#ifndef lint +static char copyright[] = +"$Id: class.c,v 1.1 1998/04/19 23:24:48 mellon Exp $ Copyright (c) 1998 The Internet Software Consortium. All rights reserved.\n"; +#endif /* not lint */ + +#include "dhcpd.h" + +/* + * Internally, there are three basic kinds of classes: classes that are + * never matched, and must be assigned through classification rules (e.g., + * known-clients and unknown-clients, above), classes that are assigned + * by doing a hash lookup, and classes that must be matched on an individual + * basis. These classes are all declared the same way: + * + * class [<class-name>] { + * match if <match-expr>; + * ... + * } + * + * It is possible to declare a class that spawns other classes - if a client + * matches the class, a new class is created that matches the client's + * parameters more specifically. Classes that are created in this way are + * attached to the class that spawned them with a hash table, and if a client + * matches the hash, the more general test is not done. Care should be taken + * in constructing such classes: a poorly-chosen spawn test can cause such a + * class to grow without bound. + * + * class [<class-name>] { + * match if <match-expr>; + * spawn <spawn-expr>; + * } + * + * Testing a whole litany of classes can take quite a bit of time for each + * incoming packet. In order to make this process more efficient, it may + * be desirable to group classes into collections, and then write a more + * complicated set of classification rules so as to perform fewer tests. + * Classes can be grouped into collections by writing a collection statement + * in the class declaration: + * + * collection <collection-name>; + * + * By default, all classes are members of the "default" collection. + * + * Beware: if you declare a class to be part of a collection other than + * "default" but do not update the classification rules, that class will + * never be considered during the client classification process. + */ + +/* + * Expressions used to make matches: + * + * match_expr :== LPAREN match_expr RPAREN | + * match_expr OR match_expr | + * match_expr AND match_expr | + * NOT match_expr | + * test_expr + * + * test_expr :== extract_expr EQUALS extract_expr | + * CHECK_COLLECTION STRING + * + * extract_expr :== SUBSTRING extract_expr NUMBER NUMBER | + * SUFFIX extract_expr NUMBER | + * OPTION IDENTIFIER DOT IDENTIFIER | + * OPTION IDENTIFIER | + * CHADDR + * HTYPE + * HARDWARE + * data_expr + * + * data_expr :== STRING | + * hex_data_expr + * + * hex_data_expr :== HEX_NUMBER | + * hex_data_expr COLON HEX_NUMBER + * + * For example: + * + * chaddr = 08:00:2b:4c:2a:29 AND htype = 1; + * + * substring chaddr 0 3 = 08:00:2b; + * + * substring dhcp-client-identifier 1 3 = "RAS"; + * + * substring relay.circuit-id = 04:2c:59:31; + */ + +/* + * Clients are classified based on classification rules, which can be + * specified on a per-group basis. By default, the following classification + * rules apply: + * + * classification-rules { + * if chaddr in known-hardware { + * add-class "known-clients"; + * } else { + * add-class "unknown-clients"; + * } + * + * check-collection "default"; + * } + * + */ + +struct class unknown_class = { + (struct class *)0, + "unknown", + (struct hash_table *)0, + (struct match_expr *)0, + (struct match_expr *)0, + (struct group *)0, +}; + +struct class known_class = { + (struct class *)0, + "unknown", + (struct hash_table *)0, + (struct match_expr *)0, + (struct match_expr *)0, + (struct group *)0, +}; + +struct collection default_collection = { + (struct collection *)0, + "default", + (struct class *)0, +}; + +struct classification_rule *default_classification_rules; +struct named_hash *named_hashes; +struct named_hash *known_hardware_hash; + +/* Build the default classification rule tree. */ + +void classification_setup () +{ + struct classification_rule *top, *now; + struct match_expr *me, *ome; + + /* Allocate the hash table of known hardware addresses. */ + known_hardware_hash = (struct named_hash *) + dmalloc (sizeof (struct named_hash), + "known-hardware named hash"); + if (!known_hardware_hash) + error ("Can't allocate known-hardware named hash."); + memset (known_hardware_hash, 0, sizeof *known_hardware_hash); + known_hardware_hash -> name = "known-hardware"; + known_hardware_hash -> hash = new_hash (); + known_hardware_hash -> next = named_hashes; + named_hashes = known_hardware_hash; + + /* if ... */ + top = (struct classification_rule *) + dmalloc (sizeof (struct classification_rule), + "default classification test"); + if (!top) + error ("Can't allocate default classification test"); + memset (top, 0, sizeof *top); + top -> op = classify_if; + + /* hardware */ + ome = (struct match_expr *)dmalloc (sizeof (struct match_expr), + "default hardware expression"); + if (!ome) + error ("Can't allocate default hardware expression"); + memset (ome, 0, sizeof *ome); + ome -> op = match_hardware; + + /* if <expr> in known-hardware */ + me = (struct match_expr *)dmalloc (sizeof (struct match_expr), + "default match expression"); + if (!me) + error ("Can't allocate default match expression"); + memset (me, 0, sizeof *me); + me -> op = match_in; + me -> data.in.hash = known_hardware_hash; + me -> data.in.expr = ome; + top -> data.ie.expr = me; + + /* add-class "unknown" */ + now = (struct classification_rule *) + dmalloc (sizeof (struct classification_rule), + "add unknown classification rule"); + if (!now) + error ("Can't allocate add of unknown class"); + memset (now, 0, sizeof *now); + now -> op = classify_add; + now -> data.add = &unknown_class; + top -> data.ie.false = now; + + /* add-class "known" */ + now = (struct classification_rule *) + dmalloc (sizeof (struct classification_rule), + "add known classification rule"); + if (!now) + error ("Can't allocate add of known class"); + memset (now, 0, sizeof *now); + now -> op = classify_add; + now -> data.add = &known_class; + top -> data.ie.true = now; + + /* check-collection "default" */ + me = (struct match_expr *)dmalloc (sizeof (struct match_expr), + "default check expression"); + if (!me) + error ("Can't allocate default check expression"); + memset (me, 0, sizeof *me); + me -> op = match_check; + me -> data.check = &default_collection; + + /* eval ... */ + now = (struct classification_rule *) + dmalloc (sizeof (struct classification_rule), + "add default collection check rule"); + if (!now) + error ("Can't allocate check of default collection"); + memset (now, 0, sizeof *now); + now -> op = classify_eval; + now -> data.eval = me; + top -> next = now; + + default_classification_rules = top; +} + +void classify_client (packet) + struct packet *packet; +{ + run_classification_ruleset (packet, default_classification_rules); +} + +int run_classification_ruleset (packet, ruleset) + struct packet *packet; + struct classification_rule *ruleset; +{ + struct classification_rule *r; + + for (r = ruleset; r; r = r -> next) { + switch (r -> op) { + case classify_if: + if (!run_classification_ruleset + (packet, + evaluate_match_expression (packet, + r -> data.ie.expr) + ? r -> data.ie.true : r -> data.ie.false)) + return 0; + break; + + case classify_eval: + evaluate_match_expression (packet, r -> data.eval); + break; + + case classify_add: + classify (packet, r -> data.add); + break; + + case classify_break: + return 0; + + default: + error ("bogus classification rule type %d\n", r -> op); + } + } + + return 1; +} + +int evaluate_match_expression (packet, expr) + struct packet *packet; + struct match_expr *expr; +{ + struct data_string left, right; + int result; + + switch (expr -> op) { + case match_check: + return check_collection (packet, expr -> data.check); + + case match_equal: + left = evaluate_data_expression (packet, + expr -> data.equal.left); + right = evaluate_data_expression (packet, + expr -> data.equal.right); + if (left.len == right.len && !memcmp (left.data, + right.data, left.len)) + result = 1; + else + result = 0; + if (left.buffer) + dfree ("evaluate_match_expression", left.buffer); + if (right.buffer) + dfree ("evaluate_match_expression", right.buffer); + return result; + + case match_and: + return (evaluate_match_expression (packet, + expr -> data.and [0]) && + evaluate_match_expression (packet, + expr -> data.and [0])); + + case match_or: + return (evaluate_match_expression (packet, + expr -> data.or [0]) || + evaluate_match_expression (packet, + expr -> data.or [0])); + + case match_not: + return (!evaluate_match_expression (packet, expr -> data.not)); + + case match_in: + left = evaluate_data_expression (packet, expr -> data.in.expr); + return (int)hash_lookup (expr -> data.in.hash -> hash, + left.data, left.len); + + case match_substring: + case match_suffix: + case match_option: + case match_hardware: + case match_const_data: + case match_packet: + warn ("Data opcode in evaluate_match_expression: %d", + expr -> op); + return 0; + + case match_extract_int8: + case match_extract_int16: + case match_extract_int32: + case match_const_int: + warn ("Numeric opcode in evaluate_match_expression: %d", + expr -> op); + return 0; + } + + warn ("Bogus opcode in evaluate_match_expression: %d", expr -> op); + return 0; +} + +struct data_string evaluate_data_expression (packet, expr) + struct packet *packet; + struct match_expr *expr; +{ + struct data_string result, data; + int offset, len; + + switch (expr -> op) { + /* Extract N bytes starting at byte M of a data string. */ + case match_substring: + data = evaluate_data_expression (packet, + expr -> data.substring.expr); + + /* Evaluate the offset and length. */ + offset = evaluate_numeric_expression + (packet, expr -> data.substring.offset); + len = evaluate_numeric_expression + (packet, expr -> data.substring.len); + + /* If the offset is after end of the string, return + an empty string. */ + if (data.len <= offset) { + if (data.buffer) + dfree ("match_substring", data.buffer); + memset (&result, 0, sizeof result); + return result; + } + + /* Otherwise, do the adjustments and return what's left. */ + data.len -= offset; + if (data.len > len) { + data.len = len; + data.terminated = 0; + } + data.data += offset; + return data; + + /* Extract the last N bytes of a data string. */ + case match_suffix: + data = evaluate_data_expression (packet, + expr -> data.suffix.expr); + + /* Evaluate the length. */ + len = evaluate_numeric_expression + (packet, expr -> data.substring.len); + + /* If we are returning the last N bytes of a string whose + length is <= N, just return the string. */ + if (data.len <= len) + return data; + data.data += data.len - len; + data.len = len; + return data; + + /* Extract an option. */ + case match_option: + return ((*expr -> data.option.universe -> lookup_func) + (packet, expr -> data.option.code)); + + /* Combine the hardware type and address. */ + case match_hardware: + result.len = packet -> raw -> hlen + 1; + result.buffer = dmalloc (result.len, + "match_hardware"); + if (!result.buffer) { + warn ("no memory for match_hardware"); + result.len = 0; + } else { + result.buffer [0] = packet -> raw -> htype; + memcpy (&result.buffer [1], packet -> raw -> chaddr, + packet -> raw -> hlen); + } + result.data = result.buffer; + result.terminated = 0; + return result; + + /* Extract part of the raw packet. */ + case match_packet: + len = evaluate_numeric_expression (packet, + expr -> data.packet.len); + offset = evaluate_numeric_expression (packet, + expr -> data.packet.len); + if (offset > packet -> packet_length) { + warn ("match_packet on %s: length %d + offset %d > %d", + print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr), + len, offset, packet -> packet_length); + memset (&result, 0, sizeof result); + return result; + } + if (offset + len > packet -> packet_length) + result.len = packet -> packet_length - offset; + else + result.len = len; + result.data = ((unsigned char *)(packet -> raw)) + offset; + result.buffer = (unsigned char *)0; + result.terminated = 0; + return result; + + /* Some constant data... */ + case match_const_data: + return expr -> data.const_data; + + case match_check: + case match_equal: + case match_and: + case match_or: + case match_not: + case match_in: + warn ("Boolean opcode in evaluate_data_expression: %d", + expr -> op); + goto null_return; + + case match_extract_int8: + case match_extract_int16: + case match_extract_int32: + case match_const_int: + warn ("Numeric opcode in evaluate_data_expression: %d", + expr -> op); + goto null_return; + } + + warn ("Bogus opcode in evaluate_data_expression: %d", expr -> op); + null_return: + memset (&result, 0, sizeof result); + return result; +} + +unsigned long evaluate_numeric_expression (packet, expr) + struct packet *packet; + struct match_expr *expr; +{ + struct data_string data; + unsigned long result; + + switch (expr -> op) { + case match_check: + case match_equal: + case match_and: + case match_or: + case match_not: + case match_in: + warn ("Boolean opcode in evaluate_numeric_expression: %d", + expr -> op); + return 0; + + case match_substring: + case match_suffix: + case match_option: + case match_hardware: + case match_const_data: + case match_packet: + warn ("Data opcode in evaluate_numeric_expression: %d", + expr -> op); + return 0; + + case match_extract_int8: + data = evaluate_data_expression (packet, + expr -> data.extract_int8); + if (data.len < 1) + return 0; + return data.data [0]; + + case match_extract_int16: + data = evaluate_data_expression (packet, + expr -> data.extract_int16); + if (data.len < 2) + return 0; + return getUShort (data.data); + + case match_extract_int32: + data = evaluate_data_expression (packet, + expr -> data.extract_int32); + if (data.len < 4) + return 0; + return getULong (data.data); + + case match_const_int: + return expr -> data.const_int; + } + + warn ("Bogus opcode in evaluate_numeric_expression: %d", expr -> op); + return 0; +} + +int check_collection (packet, collection) + struct packet *packet; + struct collection *collection; +{ + struct class *class, *nc; + struct data_string data; + int matched = 0; + + for (class = collection -> classes; class; class = class -> nic) { + if (class -> hash) { + data = evaluate_data_expression (packet, + class -> spawn); + nc = (struct class *)hash_lookup (class -> hash, + data.data, data.len); + if (nc) { + classify (packet, class); + matched = 1; + continue; + } + } + if (class -> expr && + evaluate_match_expression (packet, + class -> expr)) { + if (class -> spawn) { + data = evaluate_data_expression + (packet, class -> spawn); + nc = (struct class *) + dmalloc (sizeof (struct class), + "class spawn"); + memset (nc, 0, sizeof *nc); + nc -> group = class -> group; + if (!class -> hash) + class -> hash = new_hash (); + add_hash (class -> hash, + data.data, data.len, + (unsigned char *)nc); + classify (packet, nc); + } else + classify (packet, class); + matched = 1; + } + } + return matched; +} + +void classify (packet, class) + struct packet *packet; + struct class *class; +{ + if (packet -> class_count < PACKET_MAX_CLASSES) + packet -> classes [packet -> class_count++] = class; + else + warn ("too many groups for %s", + print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr)); +} + |